diff --git a/system/internal/repository/role.go b/system/internal/repository/role.go index 904c7ae1b..c0b545df8 100644 --- a/system/internal/repository/role.go +++ b/system/internal/repository/role.go @@ -163,6 +163,7 @@ func (r *role) Reset() error { // Value: Allow (2), Deny (1), Inherit(0) sql = `REPLACE INTO sys_rules (rel_role, resource, operation, value) VALUES -- Everyone + (1, 'system', 'user.create', 2), (1, 'compose:*', 'access', 2), (1, 'messaging:*', 'access', 2), -- Admins @@ -188,8 +189,14 @@ func (r *role) Reset() error { (2, 'system', 'access', 2), (2, 'system', 'grant', 2), (2, 'system', 'organisation.create', 2), + (2, 'system', 'user.create', 2), (2, 'system', 'role.create', 2), (2, 'system:organisation:*', 'access', 2), + (2, 'system:user:*', 'read', 2), + (2, 'system:user:*', 'update', 2), + (2, 'system:user:*', 'suspend', 2), + (2, 'system:user:*', 'unsuspend', 2), + (2, 'system:user:*', 'delete', 2), (2, 'system:role:*', 'read', 2), (2, 'system:role:*', 'update', 2), (2, 'system:role:*', 'delete', 2), diff --git a/system/internal/service/permissions.go b/system/internal/service/permissions.go index 8edbfe9a2..dc9c732e7 100644 --- a/system/internal/service/permissions.go +++ b/system/internal/service/permissions.go @@ -26,6 +26,7 @@ type ( Effective() (ee []effectivePermission, err error) CanCreateOrganisation() bool + CanCreateUser() bool CanCreateRole() bool CanCreateApplication() bool @@ -37,6 +38,11 @@ type ( CanReadApplication(app *types.Application) bool CanUpdateApplication(app *types.Application) bool CanDeleteApplication(app *types.Application) bool + + CanUpdateUser(u *types.User) bool + CanSuspendUser(u *types.User) bool + CanUnsuspendUser(u *types.User) bool + CanDeleteUser(u *types.User) bool } effectivePermission struct { @@ -88,6 +94,10 @@ func (p *permissions) CanCreateOrganisation() bool { return p.checkAccess(types.PermissionResource, "organisation.create") } +func (p *permissions) CanCreateUser() bool { + return p.checkAccess(types.PermissionResource, "user.create", p.allow()) +} + func (p *permissions) CanCreateRole() bool { return p.checkAccess(types.PermissionResource, "role.create") } @@ -124,6 +134,22 @@ func (p *permissions) CanDeleteApplication(app *types.Application) bool { return p.checkAccess(app, "delete") } +func (p *permissions) CanUpdateUser(u *types.User) bool { + return p.checkAccess(u, "update") +} + +func (p *permissions) CanSuspendUser(u *types.User) bool { + return p.checkAccess(u, "suspend") +} + +func (p *permissions) CanUnsuspendUser(u *types.User) bool { + return p.checkAccess(u, "unsuspend") +} + +func (p *permissions) CanDeleteUser(u *types.User) bool { + return p.checkAccess(u, "delete") +} + func (p *permissions) checkAccess(r resource, operation string, fallbacks ...internalRules.CheckAccessFunc) bool { return p.rules.Check(r.PermissionResource(), operation, fallbacks...) == internalRules.Allow } diff --git a/system/internal/service/user.go b/system/internal/service/user.go index cb1c536e9..d04a48b7c 100644 --- a/system/internal/service/user.go +++ b/system/internal/service/user.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" + internalAuth "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/system/internal/repository" "github.com/crusttech/crust/system/types" ) @@ -22,6 +23,8 @@ type ( db *factory.DB ctx context.Context + prm PermissionsService + user repository.UserRepository } @@ -57,20 +60,54 @@ func (svc *user) With(ctx context.Context) UserService { return &user{ db: db, ctx: ctx, + prm: DefaultPermissions, user: repository.User(ctx, db), } } func (svc *user) Delete(id uint64) error { - return svc.user.DeleteByID(id) + return svc.db.Transaction(func() (err error) { + var u *types.User + if u, err = svc.user.FindByID(id); err != nil { + return + } + + if !svc.prm.CanDeleteUser(u) { + return errors.New("not allowed to update this user") + } + + return svc.user.DeleteByID(id) + }) } func (svc *user) Suspend(id uint64) error { - return svc.user.SuspendByID(id) + return svc.db.Transaction(func() (err error) { + var u *types.User + if u, err = svc.user.FindByID(id); err != nil { + return + } + + if !svc.prm.CanSuspendUser(u) { + return errors.New("not allowed to update this user") + } + + return svc.user.SuspendByID(id) + }) } func (svc *user) Unsuspend(id uint64) error { - return svc.user.UnsuspendByID(id) + return svc.db.Transaction(func() (err error) { + var u *types.User + if u, err = svc.user.FindByID(id); err != nil { + return + } + + if !svc.prm.CanUnsuspendUser(u) { + return errors.New("not allowed to update this user") + } + + return svc.user.UnsuspendByID(id) + }) } func (svc *user) ValidateCredentials(username, password string) (*types.User, error) { @@ -121,6 +158,7 @@ func (svc *user) FindOrCreate(user *types.User) (out *types.User, err error) { out, err = svc.user.FindBySatosaID(user.SatosaID) if err == repository.ErrUserNotFound { + // @todo do we allow autocreation of nonexisting users? out, err = svc.user.Create(user) return err } @@ -130,7 +168,6 @@ func (svc *user) FindOrCreate(user *types.User) (out *types.User, err error) { return err } - // @todo need to be more selective with fields we update... out, err = svc.user.Update(out) if err != nil { return err @@ -142,10 +179,14 @@ func (svc *user) FindOrCreate(user *types.User) (out *types.User, err error) { func (svc *user) Create(input *types.User) (out *types.User, err error) { return out, svc.db.Transaction(func() error { - // Encrypt user password if out, err = svc.user.Create(input); err != nil { return err } + + if !svc.prm.CanCreateUser() { + return errors.New("not allowed to create users") + } + return nil }) } @@ -156,6 +197,10 @@ func (svc *user) Update(mod *types.User) (u *types.User, err error) { return } + if mod.ID != internalAuth.GetIdentityFromContext(svc.ctx).Identity() && !svc.prm.CanUpdateUser(u) { + return errors.New("not allowed to update this user") + } + // Assign changed values u.Email = mod.Email u.Username = mod.Username diff --git a/system/types/permission_resources.go b/system/types/permission_resources.go index eda214b38..c4cc08131 100644 --- a/system/types/permission_resources.go +++ b/system/types/permission_resources.go @@ -7,4 +7,5 @@ import ( const PermissionResource = rules.Resource("system") const ApplicationPermissionResource = rules.Resource("system:application:") const OrganisationPermissionResource = rules.Resource("system:organisation:") +const UserPermissionResource = rules.Resource("system:user:") const RolePermissionResource = rules.Resource("system:role:") diff --git a/system/types/user.go b/system/types/user.go index 4daf2ca5b..6f1d84321 100644 --- a/system/types/user.go +++ b/system/types/user.go @@ -5,6 +5,8 @@ import ( "github.com/jmoiron/sqlx/types" "golang.org/x/crypto/bcrypt" + + "github.com/crusttech/crust/internal/rules" ) type ( @@ -61,3 +63,8 @@ func (u *User) GeneratePassword(password string) error { u.Password = pwd return nil } + +// Resource returns a resource ID for this type +func (u *User) PermissionResource() rules.Resource { + return UserPermissionResource.AppendID(u.ID) +}