diff --git a/examplebroker/broker.go b/examplebroker/broker.go index 4d8314d68a..7582a7016b 100644 --- a/examplebroker/broker.go +++ b/examplebroker/broker.go @@ -360,10 +360,10 @@ func (b *Broker) NewSession(ctx context.Context, username, lang, mode string) (s } // GetAuthenticationModes returns the list of supported authentication modes for the selected broker depending on session info. -func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, err error) { +func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, err error) { sessionInfo, err := b.sessionInfo(sessionID) if err != nil { - return nil, err + return nil, "", err } log.Debugf(ctx, "Supported UI layouts by %s, %#v", sessionID, supportedUILayouts) @@ -376,12 +376,12 @@ func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, s // If the user needs or can reset the password, we only show those authentication modes. if sessionInfo.currentAuthStep == sessionInfo.neededAuthSteps && sessionInfo.pwdChange != noReset { if sessionInfo.currentAuthStep < 2 { - return nil, errors.New("password reset is not allowed before authentication") + return nil, "", errors.New("password reset is not allowed before authentication") } allModes = getPasswdResetModes(sessionInfo, supportedUILayouts) if sessionInfo.pwdChange == mustReset && len(allModes) == 0 { - return nil, fmt.Errorf("user %q must reset password, but no mode was provided for it", sessionInfo.username) + return nil, "", fmt.Errorf("user %q must reset password, but no mode was provided for it", sessionInfo.username) } } @@ -426,10 +426,10 @@ func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, s sessionInfo.allModes = allModes if err := b.updateSession(sessionID, sessionInfo); err != nil { - return nil, err + return nil, "", err } - return authenticationModes, nil + return authenticationModes, "", nil } func getSupportedModes(sessionInfo sessionInfo, supportedUILayouts []map[string]string) map[string]authMode { diff --git a/examplebroker/dbus.go b/examplebroker/dbus.go index 76df614acc..2289848160 100644 --- a/examplebroker/dbus.go +++ b/examplebroker/dbus.go @@ -85,12 +85,12 @@ func (b *Bus) NewSession(username, lang, mode string) (sessionID, encryptionKey } // GetAuthenticationModes is the method through which the broker and the daemon will communicate once dbusInterface.GetAuthenticationModes is called. -func (b *Bus) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, dbusErr *dbus.Error) { - authenticationModes, err := b.broker.GetAuthenticationModes(context.Background(), sessionID, supportedUILayouts) +func (b *Bus) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, dbusErr *dbus.Error) { + authenticationModes, msg, err := b.broker.GetAuthenticationModes(context.Background(), sessionID, supportedUILayouts) if err != nil { - return nil, dbus.MakeFailedError(err) + return nil, "", dbus.MakeFailedError(err) } - return authenticationModes, nil + return authenticationModes, msg, nil } // SelectAuthenticationMode is the method through which the broker and the daemon will communicate once dbusInterface.SelectAuthenticationMode is called. diff --git a/internal/brokers/broker.go b/internal/brokers/broker.go index 5c162184ac..1fb7b3356b 100644 --- a/internal/brokers/broker.go +++ b/internal/brokers/broker.go @@ -24,7 +24,7 @@ const LocalBrokerName = "local" type brokerer interface { NewSession(ctx context.Context, username, lang, mode string) (sessionID, encryptionKey string, err error) - GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, err error) + GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, err error) SelectAuthenticationMode(ctx context.Context, sessionID, authenticationModeName string) (uiLayoutInfo map[string]string, err error) IsAuthenticated(ctx context.Context, sessionID, authenticationData string) (access, data string, err error) EndSession(ctx context.Context, sessionID string) (err error) @@ -105,27 +105,27 @@ func (b Broker) newSession(ctx context.Context, username, lang, mode string) (se } // GetAuthenticationModes calls the broker corresponding method, stripping broker ID prefix from sessionID. -func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, err error) { +func (b *Broker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, err error) { sessionID = b.parseSessionID(sessionID) b.layoutValidatorsMu.Lock() b.layoutValidators[sessionID] = generateValidators(ctx, sessionID, supportedUILayouts) b.layoutValidatorsMu.Unlock() - authenticationModes, err = b.brokerer.GetAuthenticationModes(ctx, sessionID, supportedUILayouts) + authenticationModes, msg, err = b.brokerer.GetAuthenticationModes(ctx, sessionID, supportedUILayouts) if err != nil { - return nil, err + return nil, "", err } for _, a := range authenticationModes { for _, key := range []string{layouts.ID, layouts.Label} { if _, exists := a[key]; !exists { - return nil, fmt.Errorf("invalid authentication mode, missing %q key: %v", key, a) + return nil, "", fmt.Errorf("invalid authentication mode, missing %q key: %v", key, a) } } } - return authenticationModes, nil + return authenticationModes, msg, nil } // SelectAuthenticationMode calls the broker corresponding method, stripping broker ID prefix from sessionID. diff --git a/internal/brokers/broker_test.go b/internal/brokers/broker_test.go index bc7eb4ba66..1bb81bedf1 100644 --- a/internal/brokers/broker_test.go +++ b/internal/brokers/broker_test.go @@ -126,7 +126,7 @@ func TestGetAuthenticationModes(t *testing.T) { supportedUILayouts = append(supportedUILayouts, supportedLayouts[layout]) } - gotModes, err := b.GetAuthenticationModes(context.Background(), prefixID(t, tc.sessionID), supportedUILayouts) + gotModes, _, err := b.GetAuthenticationModes(context.Background(), prefixID(t, tc.sessionID), supportedUILayouts) if tc.wantErr { require.Error(t, err, "GetAuthenticationModes should return an error, but did not") return diff --git a/internal/brokers/dbusbroker.go b/internal/brokers/dbusbroker.go index 906e083bce..7cfc3b6422 100644 --- a/internal/brokers/dbusbroker.go +++ b/internal/brokers/dbusbroker.go @@ -72,7 +72,26 @@ func (b dbusBroker) NewSession(ctx context.Context, username, lang, mode string) } // GetAuthenticationModes calls the corresponding method on the broker bus and returns the authentication modes supported by it. -func (b dbusBroker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, err error) { +func (b dbusBroker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, err error) { + call, err := b.call(ctx, "GetAuthenticationModesV2", sessionID, supportedUILayouts) + var dbusError dbus.Error + if errors.As(err, &dbusError) && dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" { + log.Debugf(ctx, "GetAuthenticationModesV2 not supported, falling back to GetAuthenticationModes") + authenticationModes, err = b.getAuthenticationModesV1(ctx, sessionID, supportedUILayouts) + return authenticationModes, "", err + } + if err != nil { + return nil, "", err + } + + if err = call.Store(&authenticationModes, &msg); err != nil { + return nil, "", err + } + + return authenticationModes, msg, nil +} + +func (b dbusBroker) getAuthenticationModesV1(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, err error) { call, err := b.call(ctx, "GetAuthenticationModes", sessionID, supportedUILayouts) if err != nil { return nil, err diff --git a/internal/brokers/localbroker.go b/internal/brokers/localbroker.go index c0730fced7..5ebc6fb048 100644 --- a/internal/brokers/localbroker.go +++ b/internal/brokers/localbroker.go @@ -15,8 +15,8 @@ func (b localBroker) NewSession(ctx context.Context, username, lang, mode string } //nolint:unused // We still need localBroker to implement the brokerer interface, even though this method should never be called on it. -func (b localBroker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, err error) { - return nil, errors.New("GetAuthenticationModes should never be called on local broker") +func (b localBroker) GetAuthenticationModes(ctx context.Context, sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, err error) { + return nil, "", errors.New("GetAuthenticationModes should never be called on local broker") } //nolint:unused // We still need localBroker to implement the brokerer interface, even though this method should never be called on it. diff --git a/internal/proto/authd/authd.pb.go b/internal/proto/authd/authd.pb.go index 93dca3c4a9..75c1cd13a2 100644 --- a/internal/proto/authd/authd.pb.go +++ b/internal/proto/authd/authd.pb.go @@ -560,6 +560,7 @@ func (x *UILayout) GetRendersQrcode() bool { type GAMResponse struct { state protoimpl.MessageState `protogen:"open.v1"` AuthenticationModes []*GAMResponse_AuthenticationMode `protobuf:"bytes,1,rep,name=authentication_modes,json=authenticationModes,proto3" json:"authentication_modes,omitempty"` + Msg *string `protobuf:"bytes,2,opt,name=msg,proto3,oneof" json:"msg,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -601,6 +602,13 @@ func (x *GAMResponse) GetAuthenticationModes() []*GAMResponse_AuthenticationMode return nil } +func (x *GAMResponse) GetMsg() string { + if x != nil && x.Msg != nil { + return *x.Msg + } + return "" +} + type SAMRequest struct { state protoimpl.MessageState `protogen:"open.v1"` SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` @@ -1691,12 +1699,14 @@ const file_authd_proto_rawDesc = "" + "\n" + "\b_contentB\a\n" + "\x05_codeB\x11\n" + - "\x0f_renders_qrcode\"\xa3\x01\n" + + "\x0f_renders_qrcode\"\xc2\x01\n" + "\vGAMResponse\x12X\n" + - "\x14authentication_modes\x18\x01 \x03(\v2%.authd.GAMResponse.AuthenticationModeR\x13authenticationModes\x1a:\n" + + "\x14authentication_modes\x18\x01 \x03(\v2%.authd.GAMResponse.AuthenticationModeR\x13authenticationModes\x12\x15\n" + + "\x03msg\x18\x02 \x01(\tH\x00R\x03msg\x88\x01\x01\x1a:\n" + "\x12AuthenticationMode\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + - "\x05label\x18\x02 \x01(\tR\x05label\"a\n" + + "\x05label\x18\x02 \x01(\tR\x05labelB\x06\n" + + "\x04_msg\"a\n" + "\n" + "SAMRequest\x12\x1d\n" + "\n" + @@ -1879,6 +1889,7 @@ func file_authd_proto_init() { return } file_authd_proto_msgTypes[8].OneofWrappers = []any{} + file_authd_proto_msgTypes[9].OneofWrappers = []any{} file_authd_proto_msgTypes[26].OneofWrappers = []any{} file_authd_proto_msgTypes[28].OneofWrappers = []any{ (*IARequest_AuthenticationData_Secret)(nil), diff --git a/internal/proto/authd/authd.proto b/internal/proto/authd/authd.proto index eb3ae7d63c..891632ad65 100644 --- a/internal/proto/authd/authd.proto +++ b/internal/proto/authd/authd.proto @@ -83,6 +83,7 @@ message UILayout { message GAMResponse { repeated AuthenticationMode authentication_modes = 1; + optional string msg = 2; message AuthenticationMode { string id = 1; diff --git a/internal/services/errmessages/error.go b/internal/services/errmessages/error.go index 509e86f43f..2b3d4a6187 100644 --- a/internal/services/errmessages/error.go +++ b/internal/services/errmessages/error.go @@ -9,3 +9,7 @@ type ToDisplayError struct { func NewToDisplayError(err error) error { return ToDisplayError{err} } + +func (e ToDisplayError) Unwrap() error { + return e.error +} diff --git a/internal/services/pam/pam.go b/internal/services/pam/pam.go index a6b2fea97a..33b7c9081b 100644 --- a/internal/services/pam/pam.go +++ b/internal/services/pam/pam.go @@ -195,7 +195,7 @@ func (s Service) GetAuthenticationModes(ctx context.Context, req *authd.GAMReque supportedLayouts = append(supportedLayouts, layout) } - authenticationModes, err := broker.GetAuthenticationModes(ctx, sessionID, supportedLayouts) + authenticationModes, msg, err := broker.GetAuthenticationModes(ctx, sessionID, supportedLayouts) if err != nil { log.Errorf(ctx, "GetAuthenticationModes: Could not get authentication modes for session %q: %v", sessionID, err) return nil, err @@ -209,8 +209,14 @@ func (s Service) GetAuthenticationModes(ctx context.Context, req *authd.GAMReque }) } + var msgPtr *string + if msg != "" { + msgPtr = &msg + } + return &authd.GAMResponse{ AuthenticationModes: authModes, + Msg: msgPtr, }, nil } diff --git a/internal/testutils/broker.go b/internal/testutils/broker.go index c744da7eb6..942d97acbf 100644 --- a/internal/testutils/broker.go +++ b/internal/testutils/broker.go @@ -138,26 +138,26 @@ func (b *BrokerBusMock) NewSession(username, lang, mode string) (sessionID, encr } // GetAuthenticationModes returns default values to be used in tests or an error if requested. -func (b *BrokerBusMock) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, dbusErr *dbus.Error) { +func (b *BrokerBusMock) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, msg string, dbusErr *dbus.Error) { sessionID = parseSessionID(sessionID) switch sessionID { case "gam_invalid": return []map[string]string{ {"invalid": "invalid"}, - }, nil + }, "", nil case "gam_empty": - return nil, nil + return nil, "", nil case "gam_error": - return nil, dbus.MakeFailedError(fmt.Errorf("broker %q: GetAuthenticationModes errored out", b.name)) + return nil, "", dbus.MakeFailedError(fmt.Errorf("broker %q: GetAuthenticationModes errored out", b.name)) case "gam_multiple_modes": return []map[string]string{ {layouts.ID: "mode1", layouts.Label: "Mode 1"}, {layouts.ID: "mode2", layouts.Label: "Mode 2"}, - }, nil + }, "", nil default: return []map[string]string{ {layouts.ID: "mode1", layouts.Label: "Mode 1"}, - }, nil + }, "", nil } } diff --git a/pam/internal/adapter/authmodeselection.go b/pam/internal/adapter/authmodeselection.go index 1dfffda560..2860efc925 100644 --- a/pam/internal/adapter/authmodeselection.go +++ b/pam/internal/adapter/authmodeselection.go @@ -34,6 +34,7 @@ type supportedUILayoutsSet struct{} // authModesReceived is the internal event signalling that the supported authentication modes have been received. type authModesReceived struct { authModes []*authd.GAMResponse_AuthenticationMode + msg string } // authModeSelected is the internal event signalling that the an authentication mode has been selected. @@ -244,8 +245,15 @@ func getAuthenticationModes(client authd.PAMClient, sessionID string, uiLayouts } log.Debug(context.TODO(), "authModes", authModes) + var msg string + if gamResp.Msg != nil { + msg = *gamResp.Msg + log.Debugf(context.TODO(), "GetAuthenticationModes message: %q", msg) + } + return authModesReceived{ authModes: authModes, + msg: msg, } } } diff --git a/pam/internal/adapter/model.go b/pam/internal/adapter/model.go index 7a41a582c4..6ff9b791d1 100644 --- a/pam/internal/adapter/model.go +++ b/pam/internal/adapter/model.go @@ -42,13 +42,20 @@ var ( Light: "#909090", Dark: "#7f7f7f", }).Padding(1, 0, 0, 2) + + warningMsgStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + // Use a light yellow/orange color for warnings. + Light: "#f0c674", + Dark: "#e6bf69", + }).Padding(1, 0, 0, 2) ) // sessionInfo contains the global broker session information. type sessionInfo struct { - brokerID string - sessionID string - encryptionKey *rsa.PublicKey + brokerID string + sessionID string + encryptionKey *rsa.PublicKey + getAuthenticationModesMessage string } // uiModel is the global models orchestrator. @@ -281,6 +288,12 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, m.userSelectionModel.SelectUser() + case authModesReceived: + if msg.msg != "" { + safeMessageDebug(msg, "GetAuthenticationModes message: %q", msg.msg) + m.currentSession.getAuthenticationModesMessage = msg.msg + } + case UsernameSelected: safeMessageDebug(msg, "user: %q", m.username()) if m.username() == "" { @@ -447,6 +460,11 @@ func (m uiModel) View() string { view := viewBuilder.String() + if m.currentSession != nil && m.currentSession.getAuthenticationModesMessage != "" { + warningMessage := warningMsgStyle.Render(m.currentSession.getAuthenticationModesMessage) + view = lipgloss.JoinVertical(lipgloss.Left, view, warningMessage) + } + if len(view) > 0 && m.canGoBack() { infoMessage := infoMsgStyle.Render(fmt.Sprintf("Press escape key to %s", goBackLabel(m.previousStage())))