diff --git a/api/mfa/ceremony.go b/api/mfa/ceremony.go index 6035d2d98a78c..4cde8ccc69752 100644 --- a/api/mfa/ceremony.go +++ b/api/mfa/ceremony.go @@ -40,8 +40,7 @@ type Ceremony struct { // SSOMFACeremony is an SSO MFA ceremony. type SSOMFACeremony interface { GetClientCallbackURL() string - HandleRedirect(ctx context.Context, redirectURL string) error - GetCallbackMFAToken(ctx context.Context) (string, error) + Run(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) } // CreateAuthenticateChallengeFunc is a function that creates an authentication challenge. @@ -67,6 +66,8 @@ func (c *Ceremony) Run(ctx context.Context, req *proto.CreateAuthenticateChallen return nil, trace.BadParameter("mfa challenge scope must be specified") } + // If available, prepare an SSO MFA ceremony and set the client redirect URL in the challenge + // request to request an SSO challenge in addition to other challenges. if c.SSOMFACeremonyConstructor != nil { ssoMFACeremony, err := c.SSOMFACeremonyConstructor(ctx) if err != nil { diff --git a/lib/client/mfa.go b/lib/client/mfa.go index d8714a1f860ac..9797f2648b364 100644 --- a/lib/client/mfa.go +++ b/lib/client/mfa.go @@ -46,7 +46,7 @@ func (tc *TeleportClient) NewMFACeremony() *mfa.Ceremony { } context.AfterFunc(ctx, rd.Close) - return &sso.MFACeremony{Ceremony: sso.NewCLICeremony(rd, nil /*init*/)}, nil + return sso.NewCLIMFACeremony(rd), nil }, } } diff --git a/lib/client/mfa/cli.go b/lib/client/mfa/cli.go index 4be8d12297882..6c5d14e7c0cf7 100644 --- a/lib/client/mfa/cli.go +++ b/lib/client/mfa/cli.go @@ -336,21 +336,6 @@ func (w *webauthnPromptWithOTP) PromptPIN() (string, error) { } func (c *CLIPrompt) promptSSO(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { - if err := c.SSOMFACeremony.HandleRedirect(ctx, chal.SSOChallenge.RedirectUrl); err != nil { - return nil, trace.Wrap(err) - } - - mfaToken, err := c.SSOMFACeremony.GetCallbackMFAToken(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - - return &proto.MFAAuthenticateResponse{ - Response: &proto.MFAAuthenticateResponse_SSO{ - SSO: &proto.SSOResponse{ - RequestId: chal.SSOChallenge.RequestId, - Token: mfaToken, - }, - }, - }, nil + resp, err := c.SSOMFACeremony.Run(ctx, chal) + return resp, trace.Wrap(err) } diff --git a/lib/client/sso/ceremony.go b/lib/client/sso/ceremony.go index 985010eb9a561..84dae6c14d6a8 100644 --- a/lib/client/sso/ceremony.go +++ b/lib/client/sso/ceremony.go @@ -23,6 +23,7 @@ import ( "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/lib/auth/authclient" ) @@ -62,30 +63,55 @@ func NewCLICeremony(rd *Redirector, init CeremonyInit) *Ceremony { } } +// Ceremony is a customizable SSO MFA ceremony. type MFACeremony struct { - *Ceremony + clientCallbackURL string + HandleRedirect func(ctx context.Context, redirectURL string) error + GetCallbackMFAToken func(ctx context.Context) (string, error) } -// GetClientCallbackURL returns the SSO client callback URL. -func (c *MFACeremony) GetClientCallbackURL() string { - return c.clientCallbackURL +// GetClientCallbackURL returns the client callback URL. +func (m *MFACeremony) GetClientCallbackURL() string { + return m.clientCallbackURL } -// HandleRedirect handles redirect. -func (c *MFACeremony) HandleRedirect(ctx context.Context, redirectURL string) error { - return c.Ceremony.HandleRedirect(ctx, redirectURL) -} +// Run the SSO MFA ceremony. +func (m *MFACeremony) Run(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { + if err := m.HandleRedirect(ctx, chal.SSOChallenge.RedirectUrl); err != nil { + return nil, trace.Wrap(err) + } -// GetCallbackMFAResponse returns an SSO MFA auth response once the callback is complete. -func (c *MFACeremony) GetCallbackMFAToken(ctx context.Context) (string, error) { - loginResp, err := c.GetCallbackResponse(ctx) + mfaToken, err := m.GetCallbackMFAToken(ctx) if err != nil { - return "", trace.Wrap(err) + return nil, trace.Wrap(err) } - if loginResp.MFAToken == "" { - return "", trace.BadParameter("login response for SSO MFA flow missing MFA token") - } + return &proto.MFAAuthenticateResponse{ + Response: &proto.MFAAuthenticateResponse_SSO{ + SSO: &proto.SSOResponse{ + RequestId: chal.SSOChallenge.RequestId, + Token: mfaToken, + }, + }, + }, nil +} - return loginResp.MFAToken, nil +// NewCLIMFACeremony creates a new CLI SSO ceremony from the given redirector. +func NewCLIMFACeremony(rd *Redirector) *MFACeremony { + return &MFACeremony{ + clientCallbackURL: rd.ClientCallbackURL, + HandleRedirect: rd.OpenRedirect, + GetCallbackMFAToken: func(ctx context.Context) (string, error) { + loginResp, err := rd.WaitForResponse(ctx) + if err != nil { + return "", trace.Wrap(err) + } + + if loginResp.MFAToken == "" { + return "", trace.BadParameter("login response for SSO MFA flow missing MFA token") + } + + return loginResp.MFAToken, nil + }, + } }