Skip to content

Commit

Permalink
feat: list project/group permissions over current user (#342)
Browse files Browse the repository at this point in the history
Signed-off-by: Kush Sharma <[email protected]>
  • Loading branch information
kushsharma authored Sep 17, 2023
1 parent 89f7f62 commit 11a2df9
Show file tree
Hide file tree
Showing 47 changed files with 8,962 additions and 6,108 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 := "f243d9955fc7caf78cdb74d32571c043ee4fef2e"

ui:
@echo " > generating ui build"
Expand Down
6 changes: 3 additions & 3 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 All @@ -266,7 +266,7 @@ func buildAPIDependencies(
domainService := domain.NewService(logger, domainRepository, userService, organizationService)

projectRepository := postgres.NewProjectRepository(dbc)
projectService := project.NewService(projectRepository, relationService, userService, policyService, authnService)
projectService := project.NewService(projectRepository, relationService, userService, policyService, authnService, serviceUserService)

metaschemaRepository := postgres.NewMetaSchemaRepository(logger, dbc)
metaschemaService := metaschema.NewService(metaschemaRepository)
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)
}
1 change: 1 addition & 0 deletions core/policy/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ type Filter struct {
PrincipalID string
OrgID string
ProjectID string
GroupID string
RoleID string
}
30 changes: 30 additions & 0 deletions core/policy/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,33 @@ func (s Service) AssignRole(ctx context.Context, pol Policy) error {
}
return nil
}

// ListForUser lists roles assigned via policies to a user
func (s Service) ListForUser(ctx context.Context, userID, objectNamespace, objectID string) ([]role.Role, error) {
flt := Filter{
PrincipalType: schema.UserPrincipal,
PrincipalID: userID,
}
switch objectNamespace {
case schema.OrganizationNamespace:
flt.OrgID = objectID
case schema.ProjectNamespace:
flt.ProjectID = objectID
case schema.GroupNamespace:
flt.GroupID = objectID
}
policies, err := s.List(ctx, flt)
if err != nil {
return nil, err
}

roles := make([]role.Role, 0, len(policies))
for _, pol := range policies {
role, err := s.roleService.Get(ctx, pol.RoleID)
if err != nil {
return nil, err
}
roles = append(roles, role)
}
return roles, nil
}
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
43 changes: 38 additions & 5 deletions core/project/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"errors"
"fmt"

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

"github.com/raystack/frontier/core/authenticate"
"github.com/raystack/frontier/core/policy"
"github.com/raystack/frontier/pkg/utils"
Expand All @@ -28,6 +30,10 @@ type UserService interface {
IsSudo(ctx context.Context, id string) (bool, error)
}

type ServiceuserService interface {
GetByIDs(ctx context.Context, ids []string) ([]serviceuser.ServiceUser, error)
}

type PolicyService interface {
Create(ctx context.Context, policy policy.Policy) (policy.Policy, error)
}
Expand All @@ -40,18 +46,20 @@ type Service struct {
repository Repository
relationService RelationService
userService UserService
suserService ServiceuserService
policyService PolicyService
authnService AuthnService
}

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

Expand All @@ -62,8 +70,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 +106,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 +127,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 Expand Up @@ -168,6 +176,31 @@ func (s Service) ListUsers(ctx context.Context, id string, permissionFilter stri
return s.userService.GetByIDs(ctx, nonSuperUserIDs)
}

func (s Service) ListServiceUsers(ctx context.Context, id string, permissionFilter string) ([]serviceuser.ServiceUser, error) {
requestedProject, err := s.Get(ctx, id)
if err != nil {
return nil, err
}
userIDs, err := s.relationService.LookupSubjects(ctx, relation.Relation{
Object: relation.Object{
ID: requestedProject.ID,
Namespace: schema.ProjectNamespace,
},
Subject: relation.Subject{
Namespace: schema.ServiceUserPrincipal,
},
RelationName: permissionFilter,
})
if err != nil {
return nil, err
}
if len(userIDs) == 0 {
// no users
return []serviceuser.ServiceUser{}, nil
}
return s.suserService.GetByIDs(ctx, userIDs)
}

func (s Service) addProjectToOrg(ctx context.Context, prj Project, orgID string) error {
rel := relation.Relation{
Object: relation.Object{
Expand Down
4 changes: 4 additions & 0 deletions core/serviceuser/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ func (s Service) Get(ctx context.Context, id string) (ServiceUser, error) {
return s.repo.GetByID(ctx, id)
}

func (s Service) GetByIDs(ctx context.Context, ids []string) ([]ServiceUser, error) {
return s.repo.GetByIDs(ctx, ids)
}

func (s Service) ListByOrg(ctx context.Context, orgID string) ([]ServiceUser, error) {
userIDs, err := s.relService.LookupSubjects(ctx, relation.Relation{
Object: relation.Object{
Expand Down
Loading

0 comments on commit 11a2df9

Please sign in to comment.