From b923953ca58e4ef02f93f5dd3f328f98b072b129 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 25 May 2021 15:34:44 +0200 Subject: [PATCH] Removing superuser logic --- app/boot_levels.go | 22 +++- app/cli.go | 18 +-- auth/commands/commands.go | 6 +- federation/commands/sync.go | 7 +- federation/rest/sync_data.go | 5 +- federation/service/node.go | 6 +- federation/service/processer_data.go | 2 +- federation/service/processer_structure.go | 3 - federation/service/sync.go | 3 + federation/service/sync_worker_data.go | 2 +- federation/service/sync_worker_structure.go | 2 +- pkg/auth/identity.go | 12 -- pkg/auth/identity_context.go | 8 -- pkg/auth/system.go | 58 +++++++++ pkg/corredor/service.go | 13 +- pkg/envoy/store/user_test.go | 6 +- pkg/eventbus/handlers_test.go | 11 +- pkg/options/RBAC.yaml | 8 +- pkg/provision/auth.go | 2 - pkg/provision/oidc.go | 3 - pkg/provision/provision.go | 16 +-- pkg/provision/roles.go | 124 +++++++++++--------- pkg/provision/users.go | 92 +++++++++++++++ store/rdbms/users.go | 7 +- system/commands/exporter.go | 5 +- system/commands/importer.go | 7 +- system/commands/rbac.go | 4 +- system/commands/roles.go | 7 +- system/commands/settings.go | 29 +---- system/commands/sink.go | 6 +- system/commands/users.go | 10 +- system/rest/actionlog.go | 59 +++++++++- system/scim/routes.go | 5 +- system/scim/security.go | 8 +- system/service/auth.go | 3 + system/service/auth_actions.gen.go | 36 ++++++ system/service/auth_actions.yaml | 5 + system/service/role.go | 12 +- system/service/service.go | 7 +- system/service/user.go | 107 ++++++++--------- system/service/user_actions.gen.go | 64 ++++++++++ system/service/user_actions.yaml | 9 +- system/types/user.go | 21 +++- tests/automation/provision_test.go | 1 - tests/compose/provision_test.go | 1 - tests/envoy/data_shaping_test.go | 16 +-- tests/envoy/store_csv_records_test.go | 4 +- tests/envoy/store_jsonl_records_test.go | 4 +- tests/envoy/store_yaml_base_test.go | 2 +- tests/envoy/store_yaml_modules_test.go | 2 +- tests/envoy/store_yaml_pages_test.go | 2 +- tests/envoy/system_stamps_test.go | 2 +- tests/envoy/yaml_store_base_test.go | 3 +- tests/envoy/yaml_store_provision_test.go | 2 +- tests/envoy/yaml_store_relations_test.go | 3 +- tests/system/provision_test.go | 1 - 56 files changed, 590 insertions(+), 293 deletions(-) create mode 100644 pkg/auth/system.go create mode 100644 pkg/provision/users.go diff --git a/app/boot_levels.go b/app/boot_levels.go index b15ce087b..04eccff57 100644 --- a/app/boot_levels.go +++ b/app/boot_levels.go @@ -255,13 +255,31 @@ func (app *CortezaApp) Provision(ctx context.Context) (err error) { return err } + var ( + uu []*types.User + rr []*types.Role + ) + + // Basic provision for system resources that we need before anything else + if rr, err = provision.SystemRoles(ctx, app.Log, app.Store); err != nil { + return + } + + // Basic provision for system users that we need before anything else + if uu, err = provision.SystemUsers(ctx, app.Log, app.Store); err != nil { + return + } + + // now set system users with so that the whole app knows what to use + auth.SetSystemUsers(uu, rr) + if !app.Opt.Provision.Always { app.Log.Debug("provisioning skipped (PROVISION_ALWAYS=false)") } else { defer sentry.Recover() ctx = actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_APP_Provision) - ctx = auth.SetSuperUserContext(ctx) + ctx = auth.SetIdentityToContext(ctx, auth.ProvisionUser()) if err = provision.Run(ctx, app.Log, app.Store, app.Opt.Provision, app.Opt.Auth); err != nil { return err @@ -282,6 +300,8 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) { return err } + // Load users + app.WsServer = websocket.Server(app.Log, app.Opt.Websocket) ctx = actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_APP_Init) diff --git a/app/cli.go b/app/cli.go index 5697bd692..08309ae4b 100644 --- a/app/cli.go +++ b/app/cli.go @@ -81,18 +81,18 @@ func (app *CortezaApp) InitCLI() { } app.Command.AddCommand( - systemCommands.Users(app), - systemCommands.Roles(app), - systemCommands.RBAC(app), - systemCommands.Sink(app), - systemCommands.Settings(app), - systemCommands.Import(storeInit), - systemCommands.Export(storeInit), + systemCommands.Users(ctx, app), + systemCommands.Roles(ctx, app), + systemCommands.RBAC(ctx, app), + systemCommands.Sink(ctx, app), + systemCommands.Settings(ctx, app), + systemCommands.Import(ctx, storeInit), + systemCommands.Export(ctx, storeInit), serveCmd, upgradeCmd, provisionCmd, - authCommands.Command(app, storeInit), - federationCommands.Sync(app), + authCommands.Command(ctx, app, storeInit), + federationCommands.Sync(ctx, app), cli.EnvCommand(), cli.VersionCommand(), ) diff --git a/auth/commands/commands.go b/auth/commands/commands.go index 39576f854..bcca5ee68 100644 --- a/auth/commands/commands.go +++ b/auth/commands/commands.go @@ -26,7 +26,7 @@ func commandPreRunInitService(app serviceInitializer) func(*cobra.Command, []str } } -func Command(app serviceInitializer, storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Command { +func Command(ctx context.Context, app serviceInitializer, storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Command { var ( enableDiscoveredProvider bool skipValidationOnAutoDiscoveredProvider bool @@ -88,8 +88,6 @@ func Command(app serviceInitializer, storeInit func(ctx context.Context) (store. PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) - user *types.User err error @@ -112,9 +110,7 @@ func Command(app serviceInitializer, storeInit func(ctx context.Context) (store. Args: cobra.ExactArgs(1), PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { - var ( - ctx = auth.SetSuperUserContext(cli.Context()) err error ntf = service.DefaultAuthNotification ) diff --git a/federation/commands/sync.go b/federation/commands/sync.go index 30ee1de72..104eed04f 100644 --- a/federation/commands/sync.go +++ b/federation/commands/sync.go @@ -3,8 +3,6 @@ package commands import ( "context" - "github.com/cortezaproject/corteza-server/pkg/cli" - "github.com/spf13/cobra" ) @@ -14,10 +12,7 @@ type ( } ) -func Sync(app serviceInitializer) *cobra.Command { - - ctx := cli.Context() - +func Sync(ctx context.Context, app serviceInitializer) *cobra.Command { // Sync commands. cmd := &cobra.Command{ Use: "sync", diff --git a/federation/rest/sync_data.go b/federation/rest/sync_data.go index 70e7c23f9..a688d6df1 100644 --- a/federation/rest/sync_data.go +++ b/federation/rest/sync_data.go @@ -12,6 +12,7 @@ import ( "github.com/cortezaproject/corteza-server/federation/rest/request" "github.com/cortezaproject/corteza-server/federation/service" "github.com/cortezaproject/corteza-server/federation/types" + "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/errors" "github.com/cortezaproject/corteza-server/pkg/federation" "github.com/cortezaproject/corteza-server/pkg/filter" @@ -193,9 +194,7 @@ func (ctrl SyncData) readExposed(ctx context.Context, r *request.SyncDataReadExp return strings.Contains(u.Handle, "federation_"), nil }) - // TODO - remove once the federation users are doing the persist - // on the data sync, for now, it's superuser - users = append(users, &st.User{ID: 10000000000000000}) + users = append(users, auth.FederationUser()) query := buildLastSyncQuery(r.LastSync) ignoredUsersQuery := buildIgnoredUsersQuery(users) diff --git a/federation/service/node.go b/federation/service/node.go index 1881967d4..389e2fba5 100644 --- a/federation/service/node.go +++ b/federation/service/node.go @@ -271,6 +271,9 @@ func (svc node) Pair(ctx context.Context, nodeID uint64) error { return NodeErrNotAllowedToPair() } + // elevate permissions for user lookup & creation! + ctx = auth.SetIdentityToContext(ctx, auth.FederationUser()) + _, err := svc.updater( ctx, nodeID, @@ -460,9 +463,6 @@ func (svc node) fetchFederatedUser(ctx context.Context, n *types.Node) (*sysType // Generate handle for user that se this node uHandle := fmt.Sprintf("federation_%d", n.ID) - // elevate permissions for user lookup & creation! - ctx = auth.SetSuperUserContext(ctx) - u, err := svc.sysUser.FindByHandle(ctx, uHandle) if err == nil { // Reuse existing user diff --git a/federation/service/processer_data.go b/federation/service/processer_data.go index d8c47e143..7bff6b45b 100644 --- a/federation/service/processer_data.go +++ b/federation/service/processer_data.go @@ -48,7 +48,7 @@ func (dp *dataProcesser) Process(ctx context.Context, payload []byte) (Processer }, nil } - ctx = auth.SetSuperUserContext(ctx) + ctx = auth.SetIdentityToContext(ctx, auth.FederationUser()) for _, er := range o { var ( diff --git a/federation/service/processer_structure.go b/federation/service/processer_structure.go index 855660071..7690e9d52 100644 --- a/federation/service/processer_structure.go +++ b/federation/service/processer_structure.go @@ -4,7 +4,6 @@ import ( "context" "github.com/cortezaproject/corteza-server/federation/types" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/decoder" st "github.com/cortezaproject/corteza-server/system/types" ) @@ -41,8 +40,6 @@ func (dp *structureProcesser) Process(ctx context.Context, payload []byte) (Proc }, nil } - ctx = auth.SetSuperUserContext(ctx) - for _, em := range o { new := &types.SharedModule{ NodeID: dp.Node.ID, diff --git a/federation/service/sync.go b/federation/service/sync.go index d6c88dde0..a91f88635 100644 --- a/federation/service/sync.go +++ b/federation/service/sync.go @@ -10,6 +10,7 @@ import ( cs "github.com/cortezaproject/corteza-server/compose/service" ct "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/federation/types" + "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/filter" ss "github.com/cortezaproject/corteza-server/system/service" st "github.com/cortezaproject/corteza-server/system/types" @@ -62,6 +63,8 @@ func (s *Sync) CanUpdateSharedModule(ctx context.Context, new *types.SharedModul // ProcessPayload passes the payload to the syncer lib func (s *Sync) ProcessPayload(ctx context.Context, payload []byte, out chan Url, url types.SyncerURI, processer Processer) (ProcesserResponse, error) { + ctx = auth.SetIdentityToContext(ctx, auth.FederationUser()) + return s.syncer.Process(ctx, payload, out, url, processer) } diff --git a/federation/service/sync_worker_data.go b/federation/service/sync_worker_data.go index a3d01dc12..cf7586283 100644 --- a/federation/service/sync_worker_data.go +++ b/federation/service/sync_worker_data.go @@ -163,7 +163,7 @@ func (w *syncWorkerData) Watch(ctx context.Context, delay time.Duration, limit i ctx, cancel := context.WithCancel(ctx) defer cancel() - ctx = auth.SetSuperUserContext(ctx) + ctx = auth.SetIdentityToContext(ctx, auth.FederationUser()) ticker := time.NewTicker(delay) diff --git a/federation/service/sync_worker_structure.go b/federation/service/sync_worker_structure.go index 23aadec00..1e9141617 100644 --- a/federation/service/sync_worker_structure.go +++ b/federation/service/sync_worker_structure.go @@ -106,7 +106,7 @@ func (w *syncWorkerStructure) Watch(ctx context.Context, delay time.Duration, li ctx, cancel := context.WithCancel(ctx) defer cancel() - ctx = auth.SetSuperUserContext(ctx) + ctx = auth.SetIdentityToContext(ctx, auth.FederationUser()) ticker := time.NewTicker(delay) diff --git a/pkg/auth/identity.go b/pkg/auth/identity.go index 56abc34ef..b359f7031 100644 --- a/pkg/auth/identity.go +++ b/pkg/auth/identity.go @@ -13,10 +13,6 @@ type ( } ) -const ( - superUserID uint64 = 10000000000000000 -) - func NewIdentity(id uint64, rr ...uint64) *Identity { return &Identity{ id: id, @@ -40,14 +36,6 @@ func (i Identity) String() string { return fmt.Sprintf("%d", i.Identity()) } -func NewSuperUserIdentity() *Identity { - return NewIdentity(superUserID) -} - -func IsSuperUser(i Identifiable) bool { - return i != nil && superUserID == i.Identity() -} - func ExtractUserIDFromSubClaim(sub string) uint64 { userID, _ := ExtractFromSubClaim(sub) return userID diff --git a/pkg/auth/identity_context.go b/pkg/auth/identity_context.go index 2f1558856..4cc9628b4 100644 --- a/pkg/auth/identity_context.go +++ b/pkg/auth/identity_context.go @@ -19,11 +19,3 @@ func GetIdentityFromContext(ctx context.Context) Identifiable { return NewIdentity(0) } } - -// SetSuperUserContext stores system user as identity -// and accompanying JWT for it to the context -func SetSuperUserContext(ctx context.Context) context.Context { - su := NewSuperUserIdentity() - - return SetIdentityToContext(ctx, su) -} diff --git a/pkg/auth/system.go b/pkg/auth/system.go new file mode 100644 index 000000000..bcaf4b5b6 --- /dev/null +++ b/pkg/auth/system.go @@ -0,0 +1,58 @@ +package auth + +import ( + "github.com/cortezaproject/corteza-server/system/types" +) + +const ( + ProvisionUserHandle = "corteza-provisioner" + ServiceUserHandle = "corteza-service" + FederationUserHandle = "corteza-federation" + + BypassRoleHandle = "super-admin" + AuthenticatedRoleHandle = "authenticated" + AnonymousRoleHandle = "anonymous" +) + +var ( + provisionUser *types.User + serviceUser *types.User + federationUser *types.User +) + +// SetSystemUsers takes list of users and sets/updates appropriate +// provision, service & fed users +// +// These are then accessed via special *User() fn. +func SetSystemUsers(uu types.UserSet, rr types.RoleSet) { + bpr := rr.FindByHandle(BypassRoleHandle) + + for _, u := range uu { + switch u.Handle { + case ProvisionUserHandle: + provisionUser = u.Clone() + federationUser.SetRoles([]uint64{bpr.ID}) + case ServiceUserHandle: + serviceUser = u.Clone() + federationUser.SetRoles([]uint64{bpr.ID}) + case FederationUserHandle: + federationUser = u.Clone() + federationUser.SetRoles([]uint64{bpr.ID}) + } + } +} + +// ProvisionUser returns clone of system provision user +func ProvisionUser() *types.User { + return provisionUser.Clone() +} + +// ServiceUser returns clone of system service user +func ServiceUser() *types.User { + return serviceUser.Clone() +} + +// FederationUser returns clone of system federation user +func FederationUser() *types.User { + return federationUser.Clone() +} diff --git a/pkg/corredor/service.go b/pkg/corredor/service.go index 8ea789e19..b7a808850 100644 --- a/pkg/corredor/service.go +++ b/pkg/corredor/service.go @@ -3,6 +3,10 @@ package corredor import ( "context" "fmt" + "strings" + "sync" + "time" + "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/errors" "github.com/cortezaproject/corteza-server/pkg/eventbus" @@ -16,9 +20,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "strings" - "sync" - "time" ) type ( @@ -396,12 +397,8 @@ func (svc service) Exec(ctx context.Context, scriptName string, args ScriptArgs) // This is used only in case of explicit execution (onManual) and never when // scripts are executed implicitly (deferred, before/after...) func (svc service) canExec(ctx context.Context, script string) bool { - u := auth.GetIdentityFromContext(ctx) - if auth.IsSuperUser(u) { - return true - } - // @todo RBACv2 convert roles u.Roles()... + //u := auth.GetIdentityFromContext(ctx) //return svc.permissions.Check(nil, script, permOpExec) != rbac.Deny return true } diff --git a/pkg/envoy/store/user_test.go b/pkg/envoy/store/user_test.go index eb71e1e52..3edc27c88 100644 --- a/pkg/envoy/store/user_test.go +++ b/pkg/envoy/store/user_test.go @@ -20,7 +20,7 @@ func TestUser_Merger(t *testing.T) { Email: "email", Name: "name", Handle: "handle", - Kind: types.BotUser, + Kind: types.SystemUser, Meta: &types.UserMeta{}, CreatedAt: now, UpdatedAt: nowP, @@ -34,7 +34,7 @@ func TestUser_Merger(t *testing.T) { req.Equal("email", c.Email) req.Equal("name", c.Name) req.Equal("handle", c.Handle) - req.Equal(types.BotUser, c.Kind) + req.Equal(types.SystemUser, c.Kind) req.Equal(&types.UserMeta{}, c.Meta) req.Equal(now, c.CreatedAt) req.Equal(nowP, c.UpdatedAt) @@ -48,7 +48,7 @@ func TestUser_Merger(t *testing.T) { req.Equal("email", c.Email) req.Equal("name", c.Name) req.Equal("handle", c.Handle) - req.Equal(types.BotUser, c.Kind) + req.Equal(types.SystemUser, c.Kind) req.Equal(&types.UserMeta{}, c.Meta) req.Equal(now, c.CreatedAt) req.Equal(nowP, c.UpdatedAt) diff --git a/pkg/eventbus/handlers_test.go b/pkg/eventbus/handlers_test.go index dfdedde3b..92a1fa87f 100644 --- a/pkg/eventbus/handlers_test.go +++ b/pkg/eventbus/handlers_test.go @@ -152,6 +152,8 @@ func TestHandler_RegOps(t *testing.T) { func TestHandlerHandler(t *testing.T) { var ( + identity uint64 = 42 + a = assert.New(t) ctx = context.Background() ev = &mockEvent{} @@ -160,18 +162,19 @@ func TestHandlerHandler(t *testing.T) { trSimple = &handler{ handler: func(ctx context.Context, ev Event) error { passedthrough = true - a.True(auth.IsSuperUser(ev.(*mockEvent).identity)) + a.Equal(identity, ev.(*mockEvent).identity.Identity()) return nil }, } ) a.False(passedthrough) - a.False(auth.IsSuperUser(ev.identity)) + a.Nil(ev.identity) - trSimple.Handle(auth.SetSuperUserContext(ctx), ev) + a.NoError(trSimple.Handle(auth.SetIdentityToContext(ctx, auth.NewIdentity(identity)), ev)) - a.True(auth.IsSuperUser(ev.identity)) + a.NotNil(ev.identity) + a.Equal(identity, ev.identity.Identity()) a.True(passedthrough, "expecting to pass through simple handler") } diff --git a/pkg/options/RBAC.yaml b/pkg/options/RBAC.yaml index 3e068e21a..89c6111b2 100644 --- a/pkg/options/RBAC.yaml +++ b/pkg/options/RBAC.yaml @@ -8,12 +8,8 @@ props: # description: When disabled all operations on all resources are allowed - name: serviceUser - default: "corteza" - description: |- - Used at boot time, for provisioning and other service-level operations. - Service user is automatically member of all bypass roles. - - name: bypassRoles + # Using literal string instead of DefaultBypassRole constant for clarity & documentation default: "super-admin" description: |- Space delimited list of role handles. @@ -21,6 +17,7 @@ props: System will refuse to start if check-bypassing roles are also listed as authenticated or anonymous auto-assigned roles. - name: authenticatedRoles + # Using literal string instead of DefaultAuthenticatedRole constant for clarity & documentation default: "authenticated" description: |- Space delimited list of role handles. @@ -29,6 +26,7 @@ props: System will refuse to start if roles listed here are also listed under anonymous roles - name: anonymousRoles + # Using literal string instead of DefaultAnonymousRole constant for clarity & documentation default: "anonymous" description: |- Space delimited list of role handles. diff --git a/pkg/provision/auth.go b/pkg/provision/auth.go index 3496fefdc..b381be54a 100644 --- a/pkg/provision/auth.go +++ b/pkg/provision/auth.go @@ -22,8 +22,6 @@ func authSettingsAutoDiscovery(ctx context.Context, log *zap.Logger, s store.Sto boolWrapper func() bool ) - log = log.Named("auth.settings-discovery") - var ( // Setter // diff --git a/pkg/provision/oidc.go b/pkg/provision/oidc.go index 9d4bc531b..596c45757 100644 --- a/pkg/provision/oidc.go +++ b/pkg/provision/oidc.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/cortezaproject/corteza-server/auth/external" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" @@ -127,8 +126,6 @@ func authAddExternals(ctx context.Context, log *zap.Logger, s store.Settings) (e eap.Handle = kind } - ctx = auth.SetSuperUserContext(ctx) - _ = external.AddProvider(ctx, s, eap, false) } diff --git a/pkg/provision/provision.go b/pkg/provision/provision.go index b556c5a14..df49d9143 100644 --- a/pkg/provision/provision.go +++ b/pkg/provision/provision.go @@ -14,21 +14,21 @@ import ( ) func Run(ctx context.Context, log *zap.Logger, s store.Storer, provisionOpt options.ProvisionOpt, authOpt options.AuthOpt) error { - ffn := []func() error{ - func() error { return roles(ctx, log, s) }, + log = log.Named("provision") + ffn := []func() error{ // Migrations: func() error { return migrateApplications(ctx, s) }, - func() error { return migrateEmailTemplates(ctx, log, s) }, + func() error { return migrateEmailTemplates(ctx, log.Named("email-templates"), s) }, // Config (full & partial) - func() error { return importConfig(ctx, log, s, provisionOpt.Path) }, + func() error { return importConfig(ctx, log.Named("config"), s, provisionOpt.Path) }, // Auto-discoveries and other parts that cannot be imported from static files - func() error { return authSettingsAutoDiscovery(ctx, log, s) }, - func() error { return authAddExternals(ctx, log, s) }, - func() error { return oidcAutoDiscovery(ctx, log, s, authOpt) }, - func() error { return defaultAuthClient(ctx, log, s, authOpt) }, + func() error { return authSettingsAutoDiscovery(ctx, log.Named("auth.settings-discovery"), s) }, + func() error { return authAddExternals(ctx, log.Named("auth.externals"), s) }, + func() error { return oidcAutoDiscovery(ctx, log.Named("auth.oidc-auto-discovery"), s, authOpt) }, + func() error { return defaultAuthClient(ctx, log.Named("auth.clients"), s, authOpt) }, } for _, fn := range ffn { diff --git a/pkg/provision/roles.go b/pkg/provision/roles.go index 0447582f5..5a82ac7c5 100644 --- a/pkg/provision/roles.go +++ b/pkg/provision/roles.go @@ -2,6 +2,7 @@ package provision import ( "context" + "fmt" "time" "github.com/cortezaproject/corteza-server/pkg/filter" @@ -11,56 +12,48 @@ import ( "go.uber.org/zap" ) -func roles(ctx context.Context, log *zap.Logger, s store.Storer) error { +// SystemRoles creates system roles +func SystemRoles(ctx context.Context, log *zap.Logger, s store.Storer) (rr []*types.Role, err error) { const ( obsoleteEveryoneID uint64 = 1 obsoleteAdminsID uint64 = 2 ) var ( - f = types.RoleFilter{ - Archived: filter.StateInclusive, - Deleted: filter.StateInclusive, - } - now = time.Now().Round(time.Second) - - rr = types.RoleSet{ - &types.Role{ - Name: "Super administrator", - Handle: "super-admin", - Meta: &types.RoleMeta{ - Description: "Super admin is a 'bypass' role that allows all actions it's members", - Context: nil, - }, - }, - - &types.Role{ - Name: "Authenticated", - Handle: "authenticated", - Meta: &types.RoleMeta{ - Description: "Authenticated role is auto-assigned to all authenticated sessions", - Context: nil, - }, - }, - - &types.Role{ - Name: "Anonymous", - Handle: "anonymous", - Meta: &types.RoleMeta{ - Description: "Authenticated role is auto-assigned to all non-authenticated sessions", - Context: nil, - }}, - } ) - m := make(map[string]*types.Role) - if set, _, err := store.SearchRoles(ctx, s, f); err != nil { - return err - } else { - for _, r := range set { - m[r.Handle] = r - } + rr = types.RoleSet{ + &types.Role{ + Name: "Super administrator", + Handle: "super-admin", + Meta: &types.RoleMeta{ + Description: "Super admin is a 'bypass' role that allows all actions it's members", + Context: nil, + }, + }, + + &types.Role{ + Name: "Authenticated", + Handle: "authenticated", + Meta: &types.RoleMeta{ + Description: "Authenticated role is auto-assigned to all authenticated sessions", + Context: nil, + }, + }, + + &types.Role{ + Name: "Anonymous", + Handle: "anonymous", + Meta: &types.RoleMeta{ + Description: "Authenticated role is auto-assigned to all non-authenticated sessions", + Context: nil, + }}, + } + + m, err := loadRoles(ctx, s) + if err != nil { + return } for i := range rr { @@ -78,7 +71,7 @@ func roles(ctx context.Context, log *zap.Logger, s store.Storer) error { rr[i] = m[r.Handle] // make sure it's not deleted or archived - // and left other props as they are + // and leave other props as they are r.DeletedAt = nil r.ArchivedAt = nil @@ -86,7 +79,7 @@ func roles(ctx context.Context, log *zap.Logger, s store.Storer) error { } if err := store.UpsertRole(ctx, s, rr...); err != nil { - return err + return nil, fmt.Errorf("failed to provision roles: %w", err) } // let's see if everyone role is still here: @@ -95,13 +88,13 @@ func roles(ctx context.Context, log *zap.Logger, s store.Storer) error { // everyone role still present and it is using "hardcoded" ID // we can remove it - if err := store.DeleteRoleByID(ctx, s, obsoleteEveryoneID); err != nil { - return err + if err = store.DeleteRoleByID(ctx, s, obsoleteEveryoneID); err != nil { + return } // transfer all rbac rules - if err := s.TransferRbacRules(ctx, obsoleteEveryoneID, m["authenticated"].ID); err != nil { - return nil + if err = s.TransferRbacRules(ctx, obsoleteEveryoneID, m["authenticated"].ID); err != nil { + return } } @@ -114,24 +107,43 @@ func roles(ctx context.Context, log *zap.Logger, s store.Storer) error { m["admins"].ID = id.Next() m["admins"].UpdatedAt = &now - if err := store.DeleteRoleByID(ctx, s, obsoleteAdminsID); err != nil { - return err + if err = store.DeleteRoleByID(ctx, s, obsoleteAdminsID); err != nil { + return } - if err := store.CreateRole(ctx, s, m["admins"]); err != nil { - return err + if err = store.CreateRole(ctx, s, m["admins"]); err != nil { + return } // transfer all rbac rules - if err := s.TransferRoleMembers(ctx, obsoleteAdminsID, m["admins"].ID); err != nil { - return nil + if err = s.TransferRoleMembers(ctx, obsoleteAdminsID, m["admins"].ID); err != nil { + return } // transfer all rbac rules - if err := s.TransferRbacRules(ctx, obsoleteAdminsID, m["admins"].ID); err != nil { - return nil + if err = s.TransferRbacRules(ctx, obsoleteAdminsID, m["admins"].ID); err != nil { + return } } - return nil + return +} + +func loadRoles(ctx context.Context, s store.Roles) (m map[string]*types.Role, err error) { + var ( + f = types.RoleFilter{ + Archived: filter.StateInclusive, + Deleted: filter.StateInclusive, + } + ) + + m = make(map[string]*types.Role) + + if set, _, err := store.SearchRoles(ctx, s, f); err == nil { + for _, r := range set { + m[r.Handle] = r + } + } + + return } diff --git a/pkg/provision/users.go b/pkg/provision/users.go new file mode 100644 index 000000000..98b701072 --- /dev/null +++ b/pkg/provision/users.go @@ -0,0 +1,92 @@ +package provision + +import ( + "context" + "fmt" + "time" + + "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/types" + "go.uber.org/zap" +) + +// SystemUsers creates or updates system users +func SystemUsers(ctx context.Context, log *zap.Logger, s store.Users) (uu []*types.User, err error) { + var ( + now = time.Now().Round(time.Second) + ) + + uu = types.UserSet{ + &types.User{ + Email: "provision@corteza.local", + Name: "Corteza Provisioner", + Handle: auth.ProvisionUserHandle, + Kind: types.SystemUser, + }, + &types.User{ + Email: "service@corteza.local", + Name: "Corteza Service", + Handle: auth.ServiceUserHandle, + Kind: types.SystemUser, + }, + &types.User{ + Email: "federation@corteza.local", + Name: "Corteza Federation", + Handle: auth.FederationUserHandle, + Kind: types.SystemUser, + }, + } + + m, err := loadUsers(ctx, s) + if err != nil { + return + } + + for i := range uu { + u := uu[i] + if m[u.Handle] == nil { + log.Info("creating user", zap.String("handle", u.Handle)) + // this is a new user + u.ID = id.Next() + u.CreatedAt = now + + if err := store.UpsertUser(ctx, s, u); err != nil { + return nil, fmt.Errorf("failed to provision user %s: %w", u.Handle, err) + } + } else { + u.ID = m[u.Handle].ID + u.Email = m[u.Handle].Email + u.Name = m[u.Handle].Name + u.SuspendedAt = nil + u.DeletedAt = nil + + if err := store.UpsertUser(ctx, s, u); err != nil { + return nil, fmt.Errorf("failed to provision user %s: %w", u.Handle, err) + } + + } + } + + return +} + +func loadUsers(ctx context.Context, s store.Users) (m map[string]*types.User, err error) { + var ( + f = types.UserFilter{ + Suspended: filter.StateInclusive, + Deleted: filter.StateInclusive, + } + ) + + m = make(map[string]*types.User) + if set, _, err := store.SearchUsers(ctx, s, f); err == nil { + for _, r := range set { + m[r.Handle] = r + } + } + + return +} diff --git a/store/rdbms/users.go b/store/rdbms/users.go index 2fb6cc46e..888ca13bf 100644 --- a/store/rdbms/users.go +++ b/store/rdbms/users.go @@ -3,6 +3,7 @@ package rdbms import ( "context" "fmt" + "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/system/types" @@ -58,9 +59,9 @@ func (s Store) convertUserFilter(f types.UserFilter) (query squirrel.SelectBuild query = query.Where(squirrel.Eq{"usr.handle": f.Handle}) } - if f.Kind != "" { - query = query.Where(squirrel.Eq{"usr.kind": f.Kind}) - } + //if f.Kind != "" { + // query = query.Where(squirrel.Eq{"usr.kind": f.Kind}) + //} return } diff --git a/system/commands/exporter.go b/system/commands/exporter.go index faa00e626..7ff6842fc 100644 --- a/system/commands/exporter.go +++ b/system/commands/exporter.go @@ -10,14 +10,13 @@ import ( "github.com/cortezaproject/corteza-server/pkg/envoy/yaml" "github.com/spf13/cobra" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/pkg/envoy" su "github.com/cortezaproject/corteza-server/pkg/envoy/store" "github.com/cortezaproject/corteza-server/store" ) -func Export(storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Command { +func Export(ctx context.Context, storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Command { var ( output string ) @@ -29,8 +28,6 @@ func Export(storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Co Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) - f = su.NewDecodeFilter() ) diff --git a/system/commands/importer.go b/system/commands/importer.go index 66f4fc857..ccb75aa97 100644 --- a/system/commands/importer.go +++ b/system/commands/importer.go @@ -6,7 +6,6 @@ import ( "github.com/spf13/cobra" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/envoy/directory" @@ -16,7 +15,7 @@ import ( "github.com/cortezaproject/corteza-server/store" ) -func Import(storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Command { +func Import(ctx context.Context, storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Command { var ( replaceOnExisting bool mergeLeftOnExisting bool @@ -28,10 +27,6 @@ func Import(storeInit func(ctx context.Context) (store.Storer, error)) *cobra.Co Short: "Import data from yaml sources.", Run: func(cmd *cobra.Command, args []string) { - var ( - ctx = auth.SetSuperUserContext(cli.Context()) - ) - s, err := storeInit(ctx) cli.HandleError(err) diff --git a/system/commands/rbac.go b/system/commands/rbac.go index 51df09489..68d2560cd 100644 --- a/system/commands/rbac.go +++ b/system/commands/rbac.go @@ -1,6 +1,8 @@ package commands import ( + "context" + "github.com/spf13/cobra" ) @@ -39,7 +41,7 @@ import ( // } //) -func RBAC(app serviceInitializer) *cobra.Command { +func RBAC(ctx context.Context, app serviceInitializer) *cobra.Command { cmd := &cobra.Command{ Use: "rbac", Short: "RBAC tools", diff --git a/system/commands/roles.go b/system/commands/roles.go index 33a377120..058e368cc 100644 --- a/system/commands/roles.go +++ b/system/commands/roles.go @@ -1,14 +1,15 @@ package commands import ( - "github.com/cortezaproject/corteza-server/pkg/auth" + "context" + "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/system/service" "github.com/cortezaproject/corteza-server/system/types" "github.com/spf13/cobra" ) -func Roles(app serviceInitializer) *cobra.Command { +func Roles(ctx context.Context, app serviceInitializer) *cobra.Command { cmd := &cobra.Command{ Use: "roles", Short: "Role management", @@ -21,8 +22,6 @@ func Roles(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) - roleStr, userStr = args[0], args[1] role *types.Role diff --git a/system/commands/settings.go b/system/commands/settings.go index da2a2485d..24d2a4bba 100644 --- a/system/commands/settings.go +++ b/system/commands/settings.go @@ -1,19 +1,20 @@ package commands import ( + "context" "encoding/json" - "github.com/cortezaproject/corteza-server/system/types" "os" "strings" + "github.com/cortezaproject/corteza-server/system/types" + "github.com/spf13/cobra" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/system/service" ) -func Settings(app serviceInitializer) *cobra.Command { +func Settings(ctx context.Context, app serviceInitializer) *cobra.Command { var ( cmd = &cobra.Command{ Use: "settings", @@ -26,10 +27,6 @@ func Settings(app serviceInitializer) *cobra.Command { Short: "List all", PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { - var ( - ctx = auth.SetSuperUserContext(cli.Context()) - ) - prefix := cmd.Flags().Lookup("prefix").Value.String() if kv, err := service.DefaultSettings.FindByPrefix(ctx, prefix); err != nil { cli.HandleError(err) @@ -57,10 +54,6 @@ func Settings(app serviceInitializer) *cobra.Command { Args: cobra.ExactArgs(1), PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { - var ( - ctx = auth.SetSuperUserContext(cli.Context()) - ) - if v, err := service.DefaultSettings.Get(ctx, args[0], 0); err != nil { cli.HandleError(err) } else if v != nil { @@ -75,11 +68,6 @@ func Settings(app serviceInitializer) *cobra.Command { Args: cobra.ExactArgs(2), PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { - var ( - err error - ctx = auth.SetSuperUserContext(cli.Context()) - ) - value := args[1] v := &types.SettingValue{ @@ -87,16 +75,14 @@ func Settings(app serviceInitializer) *cobra.Command { } if cmd.Flags().Lookup("as-string").Changed { - err = v.SetValue(value) + cli.HandleError(v.SetValue(value)) } else { - err = v.SetRawValue(value) + err := v.SetRawValue(value) if _, is := err.(*json.SyntaxError); is { // Quote the raw value and re-parse err = v.SetRawValue(`"` + value + `"`) } - } - if err != nil { cli.HandleError(err) } @@ -113,7 +99,6 @@ func Settings(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) fh *os.File err error ) @@ -153,7 +138,6 @@ func Settings(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) fh *os.File err error ) @@ -186,7 +170,6 @@ func Settings(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) names = []string{} ) diff --git a/system/commands/sink.go b/system/commands/sink.go index e96578b94..ba353a2a1 100644 --- a/system/commands/sink.go +++ b/system/commands/sink.go @@ -1,13 +1,15 @@ package commands import ( + "context" + "time" + "github.com/cortezaproject/corteza-server/system/service" "github.com/spf13/cobra" - "time" ) // Will perform OpenID connect auto-configuration -func Sink(app serviceInitializer) *cobra.Command { +func Sink(ctx context.Context, app serviceInitializer) *cobra.Command { var ( expires string srup = service.SinkRequestUrlParams{} diff --git a/system/commands/users.go b/system/commands/users.go index 4a6461b12..65d11fe69 100644 --- a/system/commands/users.go +++ b/system/commands/users.go @@ -1,11 +1,11 @@ package commands import ( + "context" "fmt" "strconv" "syscall" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/system/service" @@ -14,7 +14,7 @@ import ( "golang.org/x/crypto/ssh/terminal" ) -func Users(app serviceInitializer) *cobra.Command { +func Users(ctx context.Context, app serviceInitializer) *cobra.Command { var ( flagNoPassword bool ) @@ -33,8 +33,6 @@ func Users(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) - queryFlag = cmd.Flags().Lookup("query").Value.String() limitFlag = cmd.Flags().Lookup("limit").Value.String() @@ -88,8 +86,6 @@ func Users(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) - authSvc = service.Auth() // @todo email validation @@ -139,8 +135,6 @@ func Users(app serviceInitializer) *cobra.Command { PreRunE: commandPreRunInitService(app), Run: func(cmd *cobra.Command, args []string) { var ( - ctx = auth.SetSuperUserContext(cli.Context()) - user *types.User err error password []byte diff --git a/system/rest/actionlog.go b/system/rest/actionlog.go index fe6c1b1e4..3710547fd 100644 --- a/system/rest/actionlog.go +++ b/system/rest/actionlog.go @@ -12,9 +12,13 @@ import ( ) type ( + actionlogUserSearcher interface { + Find(context.Context, types.UserFilter) (types.UserSet, types.UserFilter, error) + } + Actionlog struct { actionSvc actionlog.Recorder - userSvc service.UserService + userSvc actionlogUserSearcher } // Extend actionlog.Action so we can @@ -70,8 +74,9 @@ func (ctrl Actionlog) makeFilterPayload(ctx context.Context, ee []*actionlog.Act pp[e] = &actionlogActionPayload{Action: ee[e]} } - err = ctrl.userSvc.Preloader( + err = userPreloader( ctx, + ctrl.userSvc, func(c chan uint64) { for e := range ee { c <- ee[e].ActorID @@ -103,3 +108,53 @@ func (ctrl Actionlog) makeFilterPayload(ctx context.Context, ee []*actionlog.Act return &actionlogPayload{Filter: f, Set: pp}, nil } + +// Preloader collects all ids of users, loads them and sets them back +// +// +// @todo this kind of preloader is useful and can be implemented in bunch +// of places and replace old code +func userPreloader(ctx context.Context, svc actionlogUserSearcher, g func(chan uint64), f types.UserFilter, s func(*types.User) error) error { + var ( + // channel that will collect the IDs in the getter + ch = make(chan uint64, 0) + + // unique index for IDs + unq = make(map[uint64]bool) + ) + + // Reset the collection of the IDs + f.UserID = make([]uint64, 0) + + // Call getter and collect the IDs + go g(ch) + +rangeLoop: + for { + select { + case <-ctx.Done(): + close(ch) + break rangeLoop + case id, ok := <-ch: + if !ok { + // Channel closed + break rangeLoop + } + + if !unq[id] { + unq[id] = true + f.UserID = append(f.UserID, id) + } + } + + } + + // Load all users (even if deleted, suspended) from the given list of IDs + uu, _, err := svc.Find(ctx, f) + + if err != nil { + return err + } + + return uu.Walk(s) +} diff --git a/system/scim/routes.go b/system/scim/routes.go index c82aa378b..e27a820be 100644 --- a/system/scim/routes.go +++ b/system/scim/routes.go @@ -1,12 +1,13 @@ package scim import ( + "net/http" + "regexp" + "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/system/service" "github.com/go-chi/chi" "go.uber.org/zap" - "net/http" - "regexp" ) type ( diff --git a/system/scim/security.go b/system/scim/security.go index f77bf0471..f09cee2a5 100644 --- a/system/scim/security.go +++ b/system/scim/security.go @@ -2,16 +2,16 @@ package scim import ( "context" - "github.com/cortezaproject/corteza-server/pkg/auth" "net/http" + + "github.com/cortezaproject/corteza-server/pkg/auth" ) type ( getSecurityContextFn func(r *http.Request) context.Context ) -// All actions are in security context of a superuser for now -// +// Set service user to request's identity func getSecurityContext(r *http.Request) context.Context { - return auth.SetSuperUserContext(r.Context()) + return auth.SetIdentityToContext(r.Context(), auth.ServiceUser()) } diff --git a/system/service/auth.go b/system/service/auth.go index f16d1f440..a7b795f7c 100644 --- a/system/service/auth.go +++ b/system/service/auth.go @@ -815,6 +815,9 @@ func (svc auth) procLogin(ctx context.Context, s store.Storer, u *types.User, c return AuthErrFailedForSuspendedUser() case u.DeletedAt != nil: return AuthErrFailedForDeletedUser() + case u.Kind == types.SystemUser: + return AuthErrFailedForSystemUser() + } if err = svc.LoadRoleMemberships(ctx, u); err != nil { diff --git a/system/service/auth_actions.gen.go b/system/service/auth_actions.gen.go index 8047f8286..fd80fa897 100644 --- a/system/service/auth_actions.gen.go +++ b/system/service/auth_actions.gen.go @@ -851,6 +851,42 @@ func AuthErrFailedForSuspendedUser(mm ...*authActionProps) *errors.Error { return e } +// AuthErrFailedForSystemUser returns "system:auth.failedForSystemUser" as *errors.Error +// +// Note: This error will be wrapped with safe (system:auth.invalidCredentials) error! +// +// This function is auto-generated. +// +func AuthErrFailedForSystemUser(mm ...*authActionProps) *errors.Error { + var p = &authActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + "failedForSystemUser", + + errors.Meta("type", "failedForSystemUser"), + errors.Meta("resource", "system:auth"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(authLogMetaKey{}, "system user {user} tried to log-in with {credentials.kind}"), + errors.Meta(authPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + // Wrap with safe error + e = AuthErrInvalidCredentials().Wrap(e) + + return e +} + // AuthErrFailedUnconfirmedEmail returns "system:auth.failedUnconfirmedEmail" as *errors.Error // // diff --git a/system/service/auth_actions.yaml b/system/service/auth_actions.yaml index d781a6b8c..436ffb4f8 100644 --- a/system/service/auth_actions.yaml +++ b/system/service/auth_actions.yaml @@ -107,6 +107,11 @@ errors: log: "suspended user {user} tried to log-in with {credentials.kind}" severity: warning + - error: failedForSystemUser + maskedWith: invalidCredentials + log: "system user {user} tried to log-in with {credentials.kind}" + severity: warning + - error: failedUnconfirmedEmail message: "system requires confirmed email before logging in" log: "failed to log-in with with unconfirmed email" diff --git a/system/service/role.go b/system/service/role.go index 7401c1651..3654a8866 100644 --- a/system/service/role.go +++ b/system/service/role.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/cortezaproject/corteza-server/pkg/actionlog" + intAuth "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/errors" "github.com/cortezaproject/corteza-server/pkg/eventbus" "github.com/cortezaproject/corteza-server/pkg/expr" @@ -18,6 +19,7 @@ import ( "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/service/event" "github.com/cortezaproject/corteza-server/system/types" + "go.uber.org/zap" ) @@ -92,7 +94,7 @@ func Role() *role { } } -// SetImmutable sets list of handles for all system roles +// SetSystem sets list of handles for all system roles // // System roles can not be changed or deleted func (svc role) SetSystem(hh ...string) { @@ -725,12 +727,20 @@ func initRoles(ctx context.Context, log *zap.Logger, opt options.RBACOpt, eb eve if bypass, err = s(opt.BypassRoles); err != nil { return fmt.Errorf("failed to process list of bypass roles (RBAC_BYPASS_ROLES): %w", err) + } else if !bypass[intAuth.BypassRoleHandle] { + return fmt.Errorf("default bypass role %s (DefaultBypassRole) not in bypass role list (RBAC_BYPASS_ROLES)", intAuth.BypassRoleHandle) } + if authenticated, err = s(opt.AuthenticatedRoles); err != nil { return fmt.Errorf("failed to process list of authenticated roles (RBAC_AUTHENTICATED_ROLES): %w", err) + } else if !authenticated[intAuth.AuthenticatedRoleHandle] { + return fmt.Errorf("default authenticated role %s (DefaultAuthenticatedRole) not in authenticated role list (RBAC_AUTHENTICATED_ROLES)", intAuth.AuthenticatedRoleHandle) } + if anonymous, err = s(opt.AnonymousRoles); err != nil { return fmt.Errorf("failed to process list of anonymous roles (RBAC_ANONYMOUS_ROLES): %w", err) + } else if !anonymous[intAuth.AnonymousRoleHandle] { + return fmt.Errorf("default anonymous role %s (DefaultAnonymousRole) not in anonymous role list (RBAC_ANONYMOUS_ROLES)", intAuth.AnonymousRoleHandle) } for r := range authenticated { diff --git a/system/service/service.go b/system/service/service.go index 246c152e9..9ae277e12 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -7,7 +7,6 @@ import ( automationService "github.com/cortezaproject/corteza-server/automation/service" "github.com/cortezaproject/corteza-server/pkg/actionlog" - intAuth "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/eventbus" "github.com/cortezaproject/corteza-server/pkg/healthcheck" "github.com/cortezaproject/corteza-server/pkg/id" @@ -69,7 +68,7 @@ var ( DefaultAuth *auth DefaultAuthClient *authClient - DefaultUser UserService + DefaultUser *user DefaultRole *role DefaultApplication *application DefaultReminder ReminderService @@ -161,7 +160,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock DefaultAuthNotification = AuthNotification(CurrentSettings, DefaultRenderer, c.Auth) DefaultAuth = Auth() DefaultAuthClient = AuthClient(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service()) - DefaultUser = User(ctx) + DefaultUser = User() DefaultRole = Role() DefaultApplication = Application(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service()) DefaultReminder = Reminder(ctx, DefaultLogger.Named("reminder"), ws) @@ -204,7 +203,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock func Activate(ctx context.Context) (err error) { // Run initial update of current settings with super-user credentials - err = DefaultSettings.UpdateCurrent(intAuth.SetSuperUserContext(ctx)) + err = DefaultSettings.UpdateCurrent(ctx) if err != nil { return } diff --git a/system/service/user.go b/system/service/user.go index 41288af7b..bed13a6f5 100644 --- a/system/service/user.go +++ b/system/service/user.go @@ -37,6 +37,11 @@ type ( eventbus eventDispatcher store store.Storer + + // List (cache) of preloaded users, accessible by handle + // + // It also does negative caching by assigning empty User structs + preloaded map[string]*types.User } userAuth interface { @@ -55,10 +60,6 @@ type ( CanUnmaskNameOnUser(context.Context, *types.User) bool } - // Temp types to support user.Preloader - userIdGetter func(chan uint64) - userSetter func(*types.User) error - UserService interface { FindByUsername(ctx context.Context, username string) (*types.User, error) FindByEmail(ctx context.Context, email string) (*types.User, error) @@ -81,14 +82,12 @@ type ( SetPassword(ctx context.Context, userID uint64, password string) error - Preloader(context.Context, userIdGetter, types.UserFilter, userSetter) error - DeleteAuthTokensByUserID(ctx context.Context, userID uint64) (err error) DeleteAuthSessionsByUserID(ctx context.Context, userID uint64) (err error) } ) -func User(ctx context.Context) *user { +func User() *user { return &user{ eventbus: eventbus.Service(), ac: DefaultAccessControl, @@ -98,6 +97,8 @@ func User(ctx context.Context) *user { store: DefaultStore, actionlog: DefaultActionlog, + + preloaded: make(map[string]*types.User), } } @@ -111,16 +112,6 @@ func (svc user) FindByID(ctx context.Context, userID uint64) (u *types.User, err return UserErrInvalidID() } - su := internalAuth.NewIdentity(userID) - if internalAuth.IsSuperUser(su) { - // Handling case when looking for a super-user - // - // Currently, superuser is a virtual entity - // and does not exists in the db - u = &types.User{ID: userID} - return nil - } - u, err = store.LookupUserByID(ctx, svc.store, userID) if u, err = svc.proc(ctx, u, err); err != nil { return err @@ -349,6 +340,10 @@ func (svc user) Create(ctx context.Context, new *types.User) (u *types.User, err return UserErrNotAllowedToCreate() } + if new.Kind == types.SystemUser { + return UserErrNotAllowedToCreateSystem() + } + if !handle.IsValid(new.Handle) { return UserErrInvalidHandle() } @@ -420,6 +415,11 @@ func (svc user) Update(ctx context.Context, upd *types.User) (u *types.User, err uaProps.setUser(u) + if upd.Kind == types.SystemUser || u.Kind == types.SystemUser { + return UserErrNotAllowedToUpdateSystem() + } + + // @todo RBACv2 this could/should be solved via context roles if upd.ID != internalAuth.GetIdentityFromContext(ctx).Identity() { if !svc.ac.CanUpdateUser(ctx, u) { return UserErrNotAllowedToUpdate() @@ -519,6 +519,10 @@ func (svc user) Delete(ctx context.Context, userID uint64) (err error) { return } + if u.Kind == types.SystemUser { + return UserErrNotAllowedToDelete() + } + if !svc.ac.CanDeleteUser(ctx, u) { return UserErrNotAllowedToDelete() } @@ -560,6 +564,10 @@ func (svc user) Undelete(ctx context.Context, userID uint64) (err error) { return err } + if u.Kind == types.SystemUser { + return UserErrNotAllowedToDelete() + } + if !svc.ac.CanDeleteUser(ctx, u) { return UserErrNotAllowedToDelete() } @@ -593,6 +601,10 @@ func (svc user) Suspend(ctx context.Context, userID uint64) (err error) { uaProps.setUser(u) + if u.Kind == types.SystemUser { + return UserErrNotAllowedToSuspend() + } + if !svc.ac.CanSuspendUser(ctx, u) { return UserErrNotAllowedToSuspend() } @@ -634,6 +646,10 @@ func (svc user) Unsuspend(ctx context.Context, userID uint64) (err error) { uaProps.setUser(u) + if u.Kind == types.SystemUser { + return UserErrNotAllowedToUnsuspend() + } + if !svc.ac.CanUnsuspendUser(ctx, u) { return UserErrNotAllowedToUnsuspend() } @@ -646,7 +662,6 @@ func (svc user) Unsuspend(ctx context.Context, userID uint64) (err error) { }() return svc.recordAction(ctx, uaProps, UserActionUnsuspend, err) - } // SetPassword sets new password for a user @@ -665,6 +680,10 @@ func (svc user) SetPassword(ctx context.Context, userID uint64, newPassword stri uaProps.setUser(u) + if u.Kind == types.SystemUser { + return UserErrNotAllowedToUpdateSystem() + } + if !svc.ac.CanUpdateUser(ctx, u) { return UserErrNotAllowedToUpdate() } @@ -703,54 +722,20 @@ func (svc user) maskName(ctx context.Context, u *types.User) bool { return svc.settings.Privacy.Mask.Name && !svc.ac.CanUnmaskNameOnUser(ctx, u) } -// Preloader collects all ids of users, loads them and sets them back -// -// -// @todo this kind of preloader is useful and can be implemented in bunch -// of places and replace old code -func (svc user) Preloader(ctx context.Context, g userIdGetter, f types.UserFilter, s userSetter) error { - var ( - // channel that will collect the IDs in the getter - ch = make(chan uint64, 0) - - // unique index for IDs - unq = make(map[uint64]bool) - ) - - // Reset the collection of the IDs - f.UserID = make([]uint64, 0) - - // Call getter and collect the IDs - go g(ch) - -rangeLoop: - for { - select { - case <-ctx.Done(): - close(ch) - break rangeLoop - case id, ok := <-ch: - if !ok { - // Channel closed - break rangeLoop - } - - if !unq[id] { - unq[id] = true - f.UserID = append(f.UserID, id) - } +func (svc *user) Get(ctx context.Context, h string) (u *types.User, err error) { + if svc.preloaded[h] == nil { + svc.preloaded[h], err = svc.FindByHandle(ctx, h) + if err != nil { + svc.preloaded[h] = &types.User{} + return } - } - // Load all users (even if deleted, suspended) from the given list of IDs - uu, _, err := svc.Find(ctx, f) - - if err != nil { - return err + if svc.preloaded[h] == nil || svc.preloaded[h].ID == 0 { + return nil, UserErrNotFound() } - return uu.Walk(s) + return svc.preloaded[h], nil } // DeleteAuthTokensByUserID will delete all auth tokens of user which will un-authorize all auth clients of user diff --git a/system/service/user_actions.gen.go b/system/service/user_actions.gen.go index 9969c9a72..3413983d5 100644 --- a/system/service/user_actions.gen.go +++ b/system/service/user_actions.gen.go @@ -798,6 +798,38 @@ func UserErrNotAllowedToCreate(mm ...*userActionProps) *errors.Error { return e } +// UserErrNotAllowedToCreateSystem returns "system:user.notAllowedToCreateSystem" as *errors.Error +// +// +// This function is auto-generated. +// +func UserErrNotAllowedToCreateSystem(mm ...*userActionProps) *errors.Error { + var p = &userActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to create system users", nil), + + errors.Meta("type", "notAllowedToCreateSystem"), + errors.Meta("resource", "system:user"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(userLogMetaKey{}, "failed to create system users"), + errors.Meta(userPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + // UserErrNotAllowedToUpdate returns "system:user.notAllowedToUpdate" as *errors.Error // // @@ -830,6 +862,38 @@ func UserErrNotAllowedToUpdate(mm ...*userActionProps) *errors.Error { return e } +// UserErrNotAllowedToUpdateSystem returns "system:user.notAllowedToUpdateSystem" as *errors.Error +// +// +// This function is auto-generated. +// +func UserErrNotAllowedToUpdateSystem(mm ...*userActionProps) *errors.Error { + var p = &userActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to update system users", nil), + + errors.Meta("type", "notAllowedToUpdateSystem"), + errors.Meta("resource", "system:user"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(userLogMetaKey{}, "failed to update system users"), + errors.Meta(userPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + // UserErrNotAllowedToDelete returns "system:user.notAllowedToDelete" as *errors.Error // // diff --git a/system/service/user_actions.yaml b/system/service/user_actions.yaml index 500b81a5b..46a2f51dd 100644 --- a/system/service/user_actions.yaml +++ b/system/service/user_actions.yaml @@ -82,7 +82,6 @@ errors: message: "invalid email" severity: warning - - error: notAllowedToRead message: "not allowed to read this user" log: "failed to read {user.handle}; insufficient permissions" @@ -95,10 +94,18 @@ errors: message: "not allowed to create users" log: "failed to create users; insufficient permissions" + - error: notAllowedToCreateSystem + message: "not allowed to create system users" + log: "failed to create system users" + - error: notAllowedToUpdate message: "not allowed to update this user" log: "failed to update {user.handle}; insufficient permissions" + - error: notAllowedToUpdateSystem + message: "not allowed to update system users" + log: "failed to update system users" + - error: notAllowedToDelete message: "not allowed to delete this user" log: "failed to delete {user.handle}; insufficient permissions" diff --git a/system/types/user.go b/system/types/user.go index 3901eb5cf..aa2b961bb 100644 --- a/system/types/user.go +++ b/system/types/user.go @@ -105,7 +105,7 @@ type ( const ( NormalUser UserKind = "" - BotUser UserKind = "bot" + SystemUser UserKind = "sys" ) func (u User) String() string { @@ -128,6 +128,25 @@ func (u *User) SetRoles(rr []uint64) { u.roles = rr } +func (u *User) Clone() *User { + return &User{ + ID: u.ID, + Username: u.Username, + Email: u.Email, + Name: u.Name, + Handle: u.Handle, + Kind: u.Kind, + Meta: u.Meta, + EmailConfirmed: u.EmailConfirmed, + Labels: u.Labels, + CreatedAt: u.CreatedAt, + UpdatedAt: u.UpdatedAt, + SuspendedAt: u.SuspendedAt, + DeletedAt: u.DeletedAt, + roles: u.roles, + } +} + func (meta *UserMeta) Scan(value interface{}) error { //lint:ignore S1034 This typecast is intentional, we need to get []byte out of a []uint8 switch value.(type) { diff --git a/tests/automation/provision_test.go b/tests/automation/provision_test.go index d666a61a8..5d88b9fcc 100644 --- a/tests/automation/provision_test.go +++ b/tests/automation/provision_test.go @@ -7,7 +7,6 @@ import ( func TestProvisioning(t *testing.T) { t.SkipNow() //h := newHelper(t) - //ctx := auth.SetSuperUserContext(h.secCtx()) // //readers, err := impAux.ReadStatic(provision.Asset) //h.noError(err) diff --git a/tests/compose/provision_test.go b/tests/compose/provision_test.go index 3077c91e1..642413b74 100644 --- a/tests/compose/provision_test.go +++ b/tests/compose/provision_test.go @@ -7,7 +7,6 @@ import ( func TestProvisioning(t *testing.T) { t.SkipNow() //h := newHelper(t) - //ctx := auth.SetSuperUserContext(h.secCtx()) // //readers, err := impAux.ReadStatic(provision.Asset) //h.noError(err) diff --git a/tests/envoy/data_shaping_test.go b/tests/envoy/data_shaping_test.go index fdd3e36ab..34e5e5d6d 100644 --- a/tests/envoy/data_shaping_test.go +++ b/tests/envoy/data_shaping_test.go @@ -17,7 +17,7 @@ import ( func TestDataShaping(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -88,7 +88,7 @@ func TestDataShaping(t *testing.T) { func TestDataShaping_large(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -154,7 +154,7 @@ func TestDataShaping_large(t *testing.T) { func TestDataShaping_fieldTypes(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -232,7 +232,7 @@ func TestDataShaping_fieldTypes(t *testing.T) { func TestDataShaping_refs(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -320,7 +320,7 @@ func TestDataShaping_refs(t *testing.T) { func TestDataShaping_xrefsPeer(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -408,7 +408,7 @@ func TestDataShaping_xrefsPeer(t *testing.T) { func TestDataShaping_xrefsStore(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -501,7 +501,7 @@ func TestDataShaping_xrefsStore(t *testing.T) { func TestDataShaping_xrefsMix(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error @@ -600,7 +600,7 @@ func TestDataShaping_xrefsMix(t *testing.T) { func TestDataShaping_update(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) err error diff --git a/tests/envoy/store_csv_records_test.go b/tests/envoy/store_csv_records_test.go index d672655ef..8d62a9a5e 100644 --- a/tests/envoy/store_csv_records_test.go +++ b/tests/envoy/store_csv_records_test.go @@ -35,7 +35,7 @@ func TestStoreCsv_records(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) @@ -255,7 +255,7 @@ func TestStoreCsv_records_fieldTypes(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) diff --git a/tests/envoy/store_jsonl_records_test.go b/tests/envoy/store_jsonl_records_test.go index 22348aaca..b9a4a5306 100644 --- a/tests/envoy/store_jsonl_records_test.go +++ b/tests/envoy/store_jsonl_records_test.go @@ -36,7 +36,7 @@ func TestStoreJsonl_records(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) @@ -255,7 +255,7 @@ func TestStoreJsonl_records_fieldTypes(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) diff --git a/tests/envoy/store_yaml_base_test.go b/tests/envoy/store_yaml_base_test.go index 244b20f52..df5b841da 100644 --- a/tests/envoy/store_yaml_base_test.go +++ b/tests/envoy/store_yaml_base_test.go @@ -39,7 +39,7 @@ func TestStoreYaml_base(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) diff --git a/tests/envoy/store_yaml_modules_test.go b/tests/envoy/store_yaml_modules_test.go index 79e7174bf..c8e9f1611 100644 --- a/tests/envoy/store_yaml_modules_test.go +++ b/tests/envoy/store_yaml_modules_test.go @@ -30,7 +30,7 @@ func TestStoreYaml_moduleFieldRefs(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) diff --git a/tests/envoy/store_yaml_pages_test.go b/tests/envoy/store_yaml_pages_test.go index 7633bda0d..ccac655b7 100644 --- a/tests/envoy/store_yaml_pages_test.go +++ b/tests/envoy/store_yaml_pages_test.go @@ -31,7 +31,7 @@ func TestStoreYaml_pageRefs(t *testing.T) { } ) - ctx := auth.SetSuperUserContext(context.Background()) + ctx := context.Background() s := initStore(ctx, t) ni := uint64(10) diff --git a/tests/envoy/system_stamps_test.go b/tests/envoy/system_stamps_test.go index a98bcab25..2b2ec0303 100644 --- a/tests/envoy/system_stamps_test.go +++ b/tests/envoy/system_stamps_test.go @@ -15,7 +15,7 @@ import ( func TestStamps(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) req = require.New(t) diff --git a/tests/envoy/yaml_store_base_test.go b/tests/envoy/yaml_store_base_test.go index 9e397a4c1..5dc424519 100644 --- a/tests/envoy/yaml_store_base_test.go +++ b/tests/envoy/yaml_store_base_test.go @@ -9,7 +9,6 @@ import ( atypes "github.com/cortezaproject/corteza-server/automation/types" ctypes "github.com/cortezaproject/corteza-server/compose/types" - "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" su "github.com/cortezaproject/corteza-server/pkg/envoy/store" "github.com/cortezaproject/corteza-server/pkg/rbac" @@ -35,7 +34,7 @@ func TestYamlStore_base(t *testing.T) { ) var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() namespace = "base" s = initStore(ctx, t) err error diff --git a/tests/envoy/yaml_store_provision_test.go b/tests/envoy/yaml_store_provision_test.go index 966a702fc..5a79c223a 100644 --- a/tests/envoy/yaml_store_provision_test.go +++ b/tests/envoy/yaml_store_provision_test.go @@ -18,7 +18,7 @@ import ( func TestYamlStore_provision(t *testing.T) { var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() s = initStore(ctx, t) ) diff --git a/tests/envoy/yaml_store_relations_test.go b/tests/envoy/yaml_store_relations_test.go index 95d5a8834..a2d20a9d9 100644 --- a/tests/envoy/yaml_store_relations_test.go +++ b/tests/envoy/yaml_store_relations_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/cortezaproject/corteza-server/compose/types" - "github.com/cortezaproject/corteza-server/pkg/auth" su "github.com/cortezaproject/corteza-server/pkg/envoy/store" "github.com/cortezaproject/corteza-server/store" "github.com/stretchr/testify/require" @@ -29,7 +28,7 @@ func TestYamlStore_relations(t *testing.T) { ) var ( - ctx = auth.SetSuperUserContext(context.Background()) + ctx = context.Background() namespace = "relations" s = initStore(ctx, t) err error diff --git a/tests/system/provision_test.go b/tests/system/provision_test.go index 034915673..9e18d2761 100644 --- a/tests/system/provision_test.go +++ b/tests/system/provision_test.go @@ -7,7 +7,6 @@ import ( func TestProvisioning(t *testing.T) { t.SkipNow() //h := newHelper(t) - //ctx := auth.SetSuperUserContext(h.secCtx()) // //readers, err := impAux.ReadStatic(provision.Asset) //h.a.NoError(err)