diff --git a/internal/organization/organization.go b/internal/organization/organization.go new file mode 100644 index 000000000..062953c36 --- /dev/null +++ b/internal/organization/organization.go @@ -0,0 +1,15 @@ +package organization + +import ( + "context" + + "github.com/crusttech/crust/system/types" +) + +func GetFromContext(ctx context.Context) types.Organisation { + if orgID, ok := ctx.Value("organizationID").(uint64); ok { + return types.Organisation{ID: orgID} + } else { + return types.Organisation{ID: 1} + } +} diff --git a/messaging/repository/permissions.go b/messaging/repository/permissions.go deleted file mode 100644 index 02940c6c8..000000000 --- a/messaging/repository/permissions.go +++ /dev/null @@ -1,140 +0,0 @@ -package repository - -import ( - "context" - - "github.com/titpetric/factory" - - "github.com/crusttech/crust/internal/rules" - "github.com/crusttech/crust/messaging/types" - systemTypes "github.com/crusttech/crust/system/types" -) - -type PermissionsRepository interface { - With(context.Context, *factory.DB) PermissionsRepository - - // Applies mostly to admin panel - isAdmin(org *types.Organisation) bool - - // Individual rules for administration - canManageOrganisation(org *types.Organisation) bool - canManageRoles(org *types.Organisation) bool - canManageChannels(org *types.Organisation) bool - - // types.Team derived from identity uses a wildcard match - canManageWebhooks(org *types.Organisation, ch *types.Channel) bool - - // Messaging rules - canSendMessages(org *types.Organisation, ch *types.Channel) bool - canEmbedLinks(org *types.Organisation, ch *types.Channel) bool - canAttachFiles(org *types.Organisation, ch *types.Channel) bool - canEditOwnMessages(org *types.Organisation, ch *types.Channel) bool - canEditMessages(org *types.Organisation, ch *types.Channel) bool - canReact(org *types.Organisation, ch *types.Channel) bool -} - -type permissions struct { - *repository - - // identity is passed with context - resources rules.ResourcesInterface - team *systemTypes.Team -} - -func Permissions(ctx context.Context, db *factory.DB) PermissionsRepository { - return (&permissions{ - team: &systemTypes.Team{}, - }).With(ctx, db) -} - -func (r *permissions) With(ctx context.Context, db *factory.DB) PermissionsRepository { - return &permissions{ - team: r.team, - resources: rules.NewResources(ctx, db), - repository: r.repository.With(ctx, db), - } -} - -var ( - ErrPermissionsNotLoggedIn = repositoryError("PermissionsNotLoggedIn") -) - -// @todo: honor defaults from (org/team/channel).Permissions() - -func (r *permissions) isAdmin(org *types.Organisation) bool { - op := "admin" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String()) -} - -func (r *permissions) canManageOrganisation(org *types.Organisation) bool { - op := "manage.organisation" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String()) -} - -func (r *permissions) canManageRoles(org *types.Organisation) bool { - op := "manage.roles" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String()) -} - -func (r *permissions) canManageChannels(org *types.Organisation) bool { - op := "manage.channels" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String()) -} - -func (r *permissions) canManageWebhooks(org *types.Organisation, ch *types.Channel) bool { - op := "manage.webhooks" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) canSendMessages(org *types.Organisation, ch *types.Channel) bool { - op := "text.send" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) canEmbedLinks(org *types.Organisation, ch *types.Channel) bool { - op := "text.embed" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) canAttachFiles(org *types.Organisation, ch *types.Channel) bool { - op := "text.attach" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) canEditOwnMessages(org *types.Organisation, ch *types.Channel) bool { - op := "text.edit_own" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) canEditMessages(org *types.Organisation, ch *types.Channel) bool { - op := "text.edit_all" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) canReact(org *types.Organisation, ch *types.Channel) bool { - op := "text.react" - return r.hasAccess(op, org.PermissionDefault(op), org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) -} - -func (r *permissions) hasAccess(operation string, value rules.Access, scopes ...string) bool { - // reverse scopes from to order it from most-least significant - // aka: [0]channel [1]teams [2]org - last := len(scopes) - 1 - for i := 0; i < len(scopes)/2; i++ { - scopes[i], scopes[last-i] = scopes[last-i], scopes[i] - } - - for _, scope := range scopes { - if scope == "" { - continue - } - switch r.resources.IsAllowed(scope, operation) { - case rules.Allow: - return true - case rules.Deny: - return false - default: // inherit - } - } - return false -} diff --git a/messaging/repository/repository.go b/messaging/repository/repository.go index 236880d57..ee1de5517 100644 --- a/messaging/repository/repository.go +++ b/messaging/repository/repository.go @@ -6,6 +6,8 @@ import ( "github.com/titpetric/factory" "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/internal/organization" + "github.com/crusttech/crust/messaging/types" ) type ( @@ -24,6 +26,12 @@ func Identity(ctx context.Context) uint64 { return auth.GetIdentityFromContext(ctx).Identity() } +func Organization(ctx context.Context) *types.Organisation { + return &types.Organisation{ + organization.GetFromContext(ctx), + } +} + func (r *repository) With(ctx context.Context, db *factory.DB) *repository { return &repository{ ctx: ctx, diff --git a/messaging/service/permissions.go b/messaging/service/permissions.go index f76bd0ccd..78b333bf5 100644 --- a/messaging/service/permissions.go +++ b/messaging/service/permissions.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" - "github.com/crusttech/crust/internal/rules" + internalRules "github.com/crusttech/crust/internal/rules" "github.com/crusttech/crust/messaging/repository" ) @@ -14,7 +14,7 @@ type ( db db ctx context.Context - scopes rules.ScopeInterface + scopes internalRules.ScopeInterface } PermissionsService interface { @@ -22,11 +22,11 @@ type ( List() (interface{}, error) Get(teamID uint64, scope string, resource string) (interface{}, error) - Set(teamID uint64, rules []rules.Rules) (interface{}, error) + Set(teamID uint64, rules []internalRules.Rules) (interface{}, error) } ) -func Permissions(scopes rules.ScopeInterface) PermissionsService { +func Permissions(scopes internalRules.ScopeInterface) PermissionsService { return (&permissions{ scopes: scopes, }).With(context.Background()) @@ -49,6 +49,6 @@ func (p *permissions) Get(teamID uint64, scope string, resource string) (interfa return nil, errors.New("service.permissions.get: not implemented") } -func (p *permissions) Set(teamID uint64, rules []rules.Rules) (interface{}, error) { +func (p *permissions) Set(teamID uint64, rules []internalRules.Rules) (interface{}, error) { return nil, errors.New("service.permissions.set: not implemented") } diff --git a/messaging/service/rules.go b/messaging/service/rules.go new file mode 100644 index 000000000..c5f8e4a9a --- /dev/null +++ b/messaging/service/rules.go @@ -0,0 +1,144 @@ +package service + +import ( + "context" + + internalRules "github.com/crusttech/crust/internal/rules" + "github.com/crusttech/crust/messaging/repository" + "github.com/crusttech/crust/messaging/types" + systemTypes "github.com/crusttech/crust/system/types" +) + +type ( + rules struct { + db db + ctx context.Context + + // identity is passed with context + resources internalRules.ResourcesInterface + team *systemTypes.Team + org *types.Organisation + } + + RulesService interface { + With(context.Context) RulesService + + // Applies mostly to admin panel + isAdmin() bool + + // Individual rules for administration + canManageOrganisation() bool + canManageRoles() bool + canManageChannels() bool + + // types.Team derived from identity uses a wildcard match + canManageWebhooks(ch *types.Channel) bool + + // Messaging rules + canSendMessages(ch *types.Channel) bool + canEmbedLinks(ch *types.Channel) bool + canAttachFiles(ch *types.Channel) bool + canUpdateOwnMessages(ch *types.Channel) bool + canUpdateMessages(ch *types.Channel) bool + canReact(ch *types.Channel) bool + } +) + +func Rules() RulesService { + return (&rules{ + team: &systemTypes.Team{}, + }).With(context.Background()) +} + +func (r *rules) With(ctx context.Context) RulesService { + db := repository.DB(ctx) + org := repository.Organization(ctx) + return &rules{ + db: db, + ctx: ctx, + org: org, + team: r.team, + + resources: internalRules.NewResources(ctx, db), + } +} + +// @todo: honor defaults from (org/team/channel).Permissions() + +func (r *rules) isAdmin() bool { + op := "admin" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String()) +} + +func (r *rules) canManageOrganisation() bool { + op := "manage.organisation" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String()) +} + +func (r *rules) canManageRoles() bool { + op := "manage.roles" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String()) +} + +func (r *rules) canManageChannels() bool { + op := "manage.channels" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String()) +} + +func (r *rules) canManageWebhooks(ch *types.Channel) bool { + op := "manage.webhooks" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) canSendMessages(ch *types.Channel) bool { + op := "message.send" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) canEmbedLinks(ch *types.Channel) bool { + op := "message.embed" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) canAttachFiles(ch *types.Channel) bool { + op := "message.attach" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) canUpdateOwnMessages(ch *types.Channel) bool { + op := "message.update_own" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) canUpdateMessages(ch *types.Channel) bool { + op := "message.update_all" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) canReact(ch *types.Channel) bool { + op := "message.react" + return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String()) +} + +func (r *rules) hasAccess(operation string, value internalRules.Access, scopes ...string) bool { + // reverse scopes from to order it from most-least significant + // aka: [0]channel [1]teams [2]org + last := len(scopes) - 1 + for i := 0; i < len(scopes)/2; i++ { + scopes[i], scopes[last-i] = scopes[last-i], scopes[i] + } + + for _, scope := range scopes { + if scope == "" { + continue + } + switch r.resources.IsAllowed(scope, operation) { + case internalRules.Allow: + return true + case internalRules.Deny: + return false + default: // inherit + } + } + return false +} diff --git a/messaging/service/service.go b/messaging/service/service.go index 17f935339..521f72589 100644 --- a/messaging/service/service.go +++ b/messaging/service/service.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/crusttech/crust/internal/rules" + internalRules "github.com/crusttech/crust/internal/rules" "github.com/crusttech/crust/internal/store" "github.com/crusttech/crust/messaging/types" @@ -34,7 +34,7 @@ func Init() { log.Fatalf("Failed to initialize stor: %v", err) } - scopes := rules.NewScope() + scopes := internalRules.NewScope() scopes.Add(&types.Organisation{}) scopes.Add(&types.Team{}) scopes.Add(&types.Channel{})