1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
15 type Credentials struct {
19 func NewCredentials(tokens ...string) *Credentials {
20 return &Credentials{Tokens: tokens}
23 func NewContext(ctx context.Context, c *Credentials) context.Context {
24 return context.WithValue(ctx, contextKeyCredentials{}, c)
27 func FromContext(ctx context.Context) (*Credentials, bool) {
28 c, ok := ctx.Value(contextKeyCredentials{}).(*Credentials)
32 func CredentialsFromRequest(r *http.Request) *Credentials {
33 if c, ok := FromContext(r.Context()); ok {
34 // preloaded by middleware
38 c.LoadTokensFromHTTPRequest(r)
42 // EncodeTokenCookie accepts a token and returns a byte slice suitable
43 // for use as a cookie value, such that it will be decoded correctly
44 // by LoadTokensFromHTTPRequest.
45 var EncodeTokenCookie func([]byte) string = base64.URLEncoding.EncodeToString
47 // DecodeTokenCookie accepts a cookie value and returns the encoded
49 var DecodeTokenCookie func(string) ([]byte, error) = base64.URLEncoding.DecodeString
51 // LoadTokensFromHTTPRequest loads all tokens it can find in the
52 // headers and query string of an http query.
53 func (a *Credentials) LoadTokensFromHTTPRequest(r *http.Request) {
54 // Load plain token from "Authorization: Bearer ..." header
55 // (typically used by smart API clients). Note many pre-3.0
56 // clients send "OAuth2 ..." instead of "Bearer ..." and that
58 if toks := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(toks) == 2 && (toks[0] == "OAuth2" || toks[0] == "Bearer") {
59 a.Tokens = append(a.Tokens, strings.TrimSpace(toks[1]))
62 // Load base64-encoded token from "Authorization: Basic ..."
63 // header (typically used by git via credential helper)
64 if _, password, ok := r.BasicAuth(); ok {
65 a.Tokens = append(a.Tokens, strings.TrimSpace(password))
68 // Load tokens from query string. It's generally not a good
69 // idea to pass tokens around this way, but passing a narrowly
70 // scoped token is a reasonable way to implement "secret link
71 // to an object" in a generic way.
73 // ParseQuery always returns a non-nil map which might have
74 // valid parameters, even when a decoding error causes it to
75 // return a non-nil err. We ignore err; hopefully the caller
76 // will also need to parse the query string for
77 // application-specific purposes and will therefore
78 // find/report decoding errors in a suitable way.
79 qvalues, _ := url.ParseQuery(r.URL.RawQuery)
80 if val, ok := qvalues["api_token"]; ok {
81 for _, token := range val {
82 a.Tokens = append(a.Tokens, strings.TrimSpace(token))
86 a.loadTokenFromCookie(r)
88 // TODO: Load token from Rails session cookie (if Rails site
92 func (a *Credentials) loadTokenFromCookie(r *http.Request) {
93 cookie, err := r.Cookie("arvados_api_token")
94 if err != nil || len(cookie.Value) == 0 {
97 token, err := DecodeTokenCookie(cookie.Value)
101 a.Tokens = append(a.Tokens, strings.TrimSpace(string(token)))
104 // LoadTokensFromHTTPRequestBody loads credentials from the request
107 // This is separate from LoadTokensFromHTTPRequest() because it's not
108 // always desirable to read the request body. This has to be requested
109 // explicitly by the application.
110 func (a *Credentials) LoadTokensFromHTTPRequestBody(r *http.Request) error {
111 if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
114 if err := r.ParseForm(); err != nil {
117 if t := r.PostFormValue("api_token"); t != "" {
118 a.Tokens = append(a.Tokens, strings.TrimSpace(t))