16718: Merge branch 'master' into 16718-group-contents-collection-versions
[arvados.git] / sdk / go / auth / auth.go
index 4a719e922dd8a98bdc88445f413ea88788b01a53..f1c2e243b53a8f5d7ae604d1b67df55968430fcd 100644 (file)
@@ -1,6 +1,12 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
 package auth
 
 import (
+       "context"
+       "encoding/base64"
        "net/http"
        "net/url"
        "strings"
@@ -10,28 +16,50 @@ type Credentials struct {
        Tokens []string
 }
 
-func NewCredentials() *Credentials {
-       return &Credentials{Tokens: []string{}}
+func NewCredentials(tokens ...string) *Credentials {
+       return &Credentials{Tokens: tokens}
+}
+
+func NewContext(ctx context.Context, c *Credentials) context.Context {
+       return context.WithValue(ctx, contextKeyCredentials{}, c)
+}
+
+func FromContext(ctx context.Context) (*Credentials, bool) {
+       c, ok := ctx.Value(contextKeyCredentials{}).(*Credentials)
+       return c, ok
 }
 
-func NewCredentialsFromHTTPRequest(r *http.Request) *Credentials {
+func CredentialsFromRequest(r *http.Request) *Credentials {
+       if c, ok := FromContext(r.Context()); ok {
+               // preloaded by middleware
+               return c
+       }
        c := NewCredentials()
        c.LoadTokensFromHTTPRequest(r)
        return c
 }
 
-// LoadTokensFromHttpRequest loads all tokens it can find in the
+// EncodeTokenCookie accepts a token and returns a byte slice suitable
+// for use as a cookie value, such that it will be decoded correctly
+// by LoadTokensFromHTTPRequest.
+var EncodeTokenCookie func([]byte) string = base64.URLEncoding.EncodeToString
+
+// DecodeTokenCookie accepts a cookie value and returns the encoded
+// token.
+var DecodeTokenCookie func(string) ([]byte, error) = base64.URLEncoding.DecodeString
+
+// LoadTokensFromHTTPRequest loads all tokens it can find in the
 // headers and query string of an http query.
 func (a *Credentials) LoadTokensFromHTTPRequest(r *http.Request) {
        // Load plain token from "Authorization: OAuth2 ..." header
        // (typically used by smart API clients)
-       if toks := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(toks) == 2 && toks[0] == "OAuth2" {
+       if toks := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(toks) == 2 && (toks[0] == "OAuth2" || toks[0] == "Bearer") {
                a.Tokens = append(a.Tokens, toks[1])
        }
 
        // Load base64-encoded token from "Authorization: Basic ..."
        // header (typically used by git via credential helper)
-       if _, password, ok := BasicAuth(r); ok {
+       if _, password, ok := r.BasicAuth(); ok {
                a.Tokens = append(a.Tokens, password)
        }
 
@@ -51,11 +79,39 @@ func (a *Credentials) LoadTokensFromHTTPRequest(r *http.Request) {
                a.Tokens = append(a.Tokens, val...)
        }
 
+       a.loadTokenFromCookie(r)
+
        // TODO: Load token from Rails session cookie (if Rails site
        // secret is known)
 }
 
-// TODO: LoadTokensFromHttpRequestBody(). We can't assume in
-// LoadTokensFromHttpRequest() that [or how] we should read and parse
-// the request body. This has to be requested explicitly by the
-// application.
+func (a *Credentials) loadTokenFromCookie(r *http.Request) {
+       cookie, err := r.Cookie("arvados_api_token")
+       if err != nil || len(cookie.Value) == 0 {
+               return
+       }
+       token, err := DecodeTokenCookie(cookie.Value)
+       if err != nil {
+               return
+       }
+       a.Tokens = append(a.Tokens, string(token))
+}
+
+// LoadTokensFromHTTPRequestBody loads credentials from the request
+// body.
+//
+// This is separate from LoadTokensFromHTTPRequest() because it's not
+// always desirable to read the request body. This has to be requested
+// explicitly by the application.
+func (a *Credentials) LoadTokensFromHTTPRequestBody(r *http.Request) error {
+       if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
+               return nil
+       }
+       if err := r.ParseForm(); err != nil {
+               return err
+       }
+       if t := r.PostFormValue("api_token"); t != "" {
+               a.Tokens = append(a.Tokens, t)
+       }
+       return nil
+}