Add 'sdk/java-v2/' from commit '55f103e336ca9fb8bf1720d2ef4ee8dd4e221118'
[arvados.git] / sdk / go / auth / auth.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package auth
6
7 import (
8         "encoding/base64"
9         "net/http"
10         "net/url"
11         "strings"
12 )
13
14 type Credentials struct {
15         Tokens []string
16 }
17
18 func NewCredentials() *Credentials {
19         return &Credentials{Tokens: []string{}}
20 }
21
22 func CredentialsFromRequest(r *http.Request) *Credentials {
23         if c, ok := r.Context().Value(contextKeyCredentials).(*Credentials); ok {
24                 // preloaded by middleware
25                 return c
26         }
27         c := NewCredentials()
28         c.LoadTokensFromHTTPRequest(r)
29         return c
30 }
31
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
36
37 // DecodeTokenCookie accepts a cookie value and returns the encoded
38 // token.
39 var DecodeTokenCookie func(string) ([]byte, error) = base64.URLEncoding.DecodeString
40
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])
48         }
49
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)
54         }
55
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.
60         //
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...)
70         }
71
72         a.loadTokenFromCookie(r)
73
74         // TODO: Load token from Rails session cookie (if Rails site
75         // secret is known)
76 }
77
78 func (a *Credentials) loadTokenFromCookie(r *http.Request) {
79         cookie, err := r.Cookie("arvados_api_token")
80         if err != nil || len(cookie.Value) == 0 {
81                 return
82         }
83         token, err := DecodeTokenCookie(cookie.Value)
84         if err != nil {
85                 return
86         }
87         a.Tokens = append(a.Tokens, string(token))
88 }
89
90 // LoadTokensFromHTTPRequestBody() loads credentials from the request
91 // body.
92 //
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" {
98                 return nil
99         }
100         if err := r.ParseForm(); err != nil {
101                 return err
102         }
103         if t := r.PostFormValue("api_token"); t != "" {
104                 a.Tokens = append(a.Tokens, t)
105         }
106         return nil
107 }