19146: Update userAttrsCachedFromLoginCluster.
[arvados.git] / lib / controller / federation / conn.go
index 586ac23013a531dbcacdab9c705f307886abc158..d9f587852d149da46cd49ddbfc9dd46095fe180e 100644 (file)
@@ -22,6 +22,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/health"
 )
 
 type Conn struct {
@@ -30,20 +31,25 @@ type Conn struct {
        remotes map[string]backend
 }
 
-func New(cluster *arvados.Cluster) *Conn {
+func New(cluster *arvados.Cluster, healthFuncs *map[string]health.Func) *Conn {
        local := localdb.NewConn(cluster)
        remotes := map[string]backend{}
        for id, remote := range cluster.RemoteClusters {
                if !remote.Proxy || id == cluster.ClusterID {
                        continue
                }
-               conn := rpc.NewConn(id, &url.URL{Scheme: remote.Scheme, Host: remote.Host}, remote.Insecure, saltedTokenProvider(local, id))
+               conn := rpc.NewConn(id, &url.URL{Scheme: remote.Scheme, Host: remote.Host}, remote.Insecure, saltedTokenProvider(cluster, local, id))
                // Older versions of controller rely on the Via header
                // to detect loops.
                conn.SendHeader = http.Header{"Via": {"HTTP/1.1 arvados-controller"}}
                remotes[id] = conn
        }
 
+       if healthFuncs != nil {
+               hf := map[string]health.Func{"vocabulary": local.LastVocabularyError}
+               *healthFuncs = hf
+       }
+
        return &Conn{
                cluster: cluster,
                local:   local,
@@ -55,7 +61,7 @@ func New(cluster *arvados.Cluster) *Conn {
 // tokens from an incoming request context, determines whether they
 // should (and can) be salted for the given remoteID, and returns the
 // resulting tokens.
-func saltedTokenProvider(local backend, remoteID string) rpc.TokenProvider {
+func saltedTokenProvider(cluster *arvados.Cluster, local backend, remoteID string) rpc.TokenProvider {
        return func(ctx context.Context) ([]string, error) {
                var tokens []string
                incoming, ok := auth.FromContext(ctx)
@@ -63,6 +69,19 @@ func saltedTokenProvider(local backend, remoteID string) rpc.TokenProvider {
                        return nil, errors.New("no token provided")
                }
                for _, token := range incoming.Tokens {
+                       if strings.HasPrefix(token, "v2/"+cluster.ClusterID+"-") &&
+                               !strings.HasPrefix(token, "v2/"+cluster.ClusterID+"-gj3su-anonymouspublic/") &&
+                               remoteID == cluster.Login.LoginCluster {
+                               // If we did this, the login cluster would call back to us and then
+                               // reject our response because the user UUID prefix (i.e., the
+                               // LoginCluster prefix) won't match the token UUID prefix (i.e., our
+                               // prefix). The anonymous token is OK to forward, because (unlike other
+                               // local tokens for real users) the validation callback will return the
+                               // locally issued anonymous user ID instead of a login-cluster user ID.
+                               // That anonymous user ID gets mapped to the local anonymous user
+                               // automatically on the login cluster.
+                               return nil, httpErrorf(http.StatusUnauthorized, "cannot use a locally issued token to forward a request to our login cluster (%s)", remoteID)
+                       }
                        salted, err := auth.SaltToken(token, remoteID)
                        switch err {
                        case nil:
@@ -192,6 +211,10 @@ func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
        return json.RawMessage(buf.Bytes()), err
 }
 
+func (conn *Conn) VocabularyGet(ctx context.Context) (arvados.Vocabulary, error) {
+       return conn.chooseBackend(conn.cluster.ClusterID).VocabularyGet(ctx)
+}
+
 func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
        if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID {
                // defer entire login procedure to designated cluster
@@ -262,13 +285,26 @@ func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions)
                if err != nil {
                        return err
                }
-               // options.UUID is either hash+size or
-               // hash+size+hints; only hash+size need to
-               // match the computed PDH.
-               if pdh := arvados.PortableDataHash(c.ManifestText); pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") {
-                       err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID)
-                       ctxlog.FromContext(ctx).Warn(err)
-                       return err
+               haveManifest := true
+               if options.Select != nil {
+                       haveManifest = false
+                       for _, s := range options.Select {
+                               if s == "manifest_text" {
+                                       haveManifest = true
+                                       break
+                               }
+                       }
+               }
+               if haveManifest {
+                       pdh := arvados.PortableDataHash(c.ManifestText)
+                       // options.UUID is either hash+size or
+                       // hash+size+hints; only hash+size need to
+                       // match the computed PDH.
+                       if pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") {
+                               err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID)
+                               ctxlog.FromContext(ctx).Warn(err)
+                               return err
+                       }
                }
                if remoteID != "" {
                        c.ManifestText = rewriteManifest(c.ManifestText, remoteID)
@@ -452,6 +488,26 @@ func (conn *Conn) GroupUntrash(ctx context.Context, options arvados.UntrashOptio
        return conn.chooseBackend(options.UUID).GroupUntrash(ctx, options)
 }
 
+func (conn *Conn) LinkCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Link, error) {
+       return conn.chooseBackend(options.ClusterID).LinkCreate(ctx, options)
+}
+
+func (conn *Conn) LinkUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Link, error) {
+       return conn.chooseBackend(options.UUID).LinkUpdate(ctx, options)
+}
+
+func (conn *Conn) LinkGet(ctx context.Context, options arvados.GetOptions) (arvados.Link, error) {
+       return conn.chooseBackend(options.UUID).LinkGet(ctx, options)
+}
+
+func (conn *Conn) LinkList(ctx context.Context, options arvados.ListOptions) (arvados.LinkList, error) {
+       return conn.generated_LinkList(ctx, options)
+}
+
+func (conn *Conn) LinkDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Link, error) {
+       return conn.chooseBackend(options.UUID).LinkDelete(ctx, options)
+}
+
 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
        return conn.generated_SpecimenList(ctx, options)
 }
@@ -472,6 +528,10 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti
        return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
 }
 
+func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
+       return conn.local.SysTrashSweep(ctx, options)
+}
+
 var userAttrsCachedFromLoginCluster = map[string]bool{
        "created_at":  true,
        "email":       true,
@@ -493,6 +553,8 @@ var userAttrsCachedFromLoginCluster = map[string]bool{
        "owner_uuid":              false,
        "uuid":                    false,
        "writable_by":             false,
+       "can_write":               false,
+       "can_manage":              false,
 }
 
 func (conn *Conn) batchUpdateUsers(ctx context.Context,
@@ -672,6 +734,39 @@ func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arv
        return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options)
 }
 
+func (conn *Conn) APIClientAuthorizationCreate(ctx context.Context, options arvados.CreateOptions) (arvados.APIClientAuthorization, error) {
+       if conn.cluster.Login.LoginCluster != "" {
+               return conn.chooseBackend(conn.cluster.Login.LoginCluster).APIClientAuthorizationCreate(ctx, options)
+       }
+       ownerUUID, ok := options.Attrs["owner_uuid"].(string)
+       if ok && ownerUUID != "" {
+               return conn.chooseBackend(ownerUUID).APIClientAuthorizationCreate(ctx, options)
+       }
+       return conn.local.APIClientAuthorizationCreate(ctx, options)
+}
+
+func (conn *Conn) APIClientAuthorizationUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.APIClientAuthorization, error) {
+       if options.BypassFederation {
+               return conn.local.APIClientAuthorizationUpdate(ctx, options)
+       }
+       return conn.chooseBackend(options.UUID).APIClientAuthorizationUpdate(ctx, options)
+}
+
+func (conn *Conn) APIClientAuthorizationDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.APIClientAuthorization, error) {
+       return conn.chooseBackend(options.UUID).APIClientAuthorizationDelete(ctx, options)
+}
+
+func (conn *Conn) APIClientAuthorizationList(ctx context.Context, options arvados.ListOptions) (arvados.APIClientAuthorizationList, error) {
+       if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID && !options.BypassFederation {
+               return conn.chooseBackend(conn.cluster.Login.LoginCluster).APIClientAuthorizationList(ctx, options)
+       }
+       return conn.generated_APIClientAuthorizationList(ctx, options)
+}
+
+func (conn *Conn) APIClientAuthorizationGet(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
+       return conn.chooseBackend(options.UUID).APIClientAuthorizationGet(ctx, options)
+}
+
 type backend interface {
        arvados.API
        BaseURL() url.URL