"google.golang.org/api/people/v1"
)
-type googleLoginController struct {
+type oidcLoginController struct {
Cluster *arvados.Cluster
RailsProxy *railsProxy
+ Issuer string // OIDC issuer URL, e.g., "https://accounts.google.com"
+ GoogleAPI bool // Issuer is Google; use additional Google APIs/extensions as needed
- issuer string // override OIDC issuer URL (normally https://accounts.google.com) for testing
peopleAPIBasePath string // override Google People API base URL (normally set by google pkg to https://people.googleapis.com/)
provider *oidc.Provider
mu sync.Mutex
}
-func (ctrl *googleLoginController) getProvider() (*oidc.Provider, error) {
+func (ctrl *oidcLoginController) getProvider() (*oidc.Provider, error) {
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
if ctrl.provider == nil {
- issuer := ctrl.issuer
- if issuer == "" {
- issuer = "https://accounts.google.com"
- }
- provider, err := oidc.NewProvider(context.Background(), issuer)
+ provider, err := oidc.NewProvider(context.Background(), ctrl.Issuer)
if err != nil {
return nil, err
}
return ctrl.provider, nil
}
-func (ctrl *googleLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
+func (ctrl *oidcLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
return noopLogout(ctrl.Cluster, opts)
}
-func (ctrl *googleLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+func (ctrl *oidcLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
provider, err := ctrl.getProvider()
if err != nil {
return loginError(fmt.Errorf("error setting up OpenID Connect provider: %s", err))
ClientID: conf.ClientID,
})
if opts.State == "" {
- // Initiate Google sign-in.
+ // Initiate OIDC sign-in.
if opts.ReturnTo == "" {
return loginError(errors.New("missing return_to parameter"))
}
oauth2.SetAuthURLParam("prompt", "select_account")),
}, nil
} else {
- // Callback after Google sign-in.
+ // Callback after OIDC sign-in.
state := ctrl.parseOAuth2State(opts.State)
if !state.verify([]byte(ctrl.Cluster.SystemRootToken)) {
return loginError(errors.New("invalid OAuth2 state"))
}
}
-func (ctrl *googleLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
+func (ctrl *oidcLoginController) 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)
}
// primary address at index 0. The provided defaultAddr is always
// included in the returned slice, and is used as the primary if the
// Google API does not indicate one.
-func (ctrl *googleLoginController) getAuthInfo(ctx context.Context, cluster *arvados.Cluster, conf *oauth2.Config, token *oauth2.Token, idToken *oidc.IDToken) (*rpc.UserSessionAuthInfo, error) {
+func (ctrl *oidcLoginController) getAuthInfo(ctx context.Context, cluster *arvados.Cluster, conf *oauth2.Config, token *oauth2.Token, idToken *oidc.IDToken) (*rpc.UserSessionAuthInfo, error) {
var ret rpc.UserSessionAuthInfo
defer ctxlog.FromContext(ctx).WithField("ret", &ret).Debug("getAuthInfo returned")
ret.Email = claims.Email
}
- if !ctrl.Cluster.Login.Google.AlternateEmailAddresses {
+ if !ctrl.Cluster.Login.Google.AlternateEmailAddresses || !ctrl.GoogleAPI {
if ret.Email == "" {
return nil, fmt.Errorf("cannot log in with unverified email address %q", claims.Email)
}
return
}
-func (ctrl *googleLoginController) newOAuth2State(key []byte, remote, returnTo string) oauth2State {
+func (ctrl *oidcLoginController) newOAuth2State(key []byte, remote, returnTo string) oauth2State {
s := oauth2State{
Time: time.Now().Unix(),
Remote: remote,
ReturnTo string // redirect target
}
-func (ctrl *googleLoginController) parseOAuth2State(encoded string) (s oauth2State) {
+func (ctrl *oidcLoginController) parseOAuth2State(encoded string) (s oauth2State) {
// Errors are not checked. If decoding/parsing fails, the
// token will be rejected by verify().
decoded, _ := base64.RawURLEncoding.DecodeString(encoded)
c.Assert(err, check.IsNil)
s.localdb = NewConn(s.cluster)
- s.localdb.loginController.(*googleLoginController).issuer = s.fakeIssuer.URL
- s.localdb.loginController.(*googleLoginController).peopleAPIBasePath = s.fakePeopleAPI.URL
+ c.Assert(s.localdb.loginController, check.FitsTypeOf, (*oidcLoginController)(nil))
+ c.Check(s.localdb.loginController.(*oidcLoginController).Issuer, check.Equals, "https://accounts.google.com")
+ s.localdb.loginController.(*oidcLoginController).Issuer = s.fakeIssuer.URL
+ s.localdb.loginController.(*oidcLoginController).peopleAPIBasePath = s.fakePeopleAPI.URL
s.railsSpy = arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
*s.localdb.railsProxy = *rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider)
c.Check(target.Host, check.Equals, issuerURL.Host)
q := target.Query()
c.Check(q.Get("client_id"), check.Equals, "test%client$id")
- state := s.localdb.loginController.(*googleLoginController).parseOAuth2State(q.Get("state"))
+ state := s.localdb.loginController.(*oidcLoginController).parseOAuth2State(q.Get("state"))
c.Check(state.verify([]byte(s.cluster.SystemRootToken)), check.Equals, true)
c.Check(state.Time, check.Not(check.Equals), 0)
c.Check(state.Remote, check.Equals, remote)
w.WriteHeader(http.StatusForbidden)
fmt.Fprintln(w, `Error 403: accessNotConfigured`)
}))
- s.localdb.loginController.(*googleLoginController).peopleAPIBasePath = s.fakePeopleAPI.URL
+ s.localdb.loginController.(*oidcLoginController).peopleAPIBasePath = s.fakePeopleAPI.URL
}
func (s *LoginSuite) TestGoogleLogin_PeopleAPIDisabled(c *check.C) {