21361: Remove Debian 10 support from installer
[arvados.git] / lib / ctrlctx / auth.go
index 61c6253d419472924d63d0e90f26da2c8f9e0fa9..31746b64cca5c77a9d8aa695c67e0aa16d8f47ba 100644 (file)
@@ -9,7 +9,6 @@ import (
        "crypto/hmac"
        "crypto/sha256"
        "database/sql"
-       "encoding/json"
        "errors"
        "fmt"
        "io"
@@ -20,6 +19,7 @@ import (
        "git.arvados.org/arvados.git/lib/controller/api"
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/auth"
+       "github.com/ghodss/yaml"
 )
 
 var (
@@ -50,6 +50,27 @@ func WrapCallsWithAuth(cluster *arvados.Cluster) func(api.RoutableFunc) api.Rout
        }
 }
 
+// NewWithToken returns a context with the provided auth token.
+//
+// The incoming context must come from WrapCallsInTransactions or
+// NewWithTransaction.
+//
+// Used for attaching system auth to background threads.
+//
+// Also useful for tests, where context doesn't necessarily come from
+// a router that uses WrapCallsWithAuth.
+//
+// The returned context comes with its own token lookup cache, so
+// NewWithToken is not appropriate to use in a per-request code path.
+func NewWithToken(ctx context.Context, cluster *arvados.Cluster, token string) context.Context {
+       ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{token}})
+       return context.WithValue(ctx, contextKeyAuth, &authcontext{
+               authcache: &authcache{},
+               cluster:   cluster,
+               tokens:    []string{token},
+       })
+}
+
 // CurrentAuth returns the arvados.User whose privileges should be
 // used in the given context, and the arvados.APIClientAuthorization
 // the caller presented in order to authenticate the current request.
@@ -135,7 +156,7 @@ func (ac *authcache) lookup(ctx context.Context, cluster *arvados.Cluster, token
        var args []interface{}
        if len(token) > 30 && strings.HasPrefix(token, "v2/") && token[30] == '/' {
                fields := strings.Split(token, "/")
-               cond = `aca.uuid=$1 and aca.api_token=$2`
+               cond = `aca.uuid = $1 and aca.api_token = $2`
                args = []interface{}{fields[1], fields[2]}
        } else {
                // Bare token or OIDC access token
@@ -145,24 +166,26 @@ func (ac *authcache) lookup(ctx context.Context, cluster *arvados.Cluster, token
                cond = `aca.api_token in ($1, $2)`
                args = []interface{}{token, hmac}
        }
-       var scopesJSON []byte
+       var expiresAt sql.NullTime
+       var scopesYAML []byte
        err = tx.QueryRowContext(ctx, `
 select aca.uuid, aca.expires_at, aca.api_token, aca.scopes, users.uuid, users.is_active, users.is_admin
  from api_client_authorizations aca
  left join users on aca.user_id = users.id
  where `+cond+`
  and (expires_at is null or expires_at > current_timestamp at time zone 'UTC')`, args...).Scan(
-               &aca.UUID, &aca.ExpiresAt, &aca.APIToken, &scopesJSON,
+               &aca.UUID, &expiresAt, &aca.APIToken, &scopesYAML,
                &user.UUID, &user.IsActive, &user.IsAdmin)
        if err == sql.ErrNoRows {
                return nil, nil, nil
        } else if err != nil {
                return nil, nil, err
        }
-       if len(scopesJSON) > 0 {
-               err = json.Unmarshal(scopesJSON, &aca.Scopes)
+       aca.ExpiresAt = expiresAt.Time
+       if len(scopesYAML) > 0 {
+               err = yaml.Unmarshal(scopesYAML, &aca.Scopes)
                if err != nil {
-                       return nil, nil, err
+                       return nil, nil, fmt.Errorf("loading scopes for %s: %w", aca.UUID, err)
                }
        }
        ent = &authcacheent{