"net/url"
"regexp"
"strings"
-
- "git.curoverse.com/arvados.git/lib/config"
- "git.curoverse.com/arvados.git/lib/controller/localdb"
- "git.curoverse.com/arvados.git/lib/controller/rpc"
- "git.curoverse.com/arvados.git/sdk/go/arvados"
- "git.curoverse.com/arvados.git/sdk/go/auth"
- "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+ "time"
+
+ "git.arvados.org/arvados.git/lib/config"
+ "git.arvados.org/arvados.git/lib/controller/localdb"
+ "git.arvados.org/arvados.git/lib/controller/rpc"
+ "git.arvados.org/arvados.git/sdk/go/arvados"
+ "git.arvados.org/arvados.git/sdk/go/auth"
+ "git.arvados.org/arvados.git/sdk/go/ctxlog"
)
type Conn struct {
if !remote.Proxy {
continue
}
- remotes[id] = 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(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
}
return &Conn{
if err != nil {
return arvados.LoginResponse{}, fmt.Errorf("internal error getting redirect target: %s", err)
}
- target.RawQuery = url.Values{
+ params := url.Values{
"return_to": []string{options.ReturnTo},
- "remote": []string{options.Remote},
- }.Encode()
+ }
+ if options.Remote != "" {
+ params.Set("remote", options.Remote)
+ }
+ target.RawQuery = params.Encode()
return arvados.LoginResponse{
RedirectLocation: target.String(),
}, nil
}
func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
+ downstream := options.ForwardedFor
+ options.ForwardedFor = conn.cluster.ClusterID + "-" + downstream
if len(options.UUID) == 27 {
// UUID is really a UUID
c, err := conn.chooseBackend(options.UUID).CollectionGet(ctx, options)
// UUID is a PDH
first := make(chan arvados.Collection, 1)
err := conn.tryLocalThenRemotes(ctx, func(ctx context.Context, remoteID string, be backend) error {
+ if remoteID != "" && downstream != "" {
+ // If remoteID isn't in downstream, we
+ // might find the collection by taking
+ // another hop, but we don't bother:
+ // token salting and blob signature
+ // rewriting don't work over multiple
+ // hops.
+ return notFoundError{}
+ }
c, err := be.CollectionGet(ctx, options)
if err != nil {
return err
return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
}
+var userAttrsCachedFromLoginCluster = map[string]bool{
+ "created_at": true,
+ "email": true,
+ "first_name": true,
+ "is_active": true,
+ "is_admin": true,
+ "last_name": true,
+ "modified_at": true,
+ "modified_by_client_uuid": true,
+ "modified_by_user_uuid": true,
+ "prefs": true,
+ "username": true,
+
+ "etag": false,
+ "full_name": false,
+ "identity_url": false,
+ "is_invited": false,
+ "owner_uuid": false,
+ "uuid": false,
+ "writable_by": false,
+}
+
func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
- return conn.generated_UserList(ctx, options)
+ logger := ctxlog.FromContext(ctx)
+ if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID {
+ resp, err := conn.chooseBackend(id).UserList(ctx, options)
+ if err != nil {
+ return resp, err
+ }
+ batchOpts := arvados.UserBatchUpdateOptions{Updates: map[string]map[string]interface{}{}}
+ for _, user := range resp.Items {
+ if !strings.HasPrefix(user.UUID, id) {
+ continue
+ }
+ logger.Debugf("cache user info for uuid %q", user.UUID)
+
+ // If the remote cluster has null timestamps
+ // (e.g., test server with incomplete
+ // fixtures) use dummy timestamps (instead of
+ // the zero time, which causes a Rails API
+ // error "year too big to marshal: 1 UTC").
+ if user.ModifiedAt.IsZero() {
+ user.ModifiedAt = time.Now()
+ }
+ if user.CreatedAt.IsZero() {
+ user.CreatedAt = time.Now()
+ }
+
+ var allFields map[string]interface{}
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error encoding user record from remote response: %s", err)
+ }
+ err = json.Unmarshal(buf, &allFields)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error transcoding user record from remote response: %s", err)
+ }
+ updates := allFields
+ if len(options.Select) > 0 {
+ updates = map[string]interface{}{}
+ for _, k := range options.Select {
+ if v, ok := allFields[k]; ok && userAttrsCachedFromLoginCluster[k] {
+ updates[k] = v
+ }
+ }
+ } else {
+ for k := range updates {
+ if !userAttrsCachedFromLoginCluster[k] {
+ delete(updates, k)
+ }
+ }
+ }
+ batchOpts.Updates[user.UUID] = updates
+ }
+ if len(batchOpts.Updates) > 0 {
+ ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+ _, err = conn.local.UserBatchUpdate(ctxRoot, batchOpts)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error updating local user records: %s", err)
+ }
+ }
+ return resp, nil
+ } else {
+ return conn.generated_UserList(ctx, options)
+ }
}
func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
return conn.chooseBackend(options.UUID).UserDelete(ctx, options)
}
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ return conn.local.UserBatchUpdate(ctx, options)
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options)
}