15720: Merge branch 'master' into 15720-fed-user-list
[arvados.git] / lib / controller / rpc / conn.go
index e74e870ad0856d256d6a3b757a06d588b0370d7f..f4bc1733eaff7112caf4bdfc7c69f62ddae5c4a8 100644 (file)
@@ -8,6 +8,7 @@ import (
        "context"
        "crypto/tls"
        "encoding/json"
+       "errors"
        "fmt"
        "io"
        "net"
@@ -17,15 +18,21 @@ import (
        "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
+       "git.curoverse.com/arvados.git/sdk/go/auth"
 )
 
-type contextKey string
-
-const ContextKeyCredentials contextKey = "credentials"
-
 type TokenProvider func(context.Context) ([]string, error)
 
+func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
+       if incoming, ok := auth.FromContext(ctx); !ok {
+               return nil, errors.New("no token provided")
+       } else {
+               return incoming.Tokens, nil
+       }
+}
+
 type Conn struct {
+       SendHeader    http.Header
        clusterID     string
        httpClient    http.Client
        baseURL       url.URL
@@ -55,8 +62,11 @@ func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *C
                }
        }
        return &Conn{
-               clusterID:     clusterID,
-               httpClient:    http.Client{Transport: transport},
+               clusterID: clusterID,
+               httpClient: http.Client{
+                       CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
+                       Transport:     transport,
+               },
                baseURL:       *url,
                tokenProvider: tp,
        }
@@ -64,21 +74,22 @@ func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *C
 
 func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arvados.APIEndpoint, body io.Reader, opts interface{}) error {
        aClient := arvados.Client{
-               Client:  &conn.httpClient,
-               Scheme:  conn.baseURL.Scheme,
-               APIHost: conn.baseURL.Host,
+               Client:     &conn.httpClient,
+               Scheme:     conn.baseURL.Scheme,
+               APIHost:    conn.baseURL.Host,
+               SendHeader: conn.SendHeader,
        }
        tokens, err := conn.tokenProvider(ctx)
        if err != nil {
                return err
        } else if len(tokens) > 0 {
-               ctx = context.WithValue(ctx, "Authorization", "Bearer "+tokens[0])
+               ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
        } else {
                // Use a non-empty auth string to ensure we override
                // any default token set on aClient -- and to avoid
                // having the remote prompt us to send a token by
                // responding 401.
-               ctx = context.WithValue(ctx, "Authorization", "Bearer -")
+               ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
        }
 
        // Encode opts to JSON and decode from there to a
@@ -103,15 +114,53 @@ func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arva
                // remove it entirely.
                delete(params, "limit")
        }
+       if len(tokens) > 1 {
+               params["reader_tokens"] = tokens[1:]
+       }
        path := ep.Path
-       if strings.Contains(ep.Path, "/:uuid") {
+       if strings.Contains(ep.Path, "/{uuid}") {
                uuid, _ := params["uuid"].(string)
-               path = strings.Replace(path, "/:uuid", "/"+uuid, 1)
+               path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
                delete(params, "uuid")
        }
        return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
 }
 
+func (conn *Conn) BaseURL() url.URL {
+       return conn.baseURL
+}
+
+func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
+       ep := arvados.EndpointConfigGet
+       var resp json.RawMessage
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
+       return resp, err
+}
+
+func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
+       ep := arvados.EndpointLogin
+       var resp arvados.LoginResponse
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
+       return resp, err
+}
+
+// If the given location is a valid URL and its origin is the same as
+// conn.baseURL, return it as a relative URL. Otherwise, return it
+// unmodified.
+func (conn *Conn) relativeToBaseURL(location string) string {
+       u, err := url.Parse(location)
+       if err == nil && u.Scheme == conn.baseURL.Scheme && strings.ToLower(u.Host) == strings.ToLower(conn.baseURL.Host) {
+               u.Opaque = ""
+               u.Scheme = ""
+               u.User = nil
+               u.Host = ""
+               return u.String()
+       } else {
+               return location
+       }
+}
+
 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
        ep := arvados.EndpointCollectionCreate
        var resp arvados.Collection
@@ -161,6 +210,20 @@ func (conn *Conn) CollectionDelete(ctx context.Context, options arvados.DeleteOp
        return resp, err
 }
 
+func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
+       ep := arvados.EndpointCollectionTrash
+       var resp arvados.Collection
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
+func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
+       ep := arvados.EndpointCollectionUntrash
+       var resp arvados.Collection
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
        ep := arvados.EndpointContainerCreate
        var resp arvados.Container
@@ -245,9 +308,109 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti
        return resp, err
 }
 
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserCreate
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserUpdate
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserUpdateUUID
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserUpdateUUID
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserUpdateUUID
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+       ep := arvados.EndpointUserUpdateUUID
+       var resp map[string]interface{}
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserUpdateUUID
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserGet
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserGetCurrent
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserGetSystem
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+       ep := arvados.EndpointUserList
+       var resp arvados.UserList
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+       ep := arvados.EndpointUserDelete
+       var resp arvados.User
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
        ep := arvados.EndpointAPIClientAuthorizationCurrent
        var resp arvados.APIClientAuthorization
        err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
        return resp, err
 }
+
+type UserSessionAuthInfo struct {
+       Email           string   `json:"email"`
+       AlternateEmails []string `json:"alternate_emails"`
+       FirstName       string   `json:"first_name"`
+       LastName        string   `json:"last_name"`
+       Username        string   `json:"username"`
+}
+
+type UserSessionCreateOptions struct {
+       AuthInfo UserSessionAuthInfo `json:"auth_info"`
+       ReturnTo string              `json:"return_to"`
+}
+
+func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCreateOptions) (arvados.LoginResponse, error) {
+       ep := arvados.APIEndpoint{Method: "POST", Path: "auth/controller/callback"}
+       var resp arvados.LoginResponse
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}
+
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+       ep := arvados.APIEndpoint{Method: "PATCH", Path: "arvados/v1/users/batch_update"}
+       var resp arvados.UserList
+       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+       return resp, err
+}