diff --git a/api/pkg/responses/system.go b/api/pkg/responses/system.go new file mode 100644 index 00000000000..6cc01013a1b --- /dev/null +++ b/api/pkg/responses/system.go @@ -0,0 +1,17 @@ +package responses + +type SystemInfo struct { + Version string `json:"version"` + Endpoints *SystemEndpointsInfo `json:"endpoints"` + Setup bool `json:"setup"` + Authentication *SystemAuthenticationInfo `json:"authentication"` +} + +type SystemAuthenticationInfo struct { + Local bool `json:"local"` +} + +type SystemEndpointsInfo struct { + API string `json:"api"` + SSH string `json:"ssh"` +} diff --git a/api/routes/stats.go b/api/routes/stats.go index 115179a0a12..cc9943e57ed 100644 --- a/api/routes/stats.go +++ b/api/routes/stats.go @@ -23,9 +23,8 @@ func (h *Handler) GetStats(c gateway.Context) error { } func (h *Handler) GetSystemInfo(c gateway.Context) error { - var req requests.SystemGetInfo - - if err := c.Bind(&req); err != nil { + req := new(requests.GetSystemInfo) + if err := c.Bind(req); err != nil { return err } @@ -33,7 +32,7 @@ func (h *Handler) GetSystemInfo(c gateway.Context) error { req.Host = c.Request().Host } - info, err := h.service.SystemGetInfo(c.Ctx(), req) + info, err := h.service.GetSystemInfo(c.Ctx(), req) if err != nil { return err } diff --git a/api/routes/stats_test.go b/api/routes/stats_test.go index 541debbda0c..a77e2b25e47 100644 --- a/api/routes/stats_test.go +++ b/api/routes/stats_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/shellhub-io/shellhub/api/pkg/responses" "github.com/shellhub-io/shellhub/api/services/mocks" "github.com/shellhub-io/shellhub/pkg/api/authorizer" "github.com/shellhub-io/shellhub/pkg/api/requests" @@ -20,22 +21,25 @@ func TestGetSystemInfo(t *testing.T) { cases := []struct { title string - request requests.SystemGetInfo - requiredMocks func(updatePayloadMock requests.SystemGetInfo) + request requests.GetSystemInfo + requiredMocks func(updatePayloadMock requests.GetSystemInfo) expectedStatus int }{ { title: "success when try to get infos of a existing system", - request: requests.SystemGetInfo{ + request: requests.GetSystemInfo{ Host: "example.com", Port: 0, }, - requiredMocks: func(_ requests.SystemGetInfo) { - mock.On("SystemGetInfo", gomock.Anything, requests.SystemGetInfo{ - Host: "example.com", - Port: 0, - }, - ).Return(&models.SystemInfo{}, nil) + requiredMocks: func(_ requests.GetSystemInfo) { + mock. + On( + "GetSystemInfo", + gomock.Anything, + &requests.GetSystemInfo{Host: "example.com", Port: 0}, + ). + Return(&responses.SystemInfo{}, nil). + Once() }, expectedStatus: http.StatusOK, }, diff --git a/api/services/auth.go b/api/services/auth.go index 2a37aeb4631..459c432429e 100644 --- a/api/services/auth.go +++ b/api/services/auth.go @@ -11,6 +11,7 @@ import ( "encoding/hex" "encoding/pem" "net" + "slices" "strings" "time" @@ -171,6 +172,10 @@ func (s *service) AuthDevice(ctx context.Context, req requests.DeviceAuth, remot } func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser, sourceIP string) (*models.UserAuthResponse, int64, string, error) { + if s, err := s.store.SystemGet(ctx); err != nil || !s.Authentication.Local.Enabled { + return nil, 0, "", NewErrAuthMethodNotAllowed() + } + var err error var user *models.User @@ -184,7 +189,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser return nil, 0, "", NewErrAuthUnathorized(nil) } - if user.Origin != models.UserOriginLocal { + if !slices.Contains(user.Preferences.AuthMethods, models.UserAuthMethodLocal) { return nil, 0, "", NewErrAuthUnathorized(nil) } @@ -290,6 +295,7 @@ func (s *service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser res := &models.UserAuthResponse{ ID: user.ID, Origin: user.Origin.String(), + AuthMethods: user.Preferences.AuthMethods, User: user.Username, Name: user.Name, Email: user.Email, @@ -373,6 +379,7 @@ func (s *service) CreateUserToken(ctx context.Context, req *requests.CreateUserT return &models.UserAuthResponse{ ID: user.ID, Origin: user.Origin.String(), + AuthMethods: user.Preferences.AuthMethods, User: user.Username, Name: user.Name, Email: user.Email, diff --git a/api/services/auth_test.go b/api/services/auth_test.go index ea5936b31ea..20d800e68be 100644 --- a/api/services/auth_test.go +++ b/api/services/auth_test.go @@ -135,6 +135,55 @@ func TestService_AuthLocalUser(t *testing.T) { requiredMocks func() expected Expected }{ + { + description: "fails when could not retrieve the system", + sourceIP: "127.0.0.1", + req: &requests.AuthLocalUser{ + Identifier: "john_doe", + Password: "secret", + }, + requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return(nil, store.ErrNoDocuments). + Once() + }, + expected: Expected{ + res: nil, + lockout: 0, + mfaToken: "", + err: NewErrAuthMethodNotAllowed(), + }, + }, + { + description: "fails when local authentication is not allowed", + sourceIP: "127.0.0.1", + req: &requests.AuthLocalUser{ + Identifier: "john_doe", + Password: "secret", + }, + requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: false, + }, + }, + }, + nil, + ). + Once() + }, + expected: Expected{ + res: nil, + lockout: 0, + mfaToken: "", + err: NewErrAuthMethodNotAllowed(), + }, + }, { description: "fails when username is not found", sourceIP: "127.0.0.1", @@ -143,6 +192,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() mock. On("UserGetByUsername", ctx, "john_doe"). Return(nil, store.ErrNoDocuments). @@ -163,6 +225,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() mock. On("UserGetByEmail", ctx, "john.doe@test.com"). Return(nil, store.ErrNoDocuments). @@ -176,16 +251,29 @@ func TestService_AuthLocalUser(t *testing.T) { }, }, { - description: "fails when user's origin isn't 'local'", + description: "fails when user does not have local as authentication method", sourceIP: "127.0.0.1", req: &requests.AuthLocalUser{ Identifier: "john_doe", Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOrigin("other"), + Origin: models.UserOriginLocal, Status: models.UserStatusNotConfirmed, LastLogin: now, MFA: models.UserMFA{ @@ -200,6 +288,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{}, }, } @@ -220,6 +309,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -237,6 +339,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -257,6 +360,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() mock. On("UserGetByEmail", ctx, "john.doe@test.com"). Return( @@ -277,6 +393,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, }, nil, @@ -298,6 +415,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "wrong_password", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -315,6 +445,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -342,6 +473,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "wrong_password", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -359,6 +503,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -394,6 +539,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -411,6 +569,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -455,6 +614,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -472,6 +644,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -515,19 +688,25 @@ func TestService_AuthLocalUser(t *testing.T) { res: nil, lockout: 0, mfaToken: "", - err: NewErrUserUpdate(&models.User{ - ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOriginLocal, - Status: models.UserStatusConfirmed, - LastLogin: now, - UserData: models.UserData{ - Username: "john_doe", - Email: "john.doe@test.com", - }, - Password: models.UserPassword{ - Hash: "$2a$10$V/6N1wsjheBVvWosPfv02uf4WAOb9lmp8YWQCIa2UYuFV4OJby7Yi", + err: NewErrUserUpdate( + &models.User{ + ID: "65fdd16b5f62f93184ec8a39", + Origin: models.UserOriginLocal, + Status: models.UserStatusConfirmed, + LastLogin: now, + UserData: models.UserData{ + Username: "john_doe", + Email: "john.doe@test.com", + }, + Password: models.UserPassword{ + Hash: "$2a$10$V/6N1wsjheBVvWosPfv02uf4WAOb9lmp8YWQCIa2UYuFV4OJby7Yi", + }, + Preferences: models.UserPreferences{ + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + }, }, - }, errors.New("error", "", 0)), + errors.New("error", "", 0), + ), }, }, { @@ -538,6 +717,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -556,6 +748,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -597,13 +790,14 @@ func TestService_AuthLocalUser(t *testing.T) { }, expected: Expected{ res: &models.UserAuthResponse{ - ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOriginLocal.String(), - Name: "john doe", - User: "john_doe", - Email: "john.doe@test.com", - Tenant: "", - Token: "must ignore", + ID: "65fdd16b5f62f93184ec8a39", + Origin: models.UserOriginLocal.String(), + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + Name: "john doe", + User: "john_doe", + Email: "john.doe@test.com", + Tenant: "", + Token: "must ignore", }, lockout: 0, mfaToken: "", @@ -618,6 +812,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -636,6 +843,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "00000000-0000-4000-0000-000000000000", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -688,14 +896,15 @@ func TestService_AuthLocalUser(t *testing.T) { }, expected: Expected{ res: &models.UserAuthResponse{ - ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOriginLocal.String(), - Name: "john doe", - User: "john_doe", - Email: "john.doe@test.com", - Tenant: "00000000-0000-4000-0000-000000000000", - Role: "owner", - Token: "must ignore", + ID: "65fdd16b5f62f93184ec8a39", + Origin: models.UserOriginLocal.String(), + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + Name: "john doe", + User: "john_doe", + Email: "john.doe@test.com", + Tenant: "00000000-0000-4000-0000-000000000000", + Role: "owner", + Token: "must ignore", }, lockout: 0, mfaToken: "", @@ -710,6 +919,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -728,6 +950,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "00000000-0000-4000-0000-000000000000", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -781,14 +1004,15 @@ func TestService_AuthLocalUser(t *testing.T) { }, expected: Expected{ res: &models.UserAuthResponse{ - ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOriginLocal.String(), - Name: "john doe", - User: "john_doe", - Email: "john.doe@test.com", - Tenant: "", - Role: "", - Token: "must ignore", + ID: "65fdd16b5f62f93184ec8a39", + Origin: models.UserOriginLocal.String(), + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + Name: "john doe", + User: "john_doe", + Email: "john.doe@test.com", + Tenant: "", + Role: "", + Token: "must ignore", }, lockout: 0, mfaToken: "", @@ -803,6 +1027,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -821,6 +1058,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -873,14 +1111,15 @@ func TestService_AuthLocalUser(t *testing.T) { }, expected: Expected{ res: &models.UserAuthResponse{ - ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOriginLocal.String(), - Name: "john doe", - User: "john_doe", - Email: "john.doe@test.com", - Tenant: "00000000-0000-4000-0000-000000000000", - Role: "owner", - Token: "must ignore", + ID: "65fdd16b5f62f93184ec8a39", + Origin: models.UserOriginLocal.String(), + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + Name: "john doe", + User: "john_doe", + Email: "john.doe@test.com", + Tenant: "00000000-0000-4000-0000-000000000000", + Role: "owner", + Token: "must ignore", }, lockout: 0, mfaToken: "", @@ -895,6 +1134,19 @@ func TestService_AuthLocalUser(t *testing.T) { Password: "secret", }, requiredMocks: func() { + mock. + On("SystemGet", ctx). + Return( + &models.System{ + Authentication: &models.SystemAuthentication{ + Local: &models.SystemAuthenticationLocal{ + Enabled: true, + }, + }, + }, + nil, + ). + Once() user := &models.User{ ID: "65fdd16b5f62f93184ec8a39", Origin: models.UserOriginLocal, @@ -913,6 +1165,7 @@ func TestService_AuthLocalUser(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, } @@ -963,13 +1216,14 @@ func TestService_AuthLocalUser(t *testing.T) { }, expected: Expected{ res: &models.UserAuthResponse{ - ID: "65fdd16b5f62f93184ec8a39", - Origin: models.UserOriginLocal.String(), - Name: "john doe", - User: "john_doe", - Email: "john.doe@test.com", - Tenant: "", - Token: "must ignore", + ID: "65fdd16b5f62f93184ec8a39", + Origin: models.UserOriginLocal.String(), + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + Name: "john doe", + User: "john_doe", + Email: "john.doe@test.com", + Tenant: "", + Token: "must ignore", }, lockout: 0, mfaToken: "", diff --git a/api/services/errors.go b/api/services/errors.go index 6e75ade39a8..6236fcee825 100644 --- a/api/services/errors.go +++ b/api/services/errors.go @@ -133,6 +133,7 @@ var ( ErrRoleInvalid = errors.New("role is invalid", ErrLayer, ErrCodeForbidden) ErrUserDelete = errors.New("user couldn't be deleted", ErrLayer, ErrCodeInvalid) ErrSetupForbidden = errors.New("setup isn't allowed anymore", ErrLayer, ErrCodeForbidden) + ErrAuthMethodNotAllowed = errors.New("auth method not allowed", ErrLayer, ErrCodeForbidden) ) func NewErrRoleInvalid() error { @@ -144,6 +145,10 @@ func NewErrNoContentChange(err error, next error) error { return errors.Wrap(err, next) } +func NewErrAuthMethodNotAllowed() error { + return ErrAuthMethodNotAllowed +} + // NewErrNotFound returns an error with the ErrDataNotFound and wrap an error. func NewErrNotFound(err error, id string, next error) error { return errors.Wrap(errors.WithData(err, ErrDataNotFound{ID: id}), next) diff --git a/api/services/member_test.go b/api/services/member_test.go index 366470cd3fc..84da8e8e41b 100644 --- a/api/services/member_test.go +++ b/api/services/member_test.go @@ -1753,6 +1753,7 @@ func TestService_LeaveNamespace(t *testing.T) { &models.User{ ID: "000000000000000000000000", Status: models.UserStatusConfirmed, + Origin: models.UserOriginLocal, LastLogin: now, MFA: models.UserMFA{ Enabled: false, @@ -1767,6 +1768,7 @@ func TestService_LeaveNamespace(t *testing.T) { }, Preferences: models.UserPreferences{ PreferredNamespace: "", + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, }, }, 0, @@ -1787,13 +1789,15 @@ func TestService_LeaveNamespace(t *testing.T) { }, expected: Expected{ res: &models.UserAuthResponse{ - ID: "000000000000000000000000", - Name: "john doe", - User: "john_doe", - Email: "john.doe@test.com", - Tenant: "", - Role: "", - Token: "must ignore", + ID: "000000000000000000000000", + Origin: models.UserOriginLocal.String(), + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + Name: "john doe", + User: "john_doe", + Email: "john.doe@test.com", + Tenant: "", + Role: "", + Token: "must ignore", }, err: nil, }, diff --git a/api/services/mocks/services.go b/api/services/mocks/services.go index 98a9ec997ce..f4f8151c140 100644 --- a/api/services/mocks/services.go +++ b/api/services/mocks/services.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. +// Code generated by mockery v2.50.0. DO NOT EDIT. package mocks @@ -10,6 +10,8 @@ import ( models "github.com/shellhub-io/shellhub/pkg/models" + pkgresponses "github.com/shellhub-io/shellhub/api/pkg/responses" + query "github.com/shellhub-io/shellhub/pkg/api/query" requests "github.com/shellhub-io/shellhub/pkg/api/requests" @@ -28,6 +30,10 @@ type Service struct { func (_m *Service) AddNamespaceMember(ctx context.Context, req *requests.NamespaceAddMember) (*models.Namespace, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for AddNamespaceMember") + } + var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceAddMember) (*models.Namespace, error)); ok { @@ -54,6 +60,10 @@ func (_m *Service) AddNamespaceMember(ctx context.Context, req *requests.Namespa func (_m *Service) AddPublicKeyTag(ctx context.Context, tenant string, fingerprint string, tag string) error { ret := _m.Called(ctx, tenant, fingerprint, tag) + if len(ret) == 0 { + panic("no return value specified for AddPublicKeyTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, fingerprint, tag) @@ -68,6 +78,10 @@ func (_m *Service) AddPublicKeyTag(ctx context.Context, tenant string, fingerpri func (_m *Service) AuthAPIKey(ctx context.Context, key string) (*models.APIKey, error) { ret := _m.Called(ctx, key) + if len(ret) == 0 { + panic("no return value specified for AuthAPIKey") + } + var r0 *models.APIKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.APIKey, error)); ok { @@ -94,6 +108,10 @@ func (_m *Service) AuthAPIKey(ctx context.Context, key string) (*models.APIKey, func (_m *Service) AuthCacheToken(ctx context.Context, tenant string, id string, token string) error { ret := _m.Called(ctx, tenant, id, token) + if len(ret) == 0 { + panic("no return value specified for AuthCacheToken") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, id, token) @@ -108,6 +126,10 @@ func (_m *Service) AuthCacheToken(ctx context.Context, tenant string, id string, func (_m *Service) AuthDevice(ctx context.Context, req requests.DeviceAuth, remoteAddr string) (*models.DeviceAuthResponse, error) { ret := _m.Called(ctx, req, remoteAddr) + if len(ret) == 0 { + panic("no return value specified for AuthDevice") + } + var r0 *models.DeviceAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.DeviceAuth, string) (*models.DeviceAuthResponse, error)); ok { @@ -134,6 +156,10 @@ func (_m *Service) AuthDevice(ctx context.Context, req requests.DeviceAuth, remo func (_m *Service) AuthIsCacheToken(ctx context.Context, tenant string, id string) (bool, error) { ret := _m.Called(ctx, tenant, id) + if len(ret) == 0 { + panic("no return value specified for AuthIsCacheToken") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { @@ -158,6 +184,10 @@ func (_m *Service) AuthIsCacheToken(ctx context.Context, tenant string, id strin func (_m *Service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUser, sourceIP string) (*models.UserAuthResponse, int64, string, error) { ret := _m.Called(ctx, req, sourceIP) + if len(ret) == 0 { + panic("no return value specified for AuthLocalUser") + } + var r0 *models.UserAuthResponse var r1 int64 var r2 string @@ -198,6 +228,10 @@ func (_m *Service) AuthLocalUser(ctx context.Context, req *requests.AuthLocalUse func (_m *Service) AuthPublicKey(ctx context.Context, req requests.PublicKeyAuth) (*models.PublicKeyAuthResponse, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for AuthPublicKey") + } + var r0 *models.PublicKeyAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.PublicKeyAuth) (*models.PublicKeyAuthResponse, error)); ok { @@ -224,6 +258,10 @@ func (_m *Service) AuthPublicKey(ctx context.Context, req requests.PublicKeyAuth func (_m *Service) AuthUncacheToken(ctx context.Context, tenant string, id string) error { ret := _m.Called(ctx, tenant, id) + if len(ret) == 0 { + panic("no return value specified for AuthUncacheToken") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenant, id) @@ -238,6 +276,10 @@ func (_m *Service) AuthUncacheToken(ctx context.Context, tenant string, id strin func (_m *Service) BillingEvaluate(_a0 internalclient.Client, _a1 string) (bool, error) { ret := _m.Called(_a0, _a1) + if len(ret) == 0 { + panic("no return value specified for BillingEvaluate") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(internalclient.Client, string) (bool, error)); ok { @@ -262,6 +304,10 @@ func (_m *Service) BillingEvaluate(_a0 internalclient.Client, _a1 string) (bool, func (_m *Service) BillingReport(_a0 internalclient.Client, _a1 string, _a2 string) error { ret := _m.Called(_a0, _a1, _a2) + if len(ret) == 0 { + panic("no return value specified for BillingReport") + } + var r0 error if rf, ok := ret.Get(0).(func(internalclient.Client, string, string) error); ok { r0 = rf(_a0, _a1, _a2) @@ -276,6 +322,10 @@ func (_m *Service) BillingReport(_a0 internalclient.Client, _a1 string, _a2 stri func (_m *Service) CreateAPIKey(ctx context.Context, req *requests.CreateAPIKey) (*responses.CreateAPIKey, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for CreateAPIKey") + } + var r0 *responses.CreateAPIKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.CreateAPIKey) (*responses.CreateAPIKey, error)); ok { @@ -302,6 +352,10 @@ func (_m *Service) CreateAPIKey(ctx context.Context, req *requests.CreateAPIKey) func (_m *Service) CreateDeviceTag(ctx context.Context, uid models.UID, tag string) error { ret := _m.Called(ctx, uid, tag) + if len(ret) == 0 { + panic("no return value specified for CreateDeviceTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string) error); ok { r0 = rf(ctx, uid, tag) @@ -316,6 +370,10 @@ func (_m *Service) CreateDeviceTag(ctx context.Context, uid models.UID, tag stri func (_m *Service) CreateNamespace(ctx context.Context, namespace *requests.NamespaceCreate) (*models.Namespace, error) { ret := _m.Called(ctx, namespace) + if len(ret) == 0 { + panic("no return value specified for CreateNamespace") + } + var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceCreate) (*models.Namespace, error)); ok { @@ -342,6 +400,10 @@ func (_m *Service) CreateNamespace(ctx context.Context, namespace *requests.Name func (_m *Service) CreatePrivateKey(ctx context.Context) (*models.PrivateKey, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for CreatePrivateKey") + } + var r0 *models.PrivateKey var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*models.PrivateKey, error)); ok { @@ -368,6 +430,10 @@ func (_m *Service) CreatePrivateKey(ctx context.Context) (*models.PrivateKey, er func (_m *Service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCreate, tenant string) (*responses.PublicKeyCreate, error) { ret := _m.Called(ctx, req, tenant) + if len(ret) == 0 { + panic("no return value specified for CreatePublicKey") + } + var r0 *responses.PublicKeyCreate var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.PublicKeyCreate, string) (*responses.PublicKeyCreate, error)); ok { @@ -394,6 +460,10 @@ func (_m *Service) CreatePublicKey(ctx context.Context, req requests.PublicKeyCr func (_m *Service) CreateSession(ctx context.Context, session requests.SessionCreate) (*models.Session, error) { ret := _m.Called(ctx, session) + if len(ret) == 0 { + panic("no return value specified for CreateSession") + } + var r0 *models.Session var r1 error if rf, ok := ret.Get(0).(func(context.Context, requests.SessionCreate) (*models.Session, error)); ok { @@ -420,6 +490,10 @@ func (_m *Service) CreateSession(ctx context.Context, session requests.SessionCr func (_m *Service) CreateUserToken(ctx context.Context, req *requests.CreateUserToken) (*models.UserAuthResponse, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for CreateUserToken") + } + var r0 *models.UserAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.CreateUserToken) (*models.UserAuthResponse, error)); ok { @@ -446,6 +520,10 @@ func (_m *Service) CreateUserToken(ctx context.Context, req *requests.CreateUser func (_m *Service) DeactivateSession(ctx context.Context, uid models.UID) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for DeactivateSession") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) error); ok { r0 = rf(ctx, uid) @@ -460,6 +538,10 @@ func (_m *Service) DeactivateSession(ctx context.Context, uid models.UID) error func (_m *Service) DeleteAPIKey(ctx context.Context, req *requests.DeleteAPIKey) error { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for DeleteAPIKey") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *requests.DeleteAPIKey) error); ok { r0 = rf(ctx, req) @@ -474,6 +556,10 @@ func (_m *Service) DeleteAPIKey(ctx context.Context, req *requests.DeleteAPIKey) func (_m *Service) DeleteDevice(ctx context.Context, uid models.UID, tenant string) error { ret := _m.Called(ctx, uid, tenant) + if len(ret) == 0 { + panic("no return value specified for DeleteDevice") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string) error); ok { r0 = rf(ctx, uid, tenant) @@ -488,6 +574,10 @@ func (_m *Service) DeleteDevice(ctx context.Context, uid models.UID, tenant stri func (_m *Service) DeleteNamespace(ctx context.Context, tenantID string) error { ret := _m.Called(ctx, tenantID) + if len(ret) == 0 { + panic("no return value specified for DeleteNamespace") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, tenantID) @@ -502,6 +592,10 @@ func (_m *Service) DeleteNamespace(ctx context.Context, tenantID string) error { func (_m *Service) DeletePublicKey(ctx context.Context, fingerprint string, tenant string) error { ret := _m.Called(ctx, fingerprint, tenant) + if len(ret) == 0 { + panic("no return value specified for DeletePublicKey") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, fingerprint, tenant) @@ -516,6 +610,10 @@ func (_m *Service) DeletePublicKey(ctx context.Context, fingerprint string, tena func (_m *Service) DeleteTag(ctx context.Context, tenant string, tag string) error { ret := _m.Called(ctx, tenant, tag) + if len(ret) == 0 { + panic("no return value specified for DeleteTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenant, tag) @@ -530,6 +628,10 @@ func (_m *Service) DeleteTag(ctx context.Context, tenant string, tag string) err func (_m *Service) EditNamespace(ctx context.Context, req *requests.NamespaceEdit) (*models.Namespace, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for EditNamespace") + } + var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceEdit) (*models.Namespace, error)); ok { @@ -556,6 +658,10 @@ func (_m *Service) EditNamespace(ctx context.Context, req *requests.NamespaceEdi func (_m *Service) EditSessionRecordStatus(ctx context.Context, sessionRecord bool, tenantID string) error { ret := _m.Called(ctx, sessionRecord, tenantID) + if len(ret) == 0 { + panic("no return value specified for EditSessionRecordStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, bool, string) error); ok { r0 = rf(ctx, sessionRecord, tenantID) @@ -570,6 +676,10 @@ func (_m *Service) EditSessionRecordStatus(ctx context.Context, sessionRecord bo func (_m *Service) EvaluateKeyFilter(ctx context.Context, key *models.PublicKey, dev models.Device) (bool, error) { ret := _m.Called(ctx, key, dev) + if len(ret) == 0 { + panic("no return value specified for EvaluateKeyFilter") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.PublicKey, models.Device) (bool, error)); ok { @@ -594,6 +704,10 @@ func (_m *Service) EvaluateKeyFilter(ctx context.Context, key *models.PublicKey, func (_m *Service) EvaluateKeyUsername(ctx context.Context, key *models.PublicKey, username string) (bool, error) { ret := _m.Called(ctx, key, username) + if len(ret) == 0 { + panic("no return value specified for EvaluateKeyUsername") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, *models.PublicKey, string) (bool, error)); ok { @@ -618,6 +732,10 @@ func (_m *Service) EvaluateKeyUsername(ctx context.Context, key *models.PublicKe func (_m *Service) EventSession(ctx context.Context, uid models.UID, event *models.SessionEvent) error { ret := _m.Called(ctx, uid, event) + if len(ret) == 0 { + panic("no return value specified for EventSession") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, *models.SessionEvent) error); ok { r0 = rf(ctx, uid, event) @@ -632,6 +750,10 @@ func (_m *Service) EventSession(ctx context.Context, uid models.UID, event *mode func (_m *Service) GetDevice(ctx context.Context, uid models.UID) (*models.Device, error) { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for GetDevice") + } + var r0 *models.Device var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) (*models.Device, error)); ok { @@ -658,6 +780,10 @@ func (_m *Service) GetDevice(ctx context.Context, uid models.UID) (*models.Devic func (_m *Service) GetDeviceByPublicURLAddress(ctx context.Context, address string) (*models.Device, error) { ret := _m.Called(ctx, address) + if len(ret) == 0 { + panic("no return value specified for GetDeviceByPublicURLAddress") + } + var r0 *models.Device var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Device, error)); ok { @@ -684,6 +810,10 @@ func (_m *Service) GetDeviceByPublicURLAddress(ctx context.Context, address stri func (_m *Service) GetNamespace(ctx context.Context, tenantID string) (*models.Namespace, error) { ret := _m.Called(ctx, tenantID) + if len(ret) == 0 { + panic("no return value specified for GetNamespace") + } + var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Namespace, error)); ok { @@ -710,6 +840,10 @@ func (_m *Service) GetNamespace(ctx context.Context, tenantID string) (*models.N func (_m *Service) GetPublicKey(ctx context.Context, fingerprint string, tenant string) (*models.PublicKey, error) { ret := _m.Called(ctx, fingerprint, tenant) + if len(ret) == 0 { + panic("no return value specified for GetPublicKey") + } + var r0 *models.PublicKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.PublicKey, error)); ok { @@ -736,6 +870,10 @@ func (_m *Service) GetPublicKey(ctx context.Context, fingerprint string, tenant func (_m *Service) GetSession(ctx context.Context, uid models.UID) (*models.Session, error) { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for GetSession") + } + var r0 *models.Session var r1 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) (*models.Session, error)); ok { @@ -762,6 +900,10 @@ func (_m *Service) GetSession(ctx context.Context, uid models.UID) (*models.Sess func (_m *Service) GetSessionRecord(ctx context.Context, tenantID string) (bool, error) { ret := _m.Called(ctx, tenantID) + if len(ret) == 0 { + panic("no return value specified for GetSessionRecord") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { @@ -786,6 +928,10 @@ func (_m *Service) GetSessionRecord(ctx context.Context, tenantID string) (bool, func (_m *Service) GetStats(ctx context.Context) (*models.Stats, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetStats") + } + var r0 *models.Stats var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*models.Stats, error)); ok { @@ -808,10 +954,44 @@ func (_m *Service) GetStats(ctx context.Context) (*models.Stats, error) { return r0, r1 } +// GetSystemInfo provides a mock function with given fields: ctx, req +func (_m *Service) GetSystemInfo(ctx context.Context, req *requests.GetSystemInfo) (*pkgresponses.SystemInfo, error) { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for GetSystemInfo") + } + + var r0 *pkgresponses.SystemInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *requests.GetSystemInfo) (*pkgresponses.SystemInfo, error)); ok { + return rf(ctx, req) + } + if rf, ok := ret.Get(0).(func(context.Context, *requests.GetSystemInfo) *pkgresponses.SystemInfo); ok { + r0 = rf(ctx, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*pkgresponses.SystemInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *requests.GetSystemInfo) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetTags provides a mock function with given fields: ctx, tenant func (_m *Service) GetTags(ctx context.Context, tenant string) ([]string, int, error) { ret := _m.Called(ctx, tenant) + if len(ret) == 0 { + panic("no return value specified for GetTags") + } + var r0 []string var r1 int var r2 error @@ -845,6 +1025,10 @@ func (_m *Service) GetTags(ctx context.Context, tenant string) ([]string, int, e func (_m *Service) GetUserRole(ctx context.Context, tenantID string, userID string) (string, error) { ret := _m.Called(ctx, tenantID, userID) + if len(ret) == 0 { + panic("no return value specified for GetUserRole") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok { @@ -869,6 +1053,10 @@ func (_m *Service) GetUserRole(ctx context.Context, tenantID string, userID stri func (_m *Service) KeepAliveSession(ctx context.Context, uid models.UID) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for KeepAliveSession") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) error); ok { r0 = rf(ctx, uid) @@ -883,6 +1071,10 @@ func (_m *Service) KeepAliveSession(ctx context.Context, uid models.UID) error { func (_m *Service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamespace) (*models.UserAuthResponse, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for LeaveNamespace") + } + var r0 *models.UserAuthResponse var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.LeaveNamespace) (*models.UserAuthResponse, error)); ok { @@ -909,6 +1101,10 @@ func (_m *Service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamesp func (_m *Service) ListAPIKeys(ctx context.Context, req *requests.ListAPIKey) ([]models.APIKey, int, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for ListAPIKeys") + } + var r0 []models.APIKey var r1 int var r2 error @@ -942,6 +1138,10 @@ func (_m *Service) ListAPIKeys(ctx context.Context, req *requests.ListAPIKey) ([ func (_m *Service) ListDevices(ctx context.Context, req *requests.DeviceList) ([]models.Device, int, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for ListDevices") + } + var r0 []models.Device var r1 int var r2 error @@ -975,6 +1175,10 @@ func (_m *Service) ListDevices(ctx context.Context, req *requests.DeviceList) ([ func (_m *Service) ListNamespaces(ctx context.Context, req *requests.NamespaceList) ([]models.Namespace, int, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for ListNamespaces") + } + var r0 []models.Namespace var r1 int var r2 error @@ -1008,6 +1212,10 @@ func (_m *Service) ListNamespaces(ctx context.Context, req *requests.NamespaceLi func (_m *Service) ListPublicKeys(ctx context.Context, paginator query.Paginator) ([]models.PublicKey, int, error) { ret := _m.Called(ctx, paginator) + if len(ret) == 0 { + panic("no return value specified for ListPublicKeys") + } + var r0 []models.PublicKey var r1 int var r2 error @@ -1041,6 +1249,10 @@ func (_m *Service) ListPublicKeys(ctx context.Context, paginator query.Paginator func (_m *Service) ListSessions(ctx context.Context, paginator query.Paginator) ([]models.Session, int, error) { ret := _m.Called(ctx, paginator) + if len(ret) == 0 { + panic("no return value specified for ListSessions") + } + var r0 []models.Session var r1 int var r2 error @@ -1074,6 +1286,10 @@ func (_m *Service) ListSessions(ctx context.Context, paginator query.Paginator) func (_m *Service) LookupDevice(ctx context.Context, namespace string, name string) (*models.Device, error) { ret := _m.Called(ctx, namespace, name) + if len(ret) == 0 { + panic("no return value specified for LookupDevice") + } + var r0 *models.Device var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*models.Device, error)); ok { @@ -1100,6 +1316,10 @@ func (_m *Service) LookupDevice(ctx context.Context, namespace string, name stri func (_m *Service) OfflineDevice(ctx context.Context, uid models.UID) error { ret := _m.Called(ctx, uid) + if len(ret) == 0 { + panic("no return value specified for OfflineDevice") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID) error); ok { r0 = rf(ctx, uid) @@ -1110,10 +1330,14 @@ func (_m *Service) OfflineDevice(ctx context.Context, uid models.UID) error { return r0 } -// PublicKey provides a mock function with given fields: +// PublicKey provides a mock function with no fields func (_m *Service) PublicKey() *rsa.PublicKey { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for PublicKey") + } + var r0 *rsa.PublicKey if rf, ok := ret.Get(0).(func() *rsa.PublicKey); ok { r0 = rf() @@ -1130,6 +1354,10 @@ func (_m *Service) PublicKey() *rsa.PublicKey { func (_m *Service) RemoveDeviceTag(ctx context.Context, uid models.UID, tag string) error { ret := _m.Called(ctx, uid, tag) + if len(ret) == 0 { + panic("no return value specified for RemoveDeviceTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string) error); ok { r0 = rf(ctx, uid, tag) @@ -1144,6 +1372,10 @@ func (_m *Service) RemoveDeviceTag(ctx context.Context, uid models.UID, tag stri func (_m *Service) RemoveNamespaceMember(ctx context.Context, req *requests.NamespaceRemoveMember) (*models.Namespace, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for RemoveNamespaceMember") + } + var r0 *models.Namespace var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceRemoveMember) (*models.Namespace, error)); ok { @@ -1170,6 +1402,10 @@ func (_m *Service) RemoveNamespaceMember(ctx context.Context, req *requests.Name func (_m *Service) RemovePublicKeyTag(ctx context.Context, tenant string, fingerprint string, tag string) error { ret := _m.Called(ctx, tenant, fingerprint, tag) + if len(ret) == 0 { + panic("no return value specified for RemovePublicKeyTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, fingerprint, tag) @@ -1184,6 +1420,10 @@ func (_m *Service) RemovePublicKeyTag(ctx context.Context, tenant string, finger func (_m *Service) RenameDevice(ctx context.Context, uid models.UID, name string, tenant string) error { ret := _m.Called(ctx, uid, name, tenant) + if len(ret) == 0 { + panic("no return value specified for RenameDevice") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, string, string) error); ok { r0 = rf(ctx, uid, name, tenant) @@ -1198,6 +1438,10 @@ func (_m *Service) RenameDevice(ctx context.Context, uid models.UID, name string func (_m *Service) RenameTag(ctx context.Context, tenant string, oldTag string, newTag string) error { ret := _m.Called(ctx, tenant, oldTag, newTag) + if len(ret) == 0 { + panic("no return value specified for RenameTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, tenant, oldTag, newTag) @@ -1212,6 +1456,10 @@ func (_m *Service) RenameTag(ctx context.Context, tenant string, oldTag string, func (_m *Service) Setup(ctx context.Context, req requests.Setup) error { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for Setup") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, requests.Setup) error); ok { r0 = rf(ctx, req) @@ -1226,6 +1474,10 @@ func (_m *Service) Setup(ctx context.Context, req requests.Setup) error { func (_m *Service) SetupVerify(ctx context.Context, sign string) error { ret := _m.Called(ctx, sign) + if len(ret) == 0 { + panic("no return value specified for SetupVerify") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, sign) @@ -1240,6 +1492,10 @@ func (_m *Service) SetupVerify(ctx context.Context, sign string) error { func (_m *Service) SystemDownloadInstallScript(ctx context.Context) (string, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for SystemDownloadInstallScript") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { @@ -1260,36 +1516,14 @@ func (_m *Service) SystemDownloadInstallScript(ctx context.Context) (string, err return r0, r1 } -// SystemGetInfo provides a mock function with given fields: ctx, req -func (_m *Service) SystemGetInfo(ctx context.Context, req requests.SystemGetInfo) (*models.SystemInfo, error) { - ret := _m.Called(ctx, req) - - var r0 *models.SystemInfo - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, requests.SystemGetInfo) (*models.SystemInfo, error)); ok { - return rf(ctx, req) - } - if rf, ok := ret.Get(0).(func(context.Context, requests.SystemGetInfo) *models.SystemInfo); ok { - r0 = rf(ctx, req) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*models.SystemInfo) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, requests.SystemGetInfo) error); ok { - r1 = rf(ctx, req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // UpdateAPIKey provides a mock function with given fields: ctx, req func (_m *Service) UpdateAPIKey(ctx context.Context, req *requests.UpdateAPIKey) error { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for UpdateAPIKey") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *requests.UpdateAPIKey) error); ok { r0 = rf(ctx, req) @@ -1304,6 +1538,10 @@ func (_m *Service) UpdateAPIKey(ctx context.Context, req *requests.UpdateAPIKey) func (_m *Service) UpdateDevice(ctx context.Context, tenant string, uid models.UID, name *string, publicURL *bool) error { ret := _m.Called(ctx, tenant, uid, name, publicURL) + if len(ret) == 0 { + panic("no return value specified for UpdateDevice") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, models.UID, *string, *bool) error); ok { r0 = rf(ctx, tenant, uid, name, publicURL) @@ -1318,6 +1556,10 @@ func (_m *Service) UpdateDevice(ctx context.Context, tenant string, uid models.U func (_m *Service) UpdateDeviceStatus(ctx context.Context, tenant string, uid models.UID, status models.DeviceStatus) error { ret := _m.Called(ctx, tenant, uid, status) + if len(ret) == 0 { + panic("no return value specified for UpdateDeviceStatus") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, models.UID, models.DeviceStatus) error); ok { r0 = rf(ctx, tenant, uid, status) @@ -1332,6 +1574,10 @@ func (_m *Service) UpdateDeviceStatus(ctx context.Context, tenant string, uid mo func (_m *Service) UpdateDeviceTag(ctx context.Context, uid models.UID, tags []string) error { ret := _m.Called(ctx, uid, tags) + if len(ret) == 0 { + panic("no return value specified for UpdateDeviceTag") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, []string) error); ok { r0 = rf(ctx, uid, tags) @@ -1346,6 +1592,10 @@ func (_m *Service) UpdateDeviceTag(ctx context.Context, uid models.UID, tags []s func (_m *Service) UpdateNamespaceMember(ctx context.Context, req *requests.NamespaceUpdateMember) error { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for UpdateNamespaceMember") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *requests.NamespaceUpdateMember) error); ok { r0 = rf(ctx, req) @@ -1360,6 +1610,10 @@ func (_m *Service) UpdateNamespaceMember(ctx context.Context, req *requests.Name func (_m *Service) UpdatePasswordUser(ctx context.Context, id string, currentPassword string, newPassword string) error { ret := _m.Called(ctx, id, currentPassword, newPassword) + if len(ret) == 0 { + panic("no return value specified for UpdatePasswordUser") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { r0 = rf(ctx, id, currentPassword, newPassword) @@ -1374,6 +1628,10 @@ func (_m *Service) UpdatePasswordUser(ctx context.Context, id string, currentPas func (_m *Service) UpdatePublicKey(ctx context.Context, fingerprint string, tenant string, key requests.PublicKeyUpdate) (*models.PublicKey, error) { ret := _m.Called(ctx, fingerprint, tenant, key) + if len(ret) == 0 { + panic("no return value specified for UpdatePublicKey") + } + var r0 *models.PublicKey var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, requests.PublicKeyUpdate) (*models.PublicKey, error)); ok { @@ -1400,6 +1658,10 @@ func (_m *Service) UpdatePublicKey(ctx context.Context, fingerprint string, tena func (_m *Service) UpdatePublicKeyTags(ctx context.Context, tenant string, fingerprint string, tags []string) error { ret := _m.Called(ctx, tenant, fingerprint, tags) + if len(ret) == 0 { + panic("no return value specified for UpdatePublicKeyTags") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, []string) error); ok { r0 = rf(ctx, tenant, fingerprint, tags) @@ -1414,6 +1676,10 @@ func (_m *Service) UpdatePublicKeyTags(ctx context.Context, tenant string, finge func (_m *Service) UpdateSession(ctx context.Context, uid models.UID, model models.SessionUpdate) error { ret := _m.Called(ctx, uid, model) + if len(ret) == 0 { + panic("no return value specified for UpdateSession") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, models.UID, models.SessionUpdate) error); ok { r0 = rf(ctx, uid, model) @@ -1428,6 +1694,10 @@ func (_m *Service) UpdateSession(ctx context.Context, uid models.UID, model mode func (_m *Service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([]string, error) { ret := _m.Called(ctx, req) + if len(ret) == 0 { + panic("no return value specified for UpdateUser") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, *requests.UpdateUser) ([]string, error)); ok { @@ -1450,13 +1720,12 @@ func (_m *Service) UpdateUser(ctx context.Context, req *requests.UpdateUser) ([] return r0, r1 } -type mockConstructorTestingTNewService interface { +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { mock.TestingT Cleanup(func()) -} - -// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewService(t mockConstructorTestingTNewService) *Service { +}) *Service { mock := &Service{} mock.Mock.Test(t) diff --git a/api/services/system.go b/api/services/system.go index 2a15b4a09b1..2b3d405245d 100644 --- a/api/services/system.go +++ b/api/services/system.go @@ -6,48 +6,46 @@ import ( "os" "strings" + "github.com/shellhub-io/shellhub/api/pkg/responses" "github.com/shellhub-io/shellhub/pkg/api/requests" "github.com/shellhub-io/shellhub/pkg/envs" - "github.com/shellhub-io/shellhub/pkg/models" ) type SystemService interface { - SystemGetInfo(ctx context.Context, req requests.SystemGetInfo) (*models.SystemInfo, error) + // GetSystemInfo retrieves the instance's information + GetSystemInfo(ctx context.Context, req *requests.GetSystemInfo) (*responses.SystemInfo, error) + SystemDownloadInstallScript(ctx context.Context) (string, error) } -// SystemGetInfo returns system instance information. -// It receives a context (ctx) and requests.SystemGetInfo, what contains a host (host) which is used to determine the -// API and SSH host of the system, and a port (port) that can be specified to override the API port from the host. -func (s *service) SystemGetInfo(ctx context.Context, req requests.SystemGetInfo) (*models.SystemInfo, error) { +func (s *service) GetSystemInfo(ctx context.Context, req *requests.GetSystemInfo) (*responses.SystemInfo, error) { + system, err := s.store.SystemGet(ctx) + if err != nil { + return nil, err + } + apiHost := strings.Split(req.Host, ":")[0] sshPort := envs.DefaultBackend.Get("SHELLHUB_SSH_PORT") - info := &models.SystemInfo{ + resp := &responses.SystemInfo{ Version: envs.DefaultBackend.Get("SHELLHUB_VERSION"), - Endpoints: &models.SystemInfoEndpoints{ + Setup: system.Setup, + Endpoints: &responses.SystemEndpointsInfo{ API: apiHost, SSH: fmt.Sprintf("%s:%s", apiHost, sshPort), }, + Authentication: &responses.SystemAuthenticationInfo{ + Local: system.Authentication.Local.Enabled, + }, } if req.Port > 0 { - info.Endpoints.API = fmt.Sprintf("%s:%d", apiHost, req.Port) + resp.Endpoints.API = fmt.Sprintf("%s:%d", apiHost, req.Port) } else { - info.Endpoints.API = req.Host - } - - info.Setup = true - if envs.IsCommunity() { - system, err := s.store.SystemGet(ctx) - if err != nil { - return nil, NewErrSetupForbidden(err) - } - - info.Setup = system.Setup + resp.Endpoints.API = req.Host } - return info, nil + return resp, nil } func (s *service) SystemDownloadInstallScript(_ context.Context) (string, error) { diff --git a/api/store/mongo/migrations/main.go b/api/store/mongo/migrations/main.go index 290426fe6af..65a4650a487 100644 --- a/api/store/mongo/migrations/main.go +++ b/api/store/mongo/migrations/main.go @@ -95,6 +95,8 @@ func GenerateMigrations() []migrate.Migration { migration83, migration84, migration85, + migration86, + migration87, } } diff --git a/api/store/mongo/migrations/migration_86.go b/api/store/mongo/migrations/migration_86.go new file mode 100644 index 00000000000..5eb3764215b --- /dev/null +++ b/api/store/mongo/migrations/migration_86.go @@ -0,0 +1,62 @@ +package migrations + +import ( + "context" + + "github.com/shellhub-io/shellhub/pkg/models" + "github.com/sirupsen/logrus" + migrate "github.com/xakep666/mongo-migrate" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +var migration86 = migrate.Migration{ + Version: 86, + Description: "Adding an 'auth_methods' attributes to user collection", + Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { + logrus.WithFields(logrus.Fields{ + "component": "migration", + "version": 86, + "action": "Up", + }).Info("Applying migration") + + filter := bson.M{ + "preferences.auth_methods": bson.M{"$exists": false}, + } + + update := bson.M{ + "$set": bson.M{ + "preferences.auth_methods": []string{models.UserAuthMethodLocal.String()}, + }, + } + + _, err := db. + Collection("users"). + UpdateMany(ctx, filter, update) + + return err + }), + Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { + logrus.WithFields(logrus.Fields{ + "component": "migration", + "version": 86, + "action": "Down", + }).Info("Reverting migration") + + filter := bson.M{ + "preferences.auth_methods": bson.M{"$exists": true}, + } + + update := bson.M{ + "$unset": bson.M{ + "preferences.auth_methods": "", + }, + } + + _, err := db. + Collection("users"). + UpdateMany(ctx, filter, update) + + return err + }), +} diff --git a/api/store/mongo/migrations/migration_86_test.go b/api/store/mongo/migrations/migration_86_test.go new file mode 100644 index 00000000000..b332186d607 --- /dev/null +++ b/api/store/mongo/migrations/migration_86_test.go @@ -0,0 +1,122 @@ +package migrations + +import ( + "context" + "testing" + + "github.com/shellhub-io/shellhub/pkg/envs" + envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + migrate "github.com/xakep666/mongo-migrate" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func TestMigration86Up(t *testing.T) { + ctx := context.Background() + + mock := &envmock.Backend{} + envs.DefaultBackend = mock + + cases := []struct { + description string + setup func() error + test func() error + }{ + { + description: "Success to apply up on migration 86", + setup: func() error { + _, err := c. + Database("test"). + Collection("users"). + InsertOne(ctx, map[string]interface{}{ + "name": "john doe", + "preferences": map[string]string{}, + }) + + return err + }, + }, + } + + for _, tc := range cases { + t.Run(tc.description, func(tt *testing.T) { + tt.Cleanup(func() { + assert.NoError(tt, srv.Reset()) + }) + + assert.NoError(tt, tc.setup()) + + migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[85]) + require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) + + query := c. + Database("test"). + Collection("users"). + FindOne(context.TODO(), bson.M{"name": "john doe"}) + + user := make(map[string]interface{}) + require.NoError(tt, query.Decode(&user)) + + v, ok := user["preferences"].(map[string]interface{})["auth_methods"] + require.Equal(tt, true, ok) + require.Equal(tt, primitive.A{"local"}, v) + }) + } +} + +func TestMigration86Down(t *testing.T) { + ctx := context.Background() + + mock := &envmock.Backend{} + envs.DefaultBackend = mock + + cases := []struct { + description string + setup func() error + test func() error + }{ + { + description: "Success to apply down on migration 86", + setup: func() error { + _, err := c. + Database("test"). + Collection("users"). + InsertOne(ctx, map[string]interface{}{ + "name": "john doe", + "preferences": map[string]interface{}{ + "auth_methods": []string{"some_method"}, + }, + }) + + return err + }, + }, + } + + for _, tc := range cases { + t.Run(tc.description, func(t *testing.T) { + t.Cleanup(func() { + assert.NoError(t, srv.Reset()) + }) + + assert.NoError(t, tc.setup()) + + migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[85]) + require.NoError(t, migrates.Up(context.Background(), migrate.AllAvailable)) + require.NoError(t, migrates.Down(context.Background(), migrate.AllAvailable)) + + query := c. + Database("test"). + Collection("users"). + FindOne(context.TODO(), bson.M{"name": "john doe"}) + + user := make(map[string]interface{}) + require.NoError(t, query.Decode(&user)) + + _, ok := user["preferences"].(map[string]interface{})["auth_methods"] + require.Equal(t, false, ok) + }) + } +} diff --git a/api/store/mongo/migrations/migration_87.go b/api/store/mongo/migrations/migration_87.go new file mode 100644 index 00000000000..c7fb388cf43 --- /dev/null +++ b/api/store/mongo/migrations/migration_87.go @@ -0,0 +1,65 @@ +package migrations + +import ( + "context" + + "github.com/sirupsen/logrus" + migrate "github.com/xakep666/mongo-migrate" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +var migration87 = migrate.Migration{ + Version: 87, + Description: "Adding an 'authentication' attributes to system collection", + Up: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { + logrus.WithFields(logrus.Fields{ + "component": "migration", + "version": 87, + "action": "Up", + }).Info("Applying migration") + + filter := bson.M{ + "authentication": bson.M{"$exists": false}, + } + + update := bson.M{ + "$set": bson.M{ + "authentication": bson.M{ + "local": bson.M{ + "enabled": true, + }, + }, + }, + } + + _, err := db. + Collection("system"). + UpdateMany(ctx, filter, update) + + return err + }), + Down: migrate.MigrationFunc(func(ctx context.Context, db *mongo.Database) error { + logrus.WithFields(logrus.Fields{ + "component": "migration", + "version": 87, + "action": "Down", + }).Info("Reverting migration") + + filter := bson.M{ + "authentication": bson.M{"$exists": true}, + } + + update := bson.M{ + "$unset": bson.M{ + "authentication": "", + }, + } + + _, err := db. + Collection("system"). + UpdateMany(ctx, filter, update) + + return err + }), +} diff --git a/api/store/mongo/migrations/migration_87_test.go b/api/store/mongo/migrations/migration_87_test.go new file mode 100644 index 00000000000..baa31524081 --- /dev/null +++ b/api/store/mongo/migrations/migration_87_test.go @@ -0,0 +1,119 @@ +package migrations + +import ( + "context" + "testing" + + "github.com/shellhub-io/shellhub/pkg/envs" + envmock "github.com/shellhub-io/shellhub/pkg/envs/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + migrate "github.com/xakep666/mongo-migrate" + "go.mongodb.org/mongo-driver/bson" +) + +func TestMigration87Up(t *testing.T) { + ctx := context.Background() + + mock := &envmock.Backend{} + envs.DefaultBackend = mock + + mock.On("Get", "SHELLHUB_CLOUD").Return("false") + mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false") + + tests := []struct { + description string + setup func() error + }{ + { + description: "Apply up on migration 87", + setup: func() error { + _, err := c. + Database("test"). + Collection("system"). + InsertOne(ctx, map[string]interface{}{}) + + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(tt *testing.T) { + tt.Cleanup(func() { + assert.NoError(tt, srv.Reset()) + }) + + assert.NoError(tt, tc.setup()) + + migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[86]) + require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) + + query := c. + Database("test"). + Collection("system"). + FindOne(context.TODO(), bson.M{}) + + system := make(map[string]interface{}) + require.NoError(tt, query.Decode(&system)) + + authentication, ok := system["authentication"].(map[string]interface{}) + require.Equal(tt, true, ok) + require.Equal(tt, map[string]interface{}{"local": map[string]interface{}{"enabled": true}}, authentication) + }) + } +} + +func TestMigration87Down(t *testing.T) { + ctx := context.Background() + + mock := &envmock.Backend{} + envs.DefaultBackend = mock + + mock.On("Get", "SHELLHUB_CLOUD").Return("false") + mock.On("Get", "SHELLHUB_ENTERPRISE").Return("false") + + tests := []struct { + description string + setup func() error + }{ + { + description: "Apply up on migration 87", + setup: func() error { + _, err := c. + Database("test"). + Collection("system"). + InsertOne(ctx, map[string]interface{}{ + "authentication": "some_value", + }) + + return err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(tt *testing.T) { + tt.Cleanup(func() { + assert.NoError(tt, srv.Reset()) + }) + + assert.NoError(tt, tc.setup()) + + migrates := migrate.NewMigrate(c.Database("test"), GenerateMigrations()[86]) + require.NoError(tt, migrates.Up(context.Background(), migrate.AllAvailable)) + require.NoError(tt, migrates.Down(context.Background(), migrate.AllAvailable)) + + query := c. + Database("test"). + Collection("system"). + FindOne(context.TODO(), bson.M{}) + + system := make(map[string]interface{}) + require.NoError(tt, query.Decode(&system)) + + _, ok := system["authentication"] + require.Equal(tt, false, ok) + }) + } +} diff --git a/cli/services/users.go b/cli/services/users.go index 26866c43fc4..36908aa6afa 100644 --- a/cli/services/users.go +++ b/cli/services/users.go @@ -57,6 +57,9 @@ func (s *service) UserCreate(ctx context.Context, input *inputs.UserCreate) (*mo Status: models.UserStatusConfirmed, CreatedAt: clock.Now(), MaxNamespaces: MaxNumberNamespacesCommunity, + Preferences: models.UserPreferences{ + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + }, } if _, err := s.store.UserCreate(ctx, user); err != nil { diff --git a/cli/services/users_test.go b/cli/services/users_test.go index a6de2cc3b99..7d26782d877 100644 --- a/cli/services/users_test.go +++ b/cli/services/users_test.go @@ -144,6 +144,9 @@ func TestUserCreate(t *testing.T) { Status: models.UserStatusConfirmed, CreatedAt: clock.Now(), MaxNamespaces: MaxNumberNamespacesCommunity, + Preferences: models.UserPreferences{ + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + }, } mock.On("UserCreate", ctx, user).Return("", errors.New("error")).Once() }, @@ -178,6 +181,9 @@ func TestUserCreate(t *testing.T) { Status: models.UserStatusConfirmed, CreatedAt: clock.Now(), MaxNamespaces: MaxNumberNamespacesCommunity, + Preferences: models.UserPreferences{ + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + }, } mock.On("UserCreate", ctx, user).Return("000000000000000000000000", nil).Once() mock.On("SystemSet", ctx, "setup", true).Return(nil).Once() @@ -196,6 +202,9 @@ func TestUserCreate(t *testing.T) { Status: models.UserStatusConfirmed, CreatedAt: clock.Now(), MaxNamespaces: MaxNumberNamespacesCommunity, + Preferences: models.UserPreferences{ + AuthMethods: []models.UserAuthMethod{models.UserAuthMethodLocal}, + }, }, nil}, }, } diff --git a/pkg/api/requests/system.go b/pkg/api/requests/system.go index 9c019b31512..8d9c9dc9da9 100644 --- a/pkg/api/requests/system.go +++ b/pkg/api/requests/system.go @@ -1,6 +1,6 @@ package requests -type SystemGetInfo struct { +type GetSystemInfo struct { Host string `header:"X-Forwarded-Host"` Port int `header:"X-Forwarded-Port"` } diff --git a/pkg/models/system.go b/pkg/models/system.go index 4e247148a4b..3cdfc3b6a6a 100644 --- a/pkg/models/system.go +++ b/pkg/models/system.go @@ -1,16 +1,19 @@ package models -type SystemInfo struct { - Version string `json:"version"` - Endpoints *SystemInfoEndpoints `json:"endpoints"` - Setup bool `json:"setup"` +type System struct { + Setup bool `json:"setup"` + // Authentication manages the settings for available authentication methods, such as manual + // username/password authentication and SAML authentication. Each authentication method + // can be individually enabled or disabled. + Authentication *SystemAuthentication `json:"authentication" bson:"authentication"` } -type SystemInfoEndpoints struct { - API string `json:"api"` - SSH string `json:"ssh"` +type SystemAuthentication struct { + Local *SystemAuthenticationLocal `json:"manual" bson:"manual"` } -type System struct { - Setup bool `json:"setup" bson:"setup"` +type SystemAuthenticationLocal struct { + // Enabled indicates whether manual authentication using a username and password is enabled or + // not. + Enabled bool `json:"enabled" bool:"enabled"` } diff --git a/pkg/models/user.go b/pkg/models/user.go index c409b9eedbf..fbe5b2e39c5 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -39,6 +39,17 @@ func (o UserOrigin) String() string { return string(o) } +type UserAuthMethod string + +const ( + // UserAuthMethodLocal indicates that the user can authenticate using an email and password. + UserAuthMethodLocal UserAuthMethod = "local" +) + +func (a UserAuthMethod) String() string { + return string(a) +} + type User struct { ID string `json:"id,omitempty" bson:"_id,omitempty"` // Origin specifies the the user's signup method. @@ -83,6 +94,9 @@ type UserMFA struct { type UserPreferences struct { // PreferredNamespace represents the namespace the user most recently authenticated with. PreferredNamespace string `json:"-" bson:"preferred_namespace"` + + // AuthMethods indicates the authentication methods that the user can use to authenticate. + AuthMethods []UserAuthMethod `json:"-" bson:"auth_methods"` } type UserPassword struct { @@ -127,17 +141,18 @@ func (i *UserAuthIdentifier) IsEmail() bool { } type UserAuthResponse struct { - Token string `json:"token"` - User string `json:"user"` - Origin string `json:"string"` - Name string `json:"name"` - ID string `json:"id"` - Tenant string `json:"tenant"` - Email string `json:"email"` - RecoveryEmail string `json:"recovery_email"` - Role string `json:"role"` - MFA bool `json:"mfa"` - MaxNamespaces int `json:"max_namespaces"` + Token string `json:"token"` + User string `json:"user"` + Origin string `json:"string"` + AuthMethods []UserAuthMethod `json:"auth_methods"` + Name string `json:"name"` + ID string `json:"id"` + Tenant string `json:"tenant"` + Email string `json:"email"` + RecoveryEmail string `json:"recovery_email"` + Role string `json:"role"` + MFA bool `json:"mfa"` + MaxNamespaces int `json:"max_namespaces"` } // NOTE: This struct has been moved to the cloud repo as it is only used in a cloud context; @@ -153,16 +168,17 @@ type UserTokenRecover struct { // UserChanges specifies the attributes that can be updated for a user. Any zero values in this // struct must be ignored. If an attribute is a pointer type, its zero value is represented as `nil`. type UserChanges struct { - LastLogin time.Time `bson:"last_login,omitempty"` - Name string `bson:"name,omitempty"` - Username string `bson:"username,omitempty"` - Email string `bson:"email,omitempty"` - RecoveryEmail string `bson:"recovery_email,omitempty"` - Password string `bson:"password,omitempty"` - Status UserStatus `bson:"status,omitempty"` - PreferredNamespace *string `bson:"preferences.preferred_namespace,omitempty"` - MaxNamespaces *int `bson:"max_namespaces,omitempty"` - EmailMarketing *bool `bson:"email_marketing,omitempty"` + LastLogin time.Time `bson:"last_login,omitempty"` + Name string `bson:"name,omitempty"` + Username string `bson:"username,omitempty"` + Email string `bson:"email,omitempty"` + RecoveryEmail string `bson:"recovery_email,omitempty"` + Password string `bson:"password,omitempty"` + Status UserStatus `bson:"status,omitempty"` + PreferredNamespace *string `bson:"preferences.preferred_namespace,omitempty"` + MaxNamespaces *int `bson:"max_namespaces,omitempty"` + EmailMarketing *bool `bson:"email_marketing,omitempty"` + AuthMethods []UserAuthMethod `bson:"preferences.auth_methods,omitempty"` } // UserConflicts holds user attributes that must be unique for each itam and can be utilized in queries