Skip to content

Commit

Permalink
feat: list group users with their permission capabilities
Browse files Browse the repository at this point in the history
Signed-off-by: Kush Sharma <[email protected]>
  • Loading branch information
kushsharma committed Sep 10, 2023
1 parent ee692f0 commit ea16be2
Show file tree
Hide file tree
Showing 24 changed files with 3,733 additions and 3,247 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 := "a58081b1091c519b88630de5e9c6bffba93a5201"
PROTON_COMMIT := "1995346f92df2cd70bcf7cd8d46708730564fbe2"

ui:
@echo " > generating ui build"
Expand Down
8 changes: 4 additions & 4 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ func buildAPIDependencies(
authzSchemaRepository := spicedb.NewSchemaRepository(logger, sdb)
authzRelationRepository := spicedb.NewRelationRepository(sdb, cfg.SpiceDB.FullyConsistent)

relationPGRepository := postgres.NewRelationRepository(dbc)
relationService := relation.NewService(relationPGRepository, authzRelationRepository)

permissionRepository := postgres.NewPermissionRepository(dbc)
permissionService := permission.NewService(permissionRepository)

relationPGRepository := postgres.NewRelationRepository(dbc)
relationService := relation.NewService(relationPGRepository, authzRelationRepository, permissionRepository)

roleRepository := postgres.NewRoleRepository(dbc)
roleService := role.NewService(roleRepository, relationService, permissionService)

Expand Down Expand Up @@ -246,7 +246,7 @@ func buildAPIDependencies(
postgres.NewFlowRepository(logger, dbc), mailDialer, tokenService, sessionService, userService, serviceUserService)

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

resourceSchemaRepository := blob.NewSchemaConfigRepository(resourceBlobRepository.Bucket)
bootstrapService := bootstrap.NewBootstrapService(
Expand Down
7 changes: 7 additions & 0 deletions core/authenticate/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package authenticate

import "errors"

var (
ErrInvalidID = errors.New("user id is invalid")
)
4 changes: 2 additions & 2 deletions core/group/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ func (s State) String() string {
const (
Enabled State = "enabled"
Disabled State = "disabled"
)

var MemberPermission = schema.MembershipPermission
MemberPermission = schema.MembershipPermission
)

type Repository interface {
Create(ctx context.Context, grp Group) (Group, error)
Expand Down
12 changes: 2 additions & 10 deletions core/group/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/raystack/frontier/internal/bootstrap/schema"

"github.com/raystack/frontier/core/relation"
"github.com/raystack/frontier/core/user"
)

type RelationService interface {
Expand All @@ -22,36 +21,29 @@ type RelationService interface {
Delete(ctx context.Context, rel relation.Relation) error
}

type UserService interface {
GetByID(ctx context.Context, id string) (user.User, error)
GetByIDs(ctx context.Context, userIDs []string) ([]user.User, error)
}

type AuthnService interface {
GetPrincipal(ctx context.Context, via ...authenticate.ClientAssertion) (authenticate.Principal, error)
}

type Service struct {
repository Repository
relationService RelationService
userService UserService
authnService AuthnService
}

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

func (s Service) Create(ctx context.Context, grp Group) (Group, error) {
principal, err := s.authnService.GetPrincipal(ctx)
if err != nil {
return Group{}, fmt.Errorf("%w: %s", user.ErrInvalidEmail, err.Error())
return Group{}, fmt.Errorf("%w: %s", authenticate.ErrInvalidID, err.Error())
}

newGroup, err := s.repository.Create(ctx, grp)
Expand Down
4 changes: 2 additions & 2 deletions core/permission/filter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package permission

type Filter struct {
NamespaceID string
Slugs []string
Namespace string
Slugs []string
}
38 changes: 33 additions & 5 deletions core/relation/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@ import (
"errors"
"fmt"
"regexp"

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

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

type Service struct {
repository Repository
authzRepository AuthzRepository
repository Repository
authzRepository AuthzRepository
permissionService PermissionService
}

func NewService(repository Repository, authzRepository AuthzRepository) *Service {
func NewService(repository Repository, authzRepository AuthzRepository,
permissionService PermissionService) *Service {
return &Service{
repository: repository,
authzRepository: authzRepository,
repository: repository,
authzRepository: authzRepository,
permissionService: permissionService,
}
}

Expand Down Expand Up @@ -95,3 +104,22 @@ func isValidID(id string) bool {
idRegex := regexp.MustCompile("^(([a-zA-Z0-9_][a-zA-Z0-9/_|-]{0,127})|\\*)$")
return idRegex.MatchString(id)
}

func (s Service) ExhaustPermissionCheck(ctx context.Context, subject Subject, object Object) ([]CheckPair, error) {
permissions, err := s.permissionService.List(ctx, permission.Filter{
Namespace: object.Namespace,
})
if err != nil {
return nil, fmt.Errorf("fetching permissions: %w", err)
}

var relations []Relation
for _, perm := range permissions {
relations = append(relations, Relation{
Object: object,
Subject: subject,
RelationName: perm.Name,
})
}
return s.BatchCheckPermission(ctx, relations)
}
7 changes: 7 additions & 0 deletions core/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"time"

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

"github.com/raystack/frontier/pkg/metadata"
)

Expand Down Expand Up @@ -46,3 +48,8 @@ type YAML struct {
ResourceType string `json:"resource_type" yaml:"resource_type"`
Actions map[string][]string `json:"actions" yaml:"actions"`
}

type Check struct {
Object relation.Object
Permission string
}
24 changes: 11 additions & 13 deletions core/resource/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,27 +161,23 @@ func (s Service) AddResourceOwner(ctx context.Context, res Resource) error {
return nil
}

type Check struct {
Object relation.Object
Permission string
}

func (s Service) CheckAuthz(ctx context.Context, check Check) (bool, error) {
principal, err := s.authnService.GetPrincipal(ctx)
if err != nil {
return false, err
}
relSubject := relation.Subject{
ID: principal.ID,
Namespace: principal.Type,
}

relObject, err := s.buildRelationObject(ctx, check.Object)
if err != nil {
return false, err
}

return s.relationService.CheckPermission(ctx, relation.Relation{
Subject: relation.Subject{
ID: principal.ID,
Namespace: principal.Type,
},
Subject: relSubject,
Object: relObject,
RelationName: check.Permission,
})
Expand Down Expand Up @@ -232,11 +228,13 @@ func (s Service) BatchCheck(ctx context.Context, checks []Check) ([]relation.Che
if err != nil {
return nil, err
}

relSubject := relation.Subject{
ID: principal.ID,
Namespace: principal.Type,
}
relations = append(relations, relation.Relation{
Subject: relation.Subject{
ID: principal.ID,
Namespace: principal.Type,
},
Subject: relSubject,
Object: relObject,
RelationName: check.Permission,
})
Expand Down
49 changes: 49 additions & 0 deletions core/user/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package user

import (
"context"
"fmt"
"net/mail"
"strings"
"time"
Expand All @@ -20,6 +21,7 @@ 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)
ExhaustPermissionCheck(ctx context.Context, subject relation.Subject, object relation.Object) ([]relation.CheckPair, error)
}

type Service struct {
Expand Down Expand Up @@ -157,9 +159,56 @@ func (s Service) ListByGroup(ctx context.Context, groupID string, permissionFilt
// no users
return []User{}, nil
}

return s.repository.GetByIDs(ctx, userIDs)
}

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

var accessPairs []AccessPair
// 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,
}
// create permission check requests
status, err := s.relationService.ExhaustPermissionCheck(ctx, relSubject, relObj)
if err != nil {
// for now, we are choosing to ignore the error to not fail if we try to fetch permissions for
// a resource that doesn't exist
// return nil, fmt.Errorf("fetching user permissions: %w", err)
}

var capabilities []string
if len(status) > 0 {
capabilities = utils.Map(utils.Filter(status, func(pair relation.CheckPair) bool {
return pair.Status
}), func(pair relation.CheckPair) string {
if !pair.Status {
return ""
}
return pair.Relation.RelationName
})
}
accessPairs = append(accessPairs, AccessPair{
User: user,
Can: capabilities,
On: schema.JoinNamespaceAndResourceID(schema.GroupNamespace, groupID),
})
}

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
7 changes: 4 additions & 3 deletions core/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ type User struct {
UpdatedAt time.Time
}

type PagedUsers struct {
Count int32
Users []User
type AccessPair struct {
User User
On string
Can []string
}
Loading

0 comments on commit ea16be2

Please sign in to comment.