1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
16 "git.arvados.org/arvados.git/lib/controller/rpc"
17 "git.arvados.org/arvados.git/sdk/go/arvados"
18 "git.arvados.org/arvados.git/sdk/go/ctxlog"
19 "git.arvados.org/arvados.git/sdk/go/httpserver"
20 "github.com/msteinert/pam"
21 "github.com/sirupsen/logrus"
24 type pamLoginController struct {
25 Cluster *arvados.Cluster
29 func (ctrl *pamLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
30 return logout(ctx, ctrl.Cluster, opts)
33 func (ctrl *pamLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
34 return arvados.LoginResponse{}, errors.New("interactive login is not available")
37 func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
40 tx, err := pam.StartFunc(ctrl.Cluster.Login.PAM.Service, opts.Username, func(style pam.Style, message string) (string, error) {
41 ctxlog.FromContext(ctx).Debugf("pam conversation: style=%v message=%q", style, message)
44 ctxlog.FromContext(ctx).WithField("Message", message).Info("pam.ErrorMsg")
45 errorMessage = message
48 ctxlog.FromContext(ctx).WithField("Message", message).Info("pam.TextInfo")
50 case pam.PromptEchoOn, pam.PromptEchoOff:
52 return opts.Password, nil
54 return "", fmt.Errorf("unrecognized message style %d", style)
58 return arvados.APIClientAuthorization{}, err
60 // Check that the given credentials are valid.
61 err = tx.Authenticate(pam.DisallowNullAuthtok)
63 err = fmt.Errorf("PAM: %s", err)
64 if errorMessage != "" {
65 // Perhaps the error message in the
66 // conversation is helpful.
67 err = fmt.Errorf("%s; %q", err, errorMessage)
70 err = fmt.Errorf("%s (with username %q and password)", err, opts.Username)
72 // This might hint that the username was
74 err = fmt.Errorf("%s (with username %q; password was never requested by PAM service)", err, opts.Username)
76 return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(err, http.StatusUnauthorized)
78 if errorMessage != "" {
79 return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(errors.New(errorMessage), http.StatusUnauthorized)
81 // Check that the account/user is permitted to access this host.
82 err = tx.AcctMgmt(pam.DisallowNullAuthtok)
84 err = fmt.Errorf("PAM: %s", err)
85 if errorMessage != "" {
86 err = fmt.Errorf("%s; %q", err, errorMessage)
88 return arvados.APIClientAuthorization{}, httpserver.ErrorWithStatus(err, http.StatusUnauthorized)
90 user, err := tx.GetItem(pam.User)
92 return arvados.APIClientAuthorization{}, err
95 if domain := ctrl.Cluster.Login.PAM.DefaultEmailDomain; domain != "" && !strings.Contains(email, "@") {
96 email = email + "@" + domain
98 ctxlog.FromContext(ctx).WithFields(logrus.Fields{
101 }).Debug("pam authentication succeeded")
102 return ctrl.Parent.CreateAPIClientAuthorization(ctx, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{