import (
"context"
+ "database/sql"
+ "encoding/json"
"errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "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/httpserver"
)
type loginController interface {
}
func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) loginController {
- wantGoogle := cluster.Login.GoogleClientID != ""
- wantSSO := cluster.Login.ProviderAppID != ""
- wantPAM := cluster.Login.PAM
+ wantGoogle := cluster.Login.Google.Enable
+ wantOpenIDConnect := cluster.Login.OpenIDConnect.Enable
+ wantSSO := cluster.Login.SSO.Enable
+ wantPAM := cluster.Login.PAM.Enable
+ wantLDAP := cluster.Login.LDAP.Enable
switch {
- case wantGoogle && !wantSSO && !wantPAM:
- return &googleLoginController{Cluster: cluster, RailsProxy: railsProxy}
- case !wantGoogle && wantSSO && !wantPAM:
- return railsProxy
- case !wantGoogle && !wantSSO && wantPAM:
+ case wantGoogle && !wantOpenIDConnect && !wantSSO && !wantPAM && !wantLDAP:
+ return &oidcLoginController{
+ Cluster: cluster,
+ RailsProxy: railsProxy,
+ Issuer: "https://accounts.google.com",
+ ClientID: cluster.Login.Google.ClientID,
+ ClientSecret: cluster.Login.Google.ClientSecret,
+ UseGooglePeopleAPI: cluster.Login.Google.AlternateEmailAddresses,
+ EmailClaim: "email",
+ EmailVerifiedClaim: "email_verified",
+ }
+ case !wantGoogle && wantOpenIDConnect && !wantSSO && !wantPAM && !wantLDAP:
+ return &oidcLoginController{
+ Cluster: cluster,
+ RailsProxy: railsProxy,
+ Issuer: cluster.Login.OpenIDConnect.Issuer,
+ ClientID: cluster.Login.OpenIDConnect.ClientID,
+ ClientSecret: cluster.Login.OpenIDConnect.ClientSecret,
+ EmailClaim: cluster.Login.OpenIDConnect.EmailClaim,
+ EmailVerifiedClaim: cluster.Login.OpenIDConnect.EmailVerifiedClaim,
+ UsernameClaim: cluster.Login.OpenIDConnect.UsernameClaim,
+ }
+ case !wantGoogle && !wantOpenIDConnect && wantSSO && !wantPAM && !wantLDAP:
+ return &ssoLoginController{railsProxy}
+ case !wantGoogle && !wantOpenIDConnect && !wantSSO && wantPAM && !wantLDAP:
return &pamLoginController{Cluster: cluster, RailsProxy: railsProxy}
+ case !wantGoogle && !wantOpenIDConnect && !wantSSO && !wantPAM && wantLDAP:
+ return &ldapLoginController{Cluster: cluster, RailsProxy: railsProxy}
default:
return errorLoginController{
- error: errors.New("configuration problem: exactly one of Login.GoogleClientID, Login.ProviderAppID, or Login.PAM must be configured"),
+ error: errors.New("configuration problem: exactly one of Login.Google, Login.OpenIDConnect, Login.SSO, Login.PAM, and Login.LDAP must be enabled"),
}
}
}
+// Login and Logout are passed through to the wrapped railsProxy;
+// UserAuthenticate is rejected.
+type ssoLoginController struct{ *railsProxy }
+
+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)
+}
+
type errorLoginController struct{ error }
func (ctrl errorLoginController) Login(context.Context, arvados.LoginOptions) (arvados.LoginResponse, error) {
}
return arvados.LogoutResponse{RedirectLocation: target}, nil
}
+
+func createAPIClientAuthorization(ctx context.Context, conn *rpc.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
+ // opts.ReturnTo. We won't follow the resulting
+ // redirect target anyway.
+ ReturnTo: ",https://none.invalid",
+ AuthInfo: authinfo,
+ })
+ if err != nil {
+ return
+ }
+ target, err := url.Parse(newsession.RedirectLocation)
+ if err != nil {
+ return
+ }
+ token := target.Query().Get("api_token")
+ tx, err := currenttx(ctx)
+ if err != nil {
+ return
+ }
+ tokensecret := token
+ if strings.Contains(token, "/") {
+ tokenparts := strings.Split(token, "/")
+ if len(tokenparts) >= 3 {
+ tokensecret = tokenparts[2]
+ }
+ }
+ var exp sql.NullString
+ var scopes []byte
+ err = tx.QueryRowxContext(ctx, "select uuid, api_token, expires_at, scopes from api_client_authorizations where api_token=$1", tokensecret).Scan(&resp.UUID, &resp.APIToken, &exp, &scopes)
+ if err != nil {
+ return
+ }
+ resp.ExpiresAt = exp.String
+ if len(scopes) > 0 {
+ err = json.Unmarshal(scopes, &resp.Scopes)
+ if err != nil {
+ return resp, fmt.Errorf("unmarshal scopes: %s", err)
+ }
+ }
+ return
+}