From 5dc98ed5f704231823c06e688174e8fc9057a47f Mon Sep 17 00:00:00 2001 From: Khor Shu Heng Date: Wed, 26 Jul 2023 13:19:32 +0800 Subject: [PATCH] fix: use role template strings for predefined roles Signed-off-by: Khor Shu Heng --- api/cmd/bootstrap.go | 2 +- api/pkg/authz/enforcer/role.go | 46 ++++++++++++++- api/pkg/authz/enforcer/role_test.go | 89 +++++++++++++++++++++++++++++ api/service/projects_service.go | 59 +++++++++---------- 4 files changed, 164 insertions(+), 32 deletions(-) create mode 100644 api/pkg/authz/enforcer/role_test.go diff --git a/api/cmd/bootstrap.go b/api/cmd/bootstrap.go index 4f6264f0..05818daa 100644 --- a/api/cmd/bootstrap.go +++ b/api/cmd/bootstrap.go @@ -71,7 +71,7 @@ func startKetoBootstrap(globalCfg *config.Config, bootstrapOpts *BootstrapRoleMe return err } updateRequest := enforcer.NewAuthorizationUpdateRequest() - updateRequest.SetRoleMembers(enforcer.ProjectReaderRole, bootstrapOpts.ProjectReaders) + updateRequest.SetRoleMembers(enforcer.MLPProjectsReaderRole, bootstrapOpts.ProjectReaders) updateRequest.SetRoleMembers(enforcer.MLPAdminRole, bootstrapOpts.MLPAdmins) err = authEnforcer.UpdateAuthorization(context.Background(), updateRequest) if err != nil { diff --git a/api/pkg/authz/enforcer/role.go b/api/pkg/authz/enforcer/role.go index 065dda78..2b02ae54 100644 --- a/api/pkg/authz/enforcer/role.go +++ b/api/pkg/authz/enforcer/role.go @@ -1,6 +1,48 @@ package enforcer +import ( + "bytes" + "text/template" + + "github.com/caraml-dev/mlp/api/models" +) + const ( - MLPAdminRole = "mlp.administrator" - ProjectReaderRole = "mlp.projects.reader" + MLPAdminRole = "mlp.administrator" + MLPProjectsReaderRole = "mlp.projects.reader" + MLPProjectReaderRole = "mlp.projects.{{ .ProjectId }}.reader" + MLPProjectAdminRole = "mlp.projects.{{ .ProjectId }}.administrator" ) + +func ParseRole(role string, templateContext map[string]string) (string, error) { + roleParser, err := template.New("role").Parse(role) + if err != nil { + return "", err + } + var parseResultBytes bytes.Buffer + err = roleParser.Execute(&parseResultBytes, templateContext) + if err != nil { + return "", err + } + return parseResultBytes.String(), nil +} + +func ParseProjectRole(roleTemplateString string, project *models.Project) (string, error) { + parsedRole, err := ParseRole(roleTemplateString, map[string]string{"ProjectId": project.ID.String()}) + if err != nil { + return "", err + } + return parsedRole, nil +} + +func ParseProjectRoles(roleTemplateStrings []string, project *models.Project) ([]string, error) { + roles := make([]string, len(roleTemplateStrings)) + for i, roleTemplateString := range roleTemplateStrings { + parsedRole, err := ParseProjectRole(roleTemplateString, project) + roles[i] = parsedRole + if err != nil { + return nil, err + } + } + return roles, nil +} diff --git a/api/pkg/authz/enforcer/role_test.go b/api/pkg/authz/enforcer/role_test.go new file mode 100644 index 00000000..dd949efb --- /dev/null +++ b/api/pkg/authz/enforcer/role_test.go @@ -0,0 +1,89 @@ +package enforcer + +import ( + "testing" + + "github.com/caraml-dev/mlp/api/models" +) + +func TestParseRole(t *testing.T) { + type args struct { + role string + templateContext map[string]string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "parse role with project id", + args{ + role: MLPProjectReaderRole, + templateContext: map[string]string{"ProjectId": "1"}, + }, + "mlp.projects.1.reader", + false, + }, + { + "parse plain string role without template context", + args{ + role: MLPAdminRole, + templateContext: nil, + }, + "mlp.administrator", + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseRole(tt.args.role, tt.args.templateContext) + if (err != nil) != tt.wantErr { + t.Errorf("ParseRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseProjectRole(t *testing.T) { + type args struct { + role string + project *models.Project + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "parse role with project id", + args{ + role: MLPProjectReaderRole, + project: &models.Project{ + ID: 1, + }, + }, + "mlp.projects.1.reader", + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseProjectRole(tt.args.role, tt.args.project) + if (err != nil) != tt.wantErr { + t.Errorf("ParseProjectRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseProjectRole() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/api/service/projects_service.go b/api/service/projects_service.go index abf9df1d..4023cb77 100644 --- a/api/service/projects_service.go +++ b/api/service/projects_service.go @@ -118,28 +118,6 @@ func (service *projectsService) save(project *models.Project) (*models.Project, return service.projectRepository.Save(project) } -func projectReaderRole(project *models.Project) string { - return fmt.Sprintf("mlp.projects.%d.reader", project.ID) -} - -func projectAdminRole(project *models.Project) string { - return fmt.Sprintf("mlp.projects.%d.administrator", project.ID) -} - -func rolesWithReadOnlyAccess(project *models.Project) []string { - predefinedRoles := []string{ - enforcer.ProjectReaderRole, - } - return append(predefinedRoles, projectReaderRole(project)) -} - -func rolesWithAdminAccess(project *models.Project) []string { - predefinedRoles := []string{ - enforcer.MLPAdminRole, - } - return append(predefinedRoles, projectAdminRole(project)) -} - func readPermissions(project *models.Project) []string { permissions := make([]string, 0) for _, method := range []string{"get"} { @@ -158,22 +136,45 @@ func adminPermissions(project *models.Project) []string { func (service *projectsService) updateAuthorizationPolicy(ctx context.Context, project *models.Project) error { updateRequest := enforcer.NewAuthorizationUpdateRequest() - for _, role := range rolesWithReadOnlyAccess(project) { + rolesWithReadOnlyAccess, err := enforcer.ParseProjectRoles([]string{ + enforcer.MLPProjectsReaderRole, + enforcer.MLPProjectReaderRole, + }, project) + if err != nil { + return err + } + for _, role := range rolesWithReadOnlyAccess { updateRequest.SetRolePermissions(role, readPermissions(project)) } + projectAdminRole, err := enforcer.ParseProjectRole(enforcer.MLPProjectAdminRole, project) + if err != nil { + return err + } if project.Administrators != nil { - updateRequest.SetRoleMembers(projectAdminRole(project), project.Administrators) + updateRequest.SetRoleMembers(projectAdminRole, project.Administrators) } else { - updateRequest.SetRoleMembers(projectAdminRole(project), []string{}) + updateRequest.SetRoleMembers(projectAdminRole, []string{}) } - for _, role := range rolesWithAdminAccess(project) { + rolesWithAdminAccess, err := enforcer.ParseProjectRoles([]string{ + enforcer.MLPAdminRole, + enforcer.MLPProjectAdminRole, + }, project) + if err != nil { + return err + } + for _, role := range rolesWithAdminAccess { updateRequest.SetRolePermissions(role, adminPermissions(project)) } + projectReaderRole, err := enforcer.ParseProjectRole(enforcer.MLPProjectReaderRole, project) + if err != nil { + return err + } if project.Readers != nil { - updateRequest.SetRoleMembers(projectReaderRole(project), project.Readers) + updateRequest.SetRoleMembers(projectReaderRole, project.Readers) } else { - updateRequest.SetRoleMembers(projectReaderRole(project), []string{}) + updateRequest.SetRoleMembers(projectReaderRole, []string{}) + } return service.authEnforcer.UpdateAuthorization(ctx, updateRequest) @@ -191,7 +192,7 @@ func (service *projectsService) filterAuthorizedProjects(ctx context.Context, pr return nil, err } for _, role := range roles { - if slices.Contains([]string{enforcer.MLPAdminRole, enforcer.ProjectReaderRole}, role) { + if slices.Contains([]string{enforcer.MLPAdminRole, enforcer.MLPProjectsReaderRole}, role) { return projects, nil } }