Skip to content

Commit

Permalink
fix: use role template strings for predefined roles
Browse files Browse the repository at this point in the history
Signed-off-by: Khor Shu Heng <khor.heng@gojek.com>
  • Loading branch information
khorshuheng committed Jul 26, 2023
1 parent 776ba32 commit 5dc98ed
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 32 deletions.
2 changes: 1 addition & 1 deletion api/cmd/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
46 changes: 44 additions & 2 deletions api/pkg/authz/enforcer/role.go
Original file line number Diff line number Diff line change
@@ -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
}
89 changes: 89 additions & 0 deletions api/pkg/authz/enforcer/role_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
59 changes: 30 additions & 29 deletions api/service/projects_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"} {
Expand All @@ -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)
Expand All @@ -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
}
}
Expand Down

0 comments on commit 5dc98ed

Please sign in to comment.