17014: Port container request federation code to new style (WIP).
authorTom Clegg <tom@tomclegg.ca>
Mon, 19 Oct 2020 19:04:22 +0000 (15:04 -0400)
committerTom Clegg <tom@tomclegg.ca>
Mon, 19 Oct 2020 19:04:22 +0000 (15:04 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@tomclegg.ca>

lib/controller/federation/conn.go
lib/controller/localdb/conn.go
lib/controller/localdb/login.go
lib/controller/localdb/login_ldap.go
lib/controller/localdb/login_oidc.go
lib/controller/localdb/login_pam.go
lib/controller/localdb/login_testuser.go
lib/controller/rpc/conn.go
services/api/app/controllers/user_sessions_controller.rb

index 955d184c56c65c6bcec987caf3dcf7a12930e152..d1c61116d8da121fc00fcb8e32a075700c08a499 100644 (file)
@@ -26,7 +26,7 @@ import (
 
 type Conn struct {
        cluster *arvados.Cluster
-       local   backend
+       local   *localdb.Conn
        remotes map[string]backend
 }
 
@@ -333,7 +333,43 @@ func (conn *Conn) ContainerRequestList(ctx context.Context, options arvados.List
 }
 
 func (conn *Conn) ContainerRequestCreate(ctx context.Context, options arvados.CreateOptions) (arvados.ContainerRequest, error) {
-       return conn.chooseBackend(options.ClusterID).ContainerRequestCreate(ctx, options)
+       be := conn.chooseBackend(options.ClusterID)
+       if be == conn.local {
+               return be.ContainerRequestCreate(ctx, options)
+       }
+       if _, ok := options.Attrs["runtime_token"]; !ok {
+               // If runtime_token is not set, create a new token
+               aca, err := conn.local.APIClientAuthorizationCurrent(ctx, arvados.GetOptions{})
+               if err != nil {
+                       // This should probably be StatusUnauthorized
+                       // (need to update test in
+                       // lib/controller/federation_test.go):
+                       return arvados.ContainerRequest{}, httpErrorf(http.StatusForbidden, "%w", err)
+               }
+               user, err := conn.local.UserGetCurrent(ctx, arvados.GetOptions{})
+               if err != nil {
+                       return arvados.ContainerRequest{}, err
+               }
+               if len(aca.Scopes) != 0 || aca.Scopes[0] != "all" {
+                       return arvados.ContainerRequest{}, httpErrorf(http.StatusForbidden, "token scope is not [all]")
+               }
+               if strings.HasPrefix(aca.UUID, conn.cluster.ClusterID) {
+                       // Local user, submitting to a remote cluster.
+                       // Create a new (FIXME: needs to be
+                       // time-limited!) token.
+                       aca, err = localdb.CreateAPIClientAuthorization(ctx, conn.local, conn.cluster.SystemRootToken, rpc.UserSessionAuthInfo{UserUUID: user.UUID})
+                       if err != nil {
+                               return arvados.ContainerRequest{}, err
+                       }
+                       options.Attrs["runtime_token"] = aca.TokenV2()
+               } else {
+                       // Remote user. Container request will use the
+                       // current token, minus the trailing portion
+                       // (optional container uuid).
+                       options.Attrs["runtime_token"] = aca.TokenV2()
+               }
+       }
+       return be.ContainerRequestCreate(ctx, options)
 }
 
 func (conn *Conn) ContainerRequestUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.ContainerRequest, error) {
index 4f0035edf993ad525c4d82b8d5e880049432c6c2..ac120c6f389514451aeed8cfa0c8ad16aa02b4b4 100644 (file)
@@ -24,10 +24,10 @@ func NewConn(cluster *arvados.Cluster) *Conn {
        railsProxy := railsproxy.NewConn(cluster)
        var conn Conn
        conn = Conn{
-               cluster:         cluster,
-               railsProxy:      railsProxy,
-               loginController: chooseLoginController(cluster, railsProxy),
+               cluster:    cluster,
+               railsProxy: railsProxy,
        }
+       conn.loginController = chooseLoginController(cluster, &conn)
        return &conn
 }
 
index f4632751e30dc24944d04157e939d676ee33c53a..b14fd3b7d236b3aa7057e5d093865f20db9b3d6a 100644 (file)
@@ -27,7 +27,7 @@ type loginController interface {
        UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error)
 }
 
-func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) loginController {
+func chooseLoginController(cluster *arvados.Cluster, parent *Conn) loginController {
        wantGoogle := cluster.Login.Google.Enable
        wantOpenIDConnect := cluster.Login.OpenIDConnect.Enable
        wantSSO := cluster.Login.SSO.Enable
@@ -43,7 +43,7 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
        case wantGoogle:
                return &oidcLoginController{
                        Cluster:            cluster,
-                       RailsProxy:         railsProxy,
+                       Parent:             parent,
                        Issuer:             "https://accounts.google.com",
                        ClientID:           cluster.Login.Google.ClientID,
                        ClientSecret:       cluster.Login.Google.ClientSecret,
@@ -54,7 +54,7 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
        case wantOpenIDConnect:
                return &oidcLoginController{
                        Cluster:            cluster,
-                       RailsProxy:         railsProxy,
+                       Parent:             parent,
                        Issuer:             cluster.Login.OpenIDConnect.Issuer,
                        ClientID:           cluster.Login.OpenIDConnect.ClientID,
                        ClientSecret:       cluster.Login.OpenIDConnect.ClientSecret,
@@ -63,13 +63,13 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
                        UsernameClaim:      cluster.Login.OpenIDConnect.UsernameClaim,
                }
        case wantSSO:
-               return &ssoLoginController{railsProxy}
+               return &ssoLoginController{parent}
        case wantPAM:
-               return &pamLoginController{Cluster: cluster, RailsProxy: railsProxy}
+               return &pamLoginController{Cluster: cluster, Parent: parent}
        case wantLDAP:
-               return &ldapLoginController{Cluster: cluster, RailsProxy: railsProxy}
+               return &ldapLoginController{Cluster: cluster, Parent: parent}
        case wantTest:
-               return &testLoginController{Cluster: cluster, RailsProxy: railsProxy}
+               return &testLoginController{Cluster: cluster, Parent: parent}
        case wantLoginCluster:
                return &federatedLoginController{Cluster: cluster}
        default:
@@ -91,7 +91,7 @@ func countTrue(vals ...bool) int {
 
 // Login and Logout are passed through to the wrapped railsProxy;
 // UserAuthenticate is rejected.
-type ssoLoginController struct{ *railsProxy }
+type ssoLoginController struct{ *Conn }
 
 func (ctrl *ssoLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
        return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(errors.New("username/password authentication is not available"), http.StatusBadRequest)
@@ -135,7 +135,7 @@ func noopLogout(cluster *arvados.Cluster, opts arvados.LogoutOptions) (arvados.L
        return arvados.LogoutResponse{RedirectLocation: target}, nil
 }
 
-func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (resp arvados.APIClientAuthorization, err error) {
+func CreateAPIClientAuthorization(ctx context.Context, conn *Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (resp arvados.APIClientAuthorization, err error) {
        ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{rootToken}})
        newsession, err := conn.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
                // Send a fake ReturnTo value instead of the caller's
index 6c430d69bbfee5505c056430540000059c0b4c4b..a03082e302213912ac8fd3a1d304c3a3d76e5b75 100644 (file)
@@ -21,8 +21,8 @@ import (
 )
 
 type ldapLoginController struct {
-       Cluster    *arvados.Cluster
-       RailsProxy *railsProxy
+       Cluster *arvados.Cluster
+       Parent  *Conn
 }
 
 func (ctrl *ldapLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -143,7 +143,7 @@ func (ctrl *ldapLoginController) UserAuthenticate(ctx context.Context, opts arva
                return arvados.APIClientAuthorization{}, errors.New("authentication succeeded but ldap returned no email address")
        }
 
-       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+       return CreateAPIClientAuthorization(ctx, ctrl.Parent, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
                Email:     email,
                FirstName: attrs["givenname"],
                LastName:  attrs["sn"],
index e0b01f13ebee8c4f01084d0dc4c8dca76804e696..d82b2adb21367152089a9de7807252aa159676ec 100644 (file)
@@ -32,7 +32,7 @@ import (
 
 type oidcLoginController struct {
        Cluster            *arvados.Cluster
-       RailsProxy         *railsProxy
+       Parent             *Conn
        Issuer             string // OIDC issuer URL, e.g., "https://accounts.google.com"
        ClientID           string
        ClientSecret       string
@@ -129,7 +129,7 @@ func (ctrl *oidcLoginController) Login(ctx context.Context, opts arvados.LoginOp
                return loginError(err)
        }
        ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{ctrl.Cluster.SystemRootToken}})
-       return ctrl.RailsProxy.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
+       return ctrl.Parent.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
                ReturnTo: state.Remote + "," + state.ReturnTo,
                AuthInfo: *authinfo,
        })
index 2447713a2cf453ea05cfc29e2c643fa0713848a9..365f919817322f617268ce84babe5b0a3862d847 100644 (file)
@@ -20,8 +20,8 @@ import (
 )
 
 type pamLoginController struct {
-       Cluster    *arvados.Cluster
-       RailsProxy *railsProxy
+       Cluster *arvados.Cluster
+       Parent  *Conn
 }
 
 func (ctrl *pamLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -87,7 +87,7 @@ func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvad
                "user":  user,
                "email": email,
        }).Debug("pam authentication succeeded")
-       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+       return CreateAPIClientAuthorization(ctx, ctrl.Parent, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
                Username: user,
                Email:    email,
        })
index 5852273529e6434b2f54ce7fcb551a85eb360880..d07105623872ab04112c09e9da070d769edc00c2 100644 (file)
@@ -17,8 +17,8 @@ import (
 )
 
 type testLoginController struct {
-       Cluster    *arvados.Cluster
-       RailsProxy *railsProxy
+       Cluster *arvados.Cluster
+       Parent  *Conn
 }
 
 func (ctrl *testLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -45,7 +45,7 @@ func (ctrl *testLoginController) UserAuthenticate(ctx context.Context, opts arva
                                "username": username,
                                "email":    user.Email,
                        }).Debug("test authentication succeeded")
-                       return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+                       return CreateAPIClientAuthorization(ctx, ctrl.Parent, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
                                Username: username,
                                Email:    user.Email,
                        })
index b0987cb46a7aa37efe3587cdca39fe247596d00f..5ffa668010e911398ba4f9a8c3cd583a86c0336f 100644 (file)
@@ -437,6 +437,7 @@ func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arv
 }
 
 type UserSessionAuthInfo struct {
+       UserUUID        string   `json:"user_uuid"`
        Email           string   `json:"email"`
        AlternateEmails []string `json:"alternate_emails"`
        FirstName       string   `json:"first_name"`
index 8e3c3ac5e3d8b8656d587e86626f86f57c33b045..0bf939d080e497233c540711c0204289f0628f13 100644 (file)
@@ -30,12 +30,20 @@ class UserSessionsController < ApplicationController
       authinfo = request.env['omniauth.auth']['info'].with_indifferent_access
     end
 
-    begin
-      user = User.register(authinfo)
-    rescue => e
-      Rails.logger.warn "User.register error #{e}"
-      Rails.logger.warn "authinfo was #{authinfo.inspect}"
-      return redirect_to login_failure_url
+    if !authinfo['user_uuid'].blank?
+      user = User.find_by_uuid(authinfo['user_uuid'])
+      if !user
+        Rails.logger.warn "Nonexistent user_uuid in authinfo #{authinfo.inspect}"
+        return redirect_to login_failure_url
+      end
+    else
+      begin
+        user = User.register(authinfo)
+      rescue => e
+        Rails.logger.warn "User.register error #{e}"
+        Rails.logger.warn "authinfo was #{authinfo.inspect}"
+        return redirect_to login_failure_url
+      end
     end
 
     # For the benefit of functional and integration tests: