1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
14 type Credentials struct {
18 func NewCredentials() *Credentials {
19 return &Credentials{Tokens: []string{}}
22 func CredentialsFromRequest(r *http.Request) *Credentials {
23 if c, ok := r.Context().Value(contextKeyCredentials).(*Credentials); ok {
24 // preloaded by middleware
28 c.LoadTokensFromHTTPRequest(r)
32 // EncodeTokenCookie accepts a token and returns a byte slice suitable
33 // for use as a cookie value, such that it will be decoded correctly
34 // by LoadTokensFromHTTPRequest.
35 var EncodeTokenCookie func([]byte) string = base64.URLEncoding.EncodeToString
37 // DecodeTokenCookie accepts a cookie value and returns the encoded
39 var DecodeTokenCookie func(string) ([]byte, error) = base64.URLEncoding.DecodeString
41 // LoadTokensFromHTTPRequest loads all tokens it can find in the
42 // headers and query string of an http query.
43 func (a *Credentials) LoadTokensFromHTTPRequest(r *http.Request) {
44 // Load plain token from "Authorization: OAuth2 ..." header
45 // (typically used by smart API clients)
46 if toks := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(toks) == 2 && (toks[0] == "OAuth2" || toks[0] == "Bearer") {
47 a.Tokens = append(a.Tokens, toks[1])
50 // Load base64-encoded token from "Authorization: Basic ..."
51 // header (typically used by git via credential helper)
52 if _, password, ok := r.BasicAuth(); ok {
53 a.Tokens = append(a.Tokens, password)
56 // Load tokens from query string. It's generally not a good
57 // idea to pass tokens around this way, but passing a narrowly
58 // scoped token is a reasonable way to implement "secret link
59 // to an object" in a generic way.
61 // ParseQuery always returns a non-nil map which might have
62 // valid parameters, even when a decoding error causes it to
63 // return a non-nil err. We ignore err; hopefully the caller
64 // will also need to parse the query string for
65 // application-specific purposes and will therefore
66 // find/report decoding errors in a suitable way.
67 qvalues, _ := url.ParseQuery(r.URL.RawQuery)
68 if val, ok := qvalues["api_token"]; ok {
69 a.Tokens = append(a.Tokens, val...)
72 a.loadTokenFromCookie(r)
74 // TODO: Load token from Rails session cookie (if Rails site
78 func (a *Credentials) loadTokenFromCookie(r *http.Request) {
79 cookie, err := r.Cookie("arvados_api_token")
80 if err != nil || len(cookie.Value) == 0 {
83 token, err := DecodeTokenCookie(cookie.Value)
87 a.Tokens = append(a.Tokens, string(token))
90 // LoadTokensFromHTTPRequestBody() loads credentials from the request
93 // This is separate from LoadTokensFromHTTPRequest() because it's not
94 // always desirable to read the request body. This has to be requested
95 // explicitly by the application.
96 func (a *Credentials) LoadTokensFromHTTPRequestBody(r *http.Request) error {
97 if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
100 if err := r.ParseForm(); err != nil {
103 if t := r.PostFormValue("api_token"); t != "" {
104 a.Tokens = append(a.Tokens, t)