X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/27ab60e21b3cf9c908716ae74e63aba8e4cb6349..cac9d1111f86a7ff6da2176e3069dec4484154d4:/lib/controller/localdb/login_oidc.go diff --git a/lib/controller/localdb/login_oidc.go b/lib/controller/localdb/login_oidc.go index 2da7ca5ccf..74b8929a21 100644 --- a/lib/controller/localdb/login_oidc.go +++ b/lib/controller/localdb/login_oidc.go @@ -22,7 +22,6 @@ import ( "time" "git.arvados.org/arvados.git/lib/controller/api" - "git.arvados.org/arvados.git/lib/controller/railsproxy" "git.arvados.org/arvados.git/lib/controller/rpc" "git.arvados.org/arvados.git/lib/ctrlctx" "git.arvados.org/arvados.git/sdk/go/arvados" @@ -38,22 +37,24 @@ import ( "google.golang.org/api/people/v1" ) -const ( +var ( tokenCacheSize = 1000 tokenCacheNegativeTTL = time.Minute * 5 tokenCacheTTL = time.Minute * 10 + tokenCacheRaceWindow = time.Minute ) 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 - UseGooglePeopleAPI bool // Use Google People API to look up alternate email addresses - EmailClaim string // OpenID claim to use as email address; typically "email" - EmailVerifiedClaim string // If non-empty, ensure claim value is true before accepting EmailClaim; typically "email_verified" - UsernameClaim string // If non-empty, use as preferred username + UseGooglePeopleAPI bool // Use Google People API to look up alternate email addresses + EmailClaim string // OpenID claim to use as email address; typically "email" + EmailVerifiedClaim string // If non-empty, ensure claim value is true before accepting EmailClaim; typically "email_verified" + UsernameClaim string // If non-empty, use as preferred username + AuthParams map[string]string // Additional parameters to pass with authentication request // override Google People API base URL for testing purposes // (normally empty, set by google pkg to @@ -111,14 +112,12 @@ func (ctrl *oidcLoginController) Login(ctx context.Context, opts arvados.LoginOp return loginError(errors.New("missing return_to parameter")) } state := ctrl.newOAuth2State([]byte(ctrl.Cluster.SystemRootToken), opts.Remote, opts.ReturnTo) + var authparams []oauth2.AuthCodeOption + for k, v := range ctrl.AuthParams { + authparams = append(authparams, oauth2.SetAuthURLParam(k, v)) + } return arvados.LoginResponse{ - RedirectLocation: ctrl.oauth2conf.AuthCodeURL(state.String(), - // prompt=select_account tells Google - // to show the "choose which Google - // account" page, even if the client - // is currently logged in to exactly - // one Google account. - oauth2.SetAuthURLParam("prompt", "select_account")), + RedirectLocation: ctrl.oauth2conf.AuthCodeURL(state.String(), authparams...), }, nil } // Callback after OIDC sign-in. @@ -143,7 +142,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, }) @@ -177,7 +176,7 @@ func (ctrl *oidcLoginController) getAuthInfo(ctx context.Context, token *oauth2. if names := strings.Fields(strings.TrimSpace(name)); len(names) > 1 { ret.FirstName = strings.Join(names[0:len(names)-1], " ") ret.LastName = names[len(names)-1] - } else { + } else if len(names) > 0 { ret.FirstName = names[0] } ret.Email, _ = claims[ctrl.EmailClaim].(string) @@ -322,7 +321,7 @@ func OIDCAccessTokenAuthorizer(cluster *arvados.Cluster, getdb func(context.Cont // We want ctrl to be nil if the chosen controller is not a // *oidcLoginController, so we can ignore the 2nd return value // of this type cast. - ctrl, _ := chooseLoginController(cluster, railsproxy.NewConn(cluster)).(*oidcLoginController) + ctrl, _ := NewConn(cluster).loginController.(*oidcLoginController) cache, err := lru.New2Q(tokenCacheSize) if err != nil { panic(err) @@ -364,8 +363,9 @@ func (ta *oidcTokenAuthorizer) WrapCalls(origFunc api.RoutableFunc) api.Routable return origFunc(ctx, opts) } // Check each token in the incoming request. If any - // are OAuth2 access tokens, swap them out for Arvados - // tokens. + // are valid OAuth2 access tokens, insert/update them + // in the database so RailsAPI's auth code accepts + // them. for _, tok := range creds.Tokens { err = ta.registerToken(ctx, tok) if err != nil { @@ -390,9 +390,8 @@ func (ta *oidcTokenAuthorizer) registerToken(ctx context.Context, tok string) er // cached negative result (value is expiry time) if time.Now().Before(exp) { return nil - } else { - ta.cache.Remove(tok) } + ta.cache.Remove(tok) } else { // cached positive result aca := cached.(arvados.APIClientAuthorization) @@ -465,7 +464,7 @@ func (ta *oidcTokenAuthorizer) registerToken(ctx context.Context, tok string) er // Expiry time for our token is one minute longer than our // cache TTL, so we don't pass it through to RailsAPI just as // it's expiring. - exp := time.Now().UTC().Add(tokenCacheTTL + time.Minute) + exp := time.Now().UTC().Add(tokenCacheTTL + tokenCacheRaceWindow) var aca arvados.APIClientAuthorization if updating { @@ -475,7 +474,7 @@ func (ta *oidcTokenAuthorizer) registerToken(ctx context.Context, tok string) er } ctxlog.FromContext(ctx).WithField("HMAC", hmac).Debug("(*oidcTokenAuthorizer)registerToken: updated api_client_authorizations row") } else { - aca, err = createAPIClientAuthorization(ctx, ta.ctrl.RailsProxy, ta.ctrl.Cluster.SystemRootToken, *authinfo) + aca, err = ta.ctrl.Parent.CreateAPIClientAuthorization(ctx, ta.ctrl.Cluster.SystemRootToken, *authinfo) if err != nil { return err } @@ -490,6 +489,7 @@ func (ta *oidcTokenAuthorizer) registerToken(ctx context.Context, tok string) er if err != nil { return err } + aca.ExpiresAt = exp.Format(time.RFC3339Nano) ta.cache.Add(tok, aca) return nil }