Skip to content

Commit

Permalink
feat: list project/group permissions over current user
Browse files Browse the repository at this point in the history
Signed-off-by: Kush Sharma <[email protected]>
  • Loading branch information
kushsharma committed Sep 16, 2023
1 parent 89f7f62 commit 261b4dd
Show file tree
Hide file tree
Showing 37 changed files with 6,705 additions and 5,675 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
VERSION := $(shell git describe --tags ${TAG})
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto ui
.DEFAULT_GOAL := build
PROTON_COMMIT := "92eacbcf84c2f82f5e605845bb21948fe1b05bdd"
PROTON_COMMIT := "f8d2c72515a5d10205a493b75df9374e0db9c524"

ui:
@echo " > generating ui build"
Expand Down
4 changes: 2 additions & 2 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func buildAPIDependencies(
policyService := policy.NewService(policyPGRepository, relationService, roleService)

userRepository := postgres.NewUserRepository(dbc)
userService := user.NewService(userRepository, relationService, permissionRepository)
userService := user.NewService(userRepository, relationService)

svUserRepo := postgres.NewServiceUserRepository(dbc)
scUserCredRepo := postgres.NewServiceUserCredentialRepository(dbc)
Expand All @@ -246,7 +246,7 @@ func buildAPIDependencies(
postgres.NewFlowRepository(logger, dbc), mailDialer, tokenService, sessionService, userService, serviceUserService)

groupRepository := postgres.NewGroupRepository(dbc)
groupService := group.NewService(groupRepository, relationService, authnService)
groupService := group.NewService(groupRepository, relationService, authnService, policyService)

resourceSchemaRepository := blob.NewSchemaConfigRepository(resourceBlobRepository.Bucket)
bootstrapService := bootstrap.NewBootstrapService(
Expand Down
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"errors"
"fmt"
"os"
"path/filepath"

Expand Down Expand Up @@ -63,6 +64,9 @@ func Load(serverConfigFileFromFlag string) (*Frontier, error) {

// backward compatibility
conf = postHook(conf)
if conf.App.IdentityProxyHeader != "" {
fmt.Println("WARNING: running in development mode, bypassing all authorization checks")
}

return conf, nil
}
Expand Down
79 changes: 68 additions & 11 deletions core/group/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"strings"

"github.com/raystack/frontier/core/policy"

"github.com/raystack/frontier/core/authenticate"

"github.com/raystack/frontier/internal/bootstrap/schema"
Expand All @@ -25,18 +27,24 @@ type AuthnService interface {
GetPrincipal(ctx context.Context, via ...authenticate.ClientAssertion) (authenticate.Principal, error)
}

type PolicyService interface {
Create(ctx context.Context, policy policy.Policy) (policy.Policy, error)
}

type Service struct {
repository Repository
relationService RelationService
authnService AuthnService
policyService PolicyService
}

func NewService(repository Repository, relationService RelationService,
authnService AuthnService) *Service {
authnService AuthnService, policyService PolicyService) *Service {
return &Service{
repository: repository,
relationService: relationService,
authnService: authnService,
policyService: policyService,
}
}

Expand All @@ -55,14 +63,13 @@ func (s Service) Create(ctx context.Context, grp Group) (Group, error) {
if err = s.addAsOrgMember(ctx, newGroup); err != nil {
return Group{}, err
}

// attach current user to group as owner
if err = s.AddMember(ctx, newGroup.ID, schema.OwnerRelationName, principal); err != nil {
// add relationship between group to org
if err = s.addOrgToGroup(ctx, newGroup); err != nil {
return Group{}, err
}

// add relationship between group to org
if err = s.addOrgToGroup(ctx, newGroup); err != nil {
// attach current user to group as owner
if err = s.addOwner(ctx, newGroup.ID, principal); err != nil {
return Group{}, err
}

Expand Down Expand Up @@ -106,7 +113,13 @@ func (s Service) ListByUser(ctx context.Context, userId string, flt Filter) ([]G
}

// AddMember adds a subject(user) to group as member
func (s Service) AddMember(ctx context.Context, groupID, relationName string, principal authenticate.Principal) error {
func (s Service) AddMember(ctx context.Context, groupID string, principal authenticate.Principal) error {
// first create a policy for the user as member of the group
if err := s.addMemberPolicy(ctx, groupID, principal); err != nil {
return err
}

// then create a relation between group and user as member
rel := relation.Relation{
Object: relation.Object{
ID: groupID,
Expand All @@ -116,14 +129,59 @@ func (s Service) AddMember(ctx context.Context, groupID, relationName string, pr
ID: principal.ID,
Namespace: principal.Type,
},
RelationName: relationName,
RelationName: schema.MemberRelationName,
}
if _, err := s.relationService.Create(ctx, rel); err != nil {
return err
}
return nil
}

// addOwner adds a user as an owner of group by creating a policy of owner role and an owner relation
func (s Service) addOwner(ctx context.Context, groupID string, principal authenticate.Principal) error {
pol := policy.Policy{
RoleID: schema.GroupOwnerRole,
ResourceID: groupID,
ResourceType: schema.GroupNamespace,
PrincipalID: principal.ID,
PrincipalType: principal.Type,
}
if _, err := s.policyService.Create(ctx, pol); err != nil {
return err
}
// then create a relation between group and user
rel := relation.Relation{
Object: relation.Object{
ID: groupID,
Namespace: schema.GroupNamespace,
},
Subject: relation.Subject{
ID: principal.ID,
Namespace: principal.Type,
},
RelationName: schema.OwnerRelationName,
}
if _, err := s.relationService.Create(ctx, rel); err != nil {
return err
}
return nil
}

// add a policy to user as member of group
func (s Service) addMemberPolicy(ctx context.Context, groupID string, principal authenticate.Principal) error {
pol := policy.Policy{
RoleID: schema.GroupMemberRole,
ResourceID: groupID,
ResourceType: schema.GroupNamespace,
PrincipalID: principal.ID,
PrincipalType: principal.Type,
}
if _, err := s.policyService.Create(ctx, pol); err != nil {
return err
}
return nil
}

// addOrgToGroup creates an inverse relation that connects group to org
func (s Service) addOrgToGroup(ctx context.Context, team Group) error {
rel := relation.Relation{
Expand Down Expand Up @@ -200,11 +258,10 @@ func (s Service) ListByOrganization(ctx context.Context, id string) ([]Group, er
func (s Service) AddUsers(ctx context.Context, groupID string, userIDs []string) error {
var err error
for _, userID := range userIDs {
currentErr := s.AddMember(ctx, groupID, schema.MemberRelationName, authenticate.Principal{
if currentErr := s.AddMember(ctx, groupID, authenticate.Principal{
ID: userID,
Type: schema.UserPrincipal,
})
if currentErr != nil {
}); currentErr != nil {
err = errors.Join(err, currentErr)
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/invitation/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type OrganizationService interface {

type GroupService interface {
Get(ctx context.Context, id string) (group.Group, error)
AddMember(ctx context.Context, groupID, relationName string, principal authenticate.Principal) error
AddMember(ctx context.Context, groupID string, principal authenticate.Principal) error
ListByUser(ctx context.Context, userID string, flt group.Filter) ([]group.Group, error)
}

Expand Down Expand Up @@ -251,7 +251,7 @@ func (s Service) Accept(ctx context.Context, id uuid.UUID) error {
}
}
if !alreadyGroupMember {
if err = s.groupSvc.AddMember(ctx, grp.ID, schema.MemberRelationName, authenticate.Principal{
if err = s.groupSvc.AddMember(ctx, grp.ID, authenticate.Principal{
ID: user.ID,
Type: schema.UserPrincipal,
}); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion core/permission/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import "errors"
var (
ErrInvalidID = errors.New("permission id is invalid")
ErrNotExist = errors.New("permission doesn't exist")
ErrInvalidDetail = errors.New("invalid action detail")
ErrInvalidDetail = errors.New("invalid permission detail")
)
7 changes: 7 additions & 0 deletions core/permission/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ func convertDotPermissionToSlug(s string) string {
}
return s
}

func AddNamespaceIfRequired(namespace string, name string) string {
if strings.Contains(name, ".") || strings.Contains(name, "_") || strings.Contains(name, "/") {
return name
}
return fmt.Sprintf("%s:%s", namespace, name)
}
2 changes: 1 addition & 1 deletion core/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var OwnerRole = schema.RoleProjectOwner

type Repository interface {
GetByID(ctx context.Context, id string) (Project, error)
GetByIDs(ctx context.Context, ids []string) ([]Project, error)
GetByIDs(ctx context.Context, ids []string, flt Filter) ([]Project, error)
GetByName(ctx context.Context, slug string) (Project, error)
Create(ctx context.Context, org Project) (Project, error)
List(ctx context.Context, f Filter) ([]Project, error)
Expand Down
8 changes: 4 additions & 4 deletions core/project/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ func (s Service) Get(ctx context.Context, idOrName string) (Project, error) {
return s.repository.GetByName(ctx, idOrName)
}

func (s Service) GetByIDs(ctx context.Context, ids []string) ([]Project, error) {
return s.repository.GetByIDs(ctx, ids)
func (s Service) GetByIDs(ctx context.Context, ids []string, flt Filter) ([]Project, error) {
return s.repository.GetByIDs(ctx, ids, flt)
}

func (s Service) Create(ctx context.Context, prj Project) (Project, error) {
Expand Down Expand Up @@ -98,7 +98,7 @@ func (s Service) List(ctx context.Context, f Filter) ([]Project, error) {
return s.repository.List(ctx, f)
}

func (s Service) ListByUser(ctx context.Context, userID string) ([]Project, error) {
func (s Service) ListByUser(ctx context.Context, userID string, flt Filter) ([]Project, error) {
requestedUser, err := s.userService.GetByID(ctx, userID)
if err != nil {
return nil, err
Expand All @@ -119,7 +119,7 @@ func (s Service) ListByUser(ctx context.Context, userID string) ([]Project, erro
if len(projIDs) == 0 {
return []Project{}, nil
}
return s.GetByIDs(ctx, projIDs)
return s.GetByIDs(ctx, projIDs, flt)
}

func (s Service) Update(ctx context.Context, prj Project) (Project, error) {
Expand Down
85 changes: 4 additions & 81 deletions core/user/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ package user

import (
"context"
"fmt"
"net/mail"
"strings"
"time"

"github.com/raystack/frontier/core/permission"

"github.com/raystack/frontier/pkg/utils"

"github.com/raystack/frontier/core/relation"
Expand All @@ -23,29 +20,21 @@ type RelationService interface {
Delete(ctx context.Context, rel relation.Relation) error
LookupSubjects(ctx context.Context, rel relation.Relation) ([]string, error)
LookupResources(ctx context.Context, rel relation.Relation) ([]string, error)
BatchCheckPermission(ctx context.Context, relations []relation.Relation) ([]relation.CheckPair, error)
}

type PermissionService interface {
List(ctx context.Context, flt permission.Filter) ([]permission.Permission, error)
}

type Service struct {
repository Repository
relationService RelationService
Now func() time.Time
permissionService PermissionService
repository Repository
relationService RelationService
Now func() time.Time
}

func NewService(repository Repository, relationRepo RelationService,
permissionService PermissionService) *Service {
func NewService(repository Repository, relationRepo RelationService) *Service {
return &Service{
repository: repository,
relationService: relationRepo,
Now: func() time.Time {
return time.Now().UTC()
},
permissionService: permissionService,
}
}

Expand Down Expand Up @@ -172,72 +161,6 @@ func (s Service) ListByGroup(ctx context.Context, groupID string, permissionFilt
return s.repository.GetByIDs(ctx, userIDs)
}

func (s Service) ListByGroupWithAccessPairs(ctx context.Context, groupID string, permissions []string) ([]AccessPair, error) {
users, err := s.ListByGroup(ctx, groupID, schema.MembershipPermission)
if err != nil {
return nil, fmt.Errorf("fetching users: %w", err)
}

// ensure all permissions exist
permModels, err := s.permissionService.List(ctx, permission.Filter{Namespace: schema.GroupNamespace})
if err != nil {
return nil, fmt.Errorf("fetching permission: %w", err)
}
for _, perm := range permissions {
if !utils.ContainsFunc(permModels, func(p permission.Permission) bool {
return p.Name == perm
}) {
return nil, fmt.Errorf("invalid %s: %w", perm, permission.ErrNotExist)
}
}

var accessPairs []AccessPair
var checks []relation.Relation
// fetch access pairs permissions
for _, user := range users {
relSubject := relation.Subject{
ID: user.ID,
Namespace: schema.UserPrincipal,
}
relObj := relation.Object{
ID: groupID,
Namespace: schema.GroupNamespace,
}
for _, permission := range permissions {
checks = append(checks, relation.Relation{
Subject: relSubject,
Object: relObj,
RelationName: permission,
})
}

accessPairs = append(accessPairs, AccessPair{
User: user,
Can: []string{},
On: schema.JoinNamespaceAndResourceID(schema.GroupNamespace, groupID),
})
}

// create permission check requests
checkPairs, err := s.relationService.BatchCheckPermission(ctx, checks)
if err != nil {
return nil, fmt.Errorf("checking permissions: %w", err)
}
successChecks := utils.Filter(checkPairs, func(pair relation.CheckPair) bool {
return pair.Status
})

for _, checkPair := range successChecks {
accessPairs = utils.Map(accessPairs, func(pair AccessPair) AccessPair {
if pair.User.ID == checkPair.Relation.Subject.ID {
pair.Can = append(pair.Can, checkPair.Relation.RelationName)
}
return pair
})
}
return accessPairs, nil
}

func (s Service) Sudo(ctx context.Context, id string) error {
currentUser, err := s.GetByID(ctx, id)
if errors.Is(err, ErrNotExist) {
Expand Down
Loading

0 comments on commit 261b4dd

Please sign in to comment.