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: OAuth2 ..." header
55 // (typically used by smart API clients)
56 if toks := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(toks) == 2 && (toks[0] == "OAuth2" || toks[0] == "Bearer") {
57 a.Tokens = append(a.Tokens, toks[1])
60 // Load base64-encoded token from "Authorization: Basic ..."
61 // header (typically used by git via credential helper)
62 if _, password, ok := r.BasicAuth(); ok {
63 a.Tokens = append(a.Tokens, password)
66 // Load tokens from query string. It's generally not a good
67 // idea to pass tokens around this way, but passing a narrowly
68 // scoped token is a reasonable way to implement "secret link
69 // to an object" in a generic way.
71 // ParseQuery always returns a non-nil map which might have
72 // valid parameters, even when a decoding error causes it to
73 // return a non-nil err. We ignore err; hopefully the caller
74 // will also need to parse the query string for
75 // application-specific purposes and will therefore
76 // find/report decoding errors in a suitable way.
77 qvalues, _ := url.ParseQuery(r.URL.RawQuery)
78 if val, ok := qvalues["api_token"]; ok {
79 a.Tokens = append(a.Tokens, val...)
82 a.loadTokenFromCookie(r)
84 // TODO: Load token from Rails session cookie (if Rails site
88 func (a *Credentials) loadTokenFromCookie(r *http.Request) {
89 cookie, err := r.Cookie("arvados_api_token")
90 if err != nil || len(cookie.Value) == 0 {
93 token, err := DecodeTokenCookie(cookie.Value)
97 a.Tokens = append(a.Tokens, string(token))
100 // LoadTokensFromHTTPRequestBody loads credentials from the request
103 // This is separate from LoadTokensFromHTTPRequest() because it's not
104 // always desirable to read the request body. This has to be requested
105 // explicitly by the application.
106 func (a *Credentials) LoadTokensFromHTTPRequestBody(r *http.Request) error {
107 if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
110 if err := r.ParseForm(); err != nil {
113 if t := r.PostFormValue("api_token"); t != "" {
114 a.Tokens = append(a.Tokens, t)