16343: Test container request creation with LoginCluster enabled.
[arvados.git] / lib / controller / localdb / login_pam.go
index 1b1a05377aa4e4f864efb5ea0a875bf77ae19948..01dfc1379d3064b06ad7a3e7760d60250cc00a52 100644 (file)
@@ -6,7 +6,9 @@ package localdb
 
 import (
        "context"
+       "errors"
        "fmt"
+       "net/http"
        "net/url"
        "strings"
 
@@ -14,6 +16,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/httpserver"
        "github.com/msteinert/pam"
        "github.com/sirupsen/logrus"
 )
@@ -28,7 +31,12 @@ func (ctrl *pamLoginController) Logout(ctx context.Context, opts arvados.LogoutO
 }
 
 func (ctrl *pamLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+       return arvados.LoginResponse{}, errors.New("interactive login is not available")
+}
+
+func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
        errorMessage := ""
+       sentPassword := false
        tx, err := pam.StartFunc(ctrl.Cluster.Login.PAMService, opts.Username, func(style pam.Style, message string) (string, error) {
                ctxlog.FromContext(ctx).Debugf("pam conversation: style=%v message=%q", style, message)
                switch style {
@@ -40,24 +48,38 @@ func (ctrl *pamLoginController) Login(ctx context.Context, opts arvados.LoginOpt
                        ctxlog.FromContext(ctx).WithField("Message", message).Info("pam.TextInfo")
                        return "", nil
                case pam.PromptEchoOn, pam.PromptEchoOff:
+                       sentPassword = true
                        return opts.Password, nil
                default:
                        return "", fmt.Errorf("unrecognized message style %d", style)
                }
        })
        if err != nil {
-               return arvados.LoginResponse{Message: err.Error()}, nil
+               return arvados.APIClientAuthorization{}, err
        }
        err = tx.Authenticate(pam.DisallowNullAuthtok)
        if err != nil {
-               return arvados.LoginResponse{Message: err.Error()}, nil
+               err = fmt.Errorf("PAM: %s", err)
+               if errorMessage != "" {
+                       // Perhaps the error message in the
+                       // conversation is helpful.
+                       err = fmt.Errorf("%s; %q", err, errorMessage)
+               }
+               if sentPassword {
+                       err = fmt.Errorf("%s (with username %q and password)", err, opts.Username)
+               } else {
+                       // This might hint that the username was
+                       // invalid.
+                       err = fmt.Errorf("%s (with username %q; password was never requested by PAM service)", err, opts.Username)
+               }
+               return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(err, http.StatusUnauthorized)
        }
        if errorMessage != "" {
-               return arvados.LoginResponse{Message: errorMessage}, nil
+               return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(errors.New(errorMessage), http.StatusUnauthorized)
        }
        user, err := tx.GetItem(pam.User)
        if err != nil {
-               return arvados.LoginResponse{Message: err.Error()}, nil
+               return arvados.APIClientAuthorization{}, err
        }
        email := user
        if domain := ctrl.Cluster.Login.PAMDefaultEmailDomain; domain != "" && !strings.Contains(email, "@") {
@@ -66,20 +88,22 @@ func (ctrl *pamLoginController) Login(ctx context.Context, opts arvados.LoginOpt
        ctxlog.FromContext(ctx).WithFields(logrus.Fields{"user": user, "email": email}).Debug("pam authentication succeeded")
        ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{ctrl.Cluster.SystemRootToken}})
        resp, err := ctrl.RailsProxy.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
-               ReturnTo: opts.Remote + "," + opts.ReturnTo,
+               // 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: rpc.UserSessionAuthInfo{
                        Username: user,
                        Email:    email,
                },
        })
        if err != nil {
-               return arvados.LoginResponse{Message: err.Error()}, nil
+               return arvados.APIClientAuthorization{}, err
        }
        target, err := url.Parse(resp.RedirectLocation)
        if err != nil {
-               return arvados.LoginResponse{Message: err.Error()}, nil
+               return arvados.APIClientAuthorization{}, err
        }
-       resp.Token = target.Query().Get("api_token")
-       resp.RedirectLocation = ""
-       return resp, err
+       token := target.Query().Get("api_token")
+       return ctrl.RailsProxy.APIClientAuthorizationCurrent(auth.NewContext(ctx, auth.NewCredentials(token)), arvados.GetOptions{})
 }