diff --git a/pkg/auth/v3/auth.go b/pkg/auth/v3/auth.go index aed0d93..9565b89 100644 --- a/pkg/auth/v3/auth.go +++ b/pkg/auth/v3/auth.go @@ -3,6 +3,7 @@ package authv3 import ( "os" + "github.com/RafayLabs/rcloud-base/pkg/enforcer" logv2 "github.com/RafayLabs/rcloud-base/pkg/log" "github.com/RafayLabs/rcloud-base/pkg/service" kclient "github.com/ory/kratos-client-go" diff --git a/pkg/service/group.go b/pkg/service/group.go index 47c25e2..4325f27 100644 --- a/pkg/service/group.go +++ b/pkg/service/group.go @@ -4,7 +4,7 @@ import ( "context" "database/sql" "fmt" - "strconv" + "strings" "time" "github.com/RafayLabs/rcloud-base/internal/dao" @@ -77,53 +77,75 @@ func (s *groupService) createGroupRoleRelations(ctx context.Context, db bun.IDB, // TODO: add transactions projectNamespaceRoles := group.GetSpec().GetProjectNamespaceRoles() - var pgnrs []models.ProjectGroupNamespaceRole var pgrs []models.ProjectGroupRole var grs []models.GroupRole var ps []*authzv1.Policy for _, pnr := range projectNamespaceRoles { role := pnr.GetRole() - entity, err := dao.GetIdByName(ctx, db, role, &models.Role{}) + entity, err := pg.GetByName(ctx, db, role, &models.Role{}) if err != nil { return &userv3.Group{}, fmt.Errorf("unable to find role '%v'", role) } var roleId uuid.UUID + var roleName string + var scope string if rle, ok := entity.(*models.Role); ok { roleId = rle.ID + roleName = rle.Name + scope = strings.ToLower(rle.Scope) } else { return &userv3.Group{}, fmt.Errorf("unable to find role '%v'", role) } project := pnr.GetProject() org := group.GetMetadata().GetOrganization() - namespaceId := pnr.GetNamespace() // TODO: lookup id from name - switch { - case namespaceId != 0: - projectId, err := dao.GetProjectId(ctx, db, project) - if err != nil { - return &userv3.Group{}, fmt.Errorf("unable to find project '%v'", project) - } - pgnr := models.ProjectGroupNamespaceRole{ + + switch scope { + case "system": + gr := models.GroupRole{ Trash: false, RoleId: roleId, PartnerId: ids.Partner, OrganizationId: ids.Organization, GroupId: ids.Id, - ProjectId: projectId, - NamespaceId: namespaceId, Active: true, } - pgnrs = append(pgnrs, pgnr) - + grs = append(grs, gr) ps = append(ps, &authzv1.Policy{ Sub: "g:" + group.GetMetadata().GetName(), - Ns: strconv.FormatInt(namespaceId, 10), - Proj: project, + Ns: "*", + Proj: "*", + Org: "*", + Obj: role, + }) + case "organization": + if org == "" { + return &userv3.Group{}, fmt.Errorf("no org name provided for role '%v'", roleName) + } + gr := models.GroupRole{ + Trash: false, + RoleId: roleId, + PartnerId: ids.Partner, + OrganizationId: ids.Organization, + GroupId: ids.Id, + Active: true, + } + grs = append(grs, gr) + ps = append(ps, &authzv1.Policy{ + Sub: "g:" + group.GetMetadata().GetName(), + Ns: "*", + Proj: "*", Org: org, Obj: role, }) - case project != "": - projectId, err := dao.GetProjectId(ctx, db, project) + case "project": + if org == "" { + return &userv3.Group{}, fmt.Errorf("no org name provided for role '%v'", roleName) + } + if project == "" { + return &userv3.Group{}, fmt.Errorf("no project name provided for role '%v'", roleName) + } + projectId, err := pg.GetProjectId(ctx, s.db, project) if err != nil { return &userv3.Group{}, fmt.Errorf("unable to find project '%v'", project) } @@ -145,29 +167,6 @@ func (s *groupService) createGroupRoleRelations(ctx context.Context, db bun.IDB, Org: org, Obj: role, }) - default: - gr := models.GroupRole{ - Trash: false, - RoleId: roleId, - PartnerId: ids.Partner, - OrganizationId: ids.Organization, - GroupId: ids.Id, - Active: true, - } - grs = append(grs, gr) - ps = append(ps, &authzv1.Policy{ - Sub: "g:" + group.GetMetadata().GetName(), - Ns: "*", - Proj: "*", - Org: org, - Obj: role, - }) - } - } - if len(pgnrs) > 0 { - _, err := dao.Create(ctx, db, &pgnrs) - if err != nil { - return &userv3.Group{}, err } } if len(pgrs) > 0 { diff --git a/pkg/service/group_test.go b/pkg/service/group_test.go index e996cf7..61ab985 100644 --- a/pkg/service/group_test.go +++ b/pkg/service/group_test.go @@ -216,9 +216,10 @@ func TestCreateGroupNoUsersWithRoles(t *testing.T) { name string roles []*userv3.ProjectNamespaceRole dbname string + scope string shouldfail bool }{ - {"just role", []*userv3.ProjectNamespaceRole{{Role: uuid.New().String()}}, "authsrv_grouprole", false}, + {"just role", []*userv3.ProjectNamespaceRole{{Role: uuid.New().String()}}, "authsrv_grouprole", "system", false}, // {"just project", []*userv3.ProjectNamespaceRole{{Project: &projectid}}, "authsrv_grouprole", true}, // no role creation without role // {"just namespace", []*userv3.ProjectNamespaceRole{{Namespace: &namespaceid}}, "authsrv_grouprole", true}, // no role creation without role, // {"project and namespace", []*userv3.ProjectNamespaceRole{{Project: &projectid, Namespace: &namespaceid}}, "authsrv_grouprole", true}, // no role creation without role, @@ -249,8 +250,8 @@ func TestCreateGroupNoUsersWithRoles(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery(`INSERT INTO "authsrv_group"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(guuid)) - mock.ExpectQuery(`SELECT "resourcerole"."id" FROM "authsrv_resourcerole" AS "resourcerole"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) + mock.ExpectQuery(`SELECT "resourcerole"."id".* FROM "authsrv_resourcerole" AS "resourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(pruuid, "role-name", tc.scope)) if tc.roles[0].Project != nil { mock.ExpectQuery(`SELECT "project"."id" FROM "authsrv_project" AS "project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) @@ -295,14 +296,15 @@ func TestCreateGroupWithUsersWithRoles(t *testing.T) { users []string roles []*userv3.ProjectNamespaceRole dbname string + scope string shouldfail bool }{ - {"just role", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Role: uuid.New().String()}}, "authsrv_grouprole", false}, - {"just project", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid}}, "authsrv_grouprole", true}, // no role creation without role - {"just namespace", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Namespace: &namespaceid}}, "authsrv_grouprole", true}, // no role creation without role, - {"project and namespace", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid, Namespace: &namespaceid}}, "authsrv_grouprole", true}, // no role creation without role, - {"project and role", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid, Role: uuid.New().String()}}, "authsrv_projectgrouprole", false}, - {"project role namespace", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid, Namespace: &namespaceid, Role: uuid.New().String()}}, "authsrv_projectgroupnamespacerole", false}, + {"just role", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Role: uuid.New().String()}}, "authsrv_grouprole", "system", false}, + {"just project", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid}}, "authsrv_grouprole", "system", true}, // no role creation without role + {"just namespace", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Namespace: &namespaceid}}, "authsrv_projectgrouprole", "project", true}, // no role creation without role, + {"project and namespace", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid, Namespace: &namespaceid}}, "authsrv_grouprole", "project", true}, // no role creation without role, + {"project and role", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid, Role: uuid.New().String()}}, "authsrv_projectgrouprole", "project", false}, + // {"project role namespace", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Project: &projectid, Namespace: &namespaceid, Role: uuid.New().String()}}, "authsrv_projectgroupnamespacerole", false}, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { @@ -334,8 +336,8 @@ func TestCreateGroupWithUsersWithRoles(t *testing.T) { mock.ExpectQuery(`INSERT INTO "authsrv_groupaccount"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) - mock.ExpectQuery(`SELECT "resourcerole"."id" FROM "authsrv_resourcerole" AS "resourcerole"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) + mock.ExpectQuery(`SELECT "resourcerole"."id".* FROM "authsrv_resourcerole" AS "resourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(pruuid, "role-name", tc.scope)) if tc.roles[0].Project != nil { mock.ExpectQuery(`SELECT "project"."id" FROM "authsrv_project" AS "project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) @@ -383,8 +385,9 @@ func TestUpdateGroupWithUsersWithRoles(t *testing.T) { users []string roles []*userv3.ProjectNamespaceRole dbname string + scope string }{ - {"user role update", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Role: uuid.New().String()}}, "authsrv_grouprole"}, + {"user role update", []string{"user-" + uuid.New().String()}, []*userv3.ProjectNamespaceRole{{Role: uuid.New().String()}}, "authsrv_grouprole", "system"}, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { @@ -422,8 +425,8 @@ func TestUpdateGroupWithUsersWithRoles(t *testing.T) { WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`UPDATE "authsrv_projectgroupnamespacerole" AS "projectgroupnamespacerole" SET trash = TRUE WHERE ."group_id" = '` + guuid). WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT "resourcerole"."id" FROM "authsrv_resourcerole" AS "resourcerole"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) + mock.ExpectQuery(`SELECT "resourcerole"."id".* FROM "authsrv_resourcerole" AS "resourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(pruuid, "role-name", tc.scope)) if tc.roles[0].Project != nil { mock.ExpectQuery(`SELECT "project"."id" FROM "authsrv_project" AS "project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) diff --git a/pkg/service/role.go b/pkg/service/role.go index 751b3c2..6e5d5cd 100644 --- a/pkg/service/role.go +++ b/pkg/service/role.go @@ -131,10 +131,7 @@ func (s *roleService) Create(ctx context.Context, role *rolev3.Role) (*rolev3.Ro } scope := role.GetSpec().GetScope() - // since this is purely additional metadata at this point, we - // can kinda treat it as optional, and so we are allowing empty - // TODO: check if "" is valid - if !contains([]string{"system", "organization", "project", ""}, strings.ToLower(scope)) { + if !contains([]string{"system", "organization", "project"}, strings.ToLower(scope)) { return nil, fmt.Errorf("unknown scope '%v'", scope) } diff --git a/pkg/service/user.go b/pkg/service/user.go index 964d352..430ab55 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -4,7 +4,7 @@ import ( "context" "database/sql" "fmt" - "strconv" + "strings" "time" "github.com/google/uuid" @@ -102,56 +102,84 @@ func (s *userService) createUserRoleRelations(ctx context.Context, db bun.IDB, u projectNamespaceRoles := user.GetSpec().GetProjectNamespaceRoles() // TODO: add transactions - var panrs []models.ProjectAccountNamespaceRole var pars []models.ProjectAccountResourcerole var ars []models.AccountResourcerole var ps []*authzv1.Policy for _, pnr := range projectNamespaceRoles { role := pnr.GetRole() - entity, err := dao.GetIdByName(ctx, db, role, &models.Role{}) + entity, err := dao.GetByName(ctx, db, role, &models.Role{}) if err != nil { - return user, fmt.Errorf("unable to find role '%v'", role) + return &userv3.User{}, fmt.Errorf("unable to find role '%v'", role) } var roleId uuid.UUID + var roleName string + var scope string if rle, ok := entity.(*models.Role); ok { roleId = rle.ID + roleName = rle.Name + scope = strings.ToLower(rle.Scope) } else { - return user, fmt.Errorf("unable to find role '%v'", role) + return &userv3.User{}, fmt.Errorf("unable to find role '%v'", role) } project := pnr.GetProject() org := user.GetMetadata().GetOrganization() - namespaceId := pnr.GetNamespace() // TODO: lookup id from name - switch { - case pnr.Namespace != nil: - projectId, err := dao.GetProjectId(ctx, db, project) - if err != nil { - return user, fmt.Errorf("unable to find project '%v'", project) - } - panr := models.ProjectAccountNamespaceRole{ + switch scope { + case "system": + ar := models.AccountResourcerole{ CreatedAt: time.Now(), ModifiedAt: time.Now(), Trash: false, + Default: true, + RoleId: roleId, + PartnerId: ids.Partner, + OrganizationId: ids.Organization, // Not really used + AccountId: ids.Id, + Active: true, + } + ars = append(ars, ar) + + ps = append(ps, &authzv1.Policy{ + Sub: "u:" + user.GetMetadata().GetName(), + Ns: "*", + Proj: "*", + Org: "*", + Obj: role, + }) + case "organization": + if org == "" { + return &userv3.User{}, fmt.Errorf("no org name provided for role '%v'", roleName) + } + + ar := models.AccountResourcerole{ + CreatedAt: time.Now(), + ModifiedAt: time.Now(), + Trash: false, + Default: true, RoleId: roleId, PartnerId: ids.Partner, OrganizationId: ids.Organization, AccountId: ids.Id, - ProjectId: projectId, - NamespaceId: namespaceId, Active: true, } - panrs = append(panrs, panr) + ars = append(ars, ar) ps = append(ps, &authzv1.Policy{ Sub: "u:" + user.GetMetadata().GetName(), - Ns: strconv.FormatInt(namespaceId, 10), - Proj: project, + Ns: "*", + Proj: "*", Org: org, Obj: role, }) - case project != "": - projectId, err := dao.GetProjectId(ctx, db, project) + case "project": + if org == "" { + return &userv3.User{}, fmt.Errorf("no org name provided for role '%v'", roleName) + } + if project == "" { + return &userv3.User{}, fmt.Errorf("no project name provided for role '%v'", roleName) + } + projectId, err := pg.GetProjectId(ctx, db, project) if err != nil { return user, fmt.Errorf("unable to find project '%v'", project) } @@ -177,32 +205,9 @@ func (s *userService) createUserRoleRelations(ctx context.Context, db bun.IDB, u Obj: role, }) default: - ar := models.AccountResourcerole{ - CreatedAt: time.Now(), - ModifiedAt: time.Now(), - Trash: false, - Default: true, - RoleId: roleId, - PartnerId: ids.Partner, - OrganizationId: ids.Organization, - AccountId: ids.Id, - Active: true, + if err != nil { + return user, fmt.Errorf("namespace specific roles are not handled") } - ars = append(ars, ar) - - ps = append(ps, &authzv1.Policy{ - Sub: "u:" + user.GetMetadata().GetName(), - Ns: "*", - Proj: "*", - Org: org, - Obj: role, - }) - } - } - if len(panrs) > 0 { - _, err := dao.Create(ctx, db, &panrs) - if err != nil { - return &userv3.User{}, err } } if len(pars) > 0 { diff --git a/pkg/service/user_test.go b/pkg/service/user_test.go index f95db5b..47a52ba 100644 --- a/pkg/service/user_test.go +++ b/pkg/service/user_test.go @@ -99,14 +99,16 @@ func TestCreateUserWithRole(t *testing.T) { name string roles []*userv3.ProjectNamespaceRole dbname string + scope string shouldfail bool }{ - {"just role", []*userv3.ProjectNamespaceRole{{Role: rname}}, "authsrv_accountresourcerole", false}, - {"just project", []*userv3.ProjectNamespaceRole{{Project: &prname}}, "authsrv_accountrole", true}, // no role creation without role - {"just namespace", []*userv3.ProjectNamespaceRole{{Namespace: &namespaceid}}, "authsrv_accountrole", true}, // no role creation without role, - {"project and namespace", []*userv3.ProjectNamespaceRole{{Project: &prname, Namespace: &namespaceid}}, "authsrv_accountrole", true}, // no role creation without role, - {"project and role", []*userv3.ProjectNamespaceRole{{Project: &prname, Role: rname}}, "authsrv_projectaccountresourcerole", false}, - {"project role namespace", []*userv3.ProjectNamespaceRole{{Project: &prname, Namespace: &namespaceid, Role: rname}}, "authsrv_projectaccountnamespacerole", false}, + {"just role", []*userv3.ProjectNamespaceRole{{Role: rname}}, "authsrv_accountresourcerole", "system", false}, + {"just role org scope", []*userv3.ProjectNamespaceRole{{Role: rname}}, "authsrv_accountresourcerole", "organization", false}, + {"just project", []*userv3.ProjectNamespaceRole{{Project: &prname}}, "authsrv_accountrole", "system", true}, // no role creation without role + {"just namespace", []*userv3.ProjectNamespaceRole{{Namespace: &namespaceid}}, "authsrv_accountrole", "system", true}, // no role creation without role, + {"project and namespace", []*userv3.ProjectNamespaceRole{{Project: &prname, Namespace: &namespaceid}}, "authsrv_accountrole", "system", true}, // no role creation without role, + {"project and role", []*userv3.ProjectNamespaceRole{{Project: &prname, Role: rname}}, "authsrv_projectaccountresourcerole", "project", false}, + {"project role namespace", []*userv3.ProjectNamespaceRole{{Project: &prname, Namespace: &namespaceid, Role: rname}}, "authsrv_projectaccountresourcerole", "project", false}, } for _, tc := range tt { @@ -129,8 +131,8 @@ func TestCreateUserWithRole(t *testing.T) { WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(ouuid)) mock.ExpectBegin() - mock.ExpectQuery(`SELECT "resourcerole"."id" FROM "authsrv_resourcerole" AS "resourcerole"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) + mock.ExpectQuery(`SELECT "resourcerole"."id".* FROM "authsrv_resourcerole" AS "resourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(pruuid, "role-name", tc.scope)) if tc.roles[0].Project != nil { mock.ExpectQuery(`SELECT "project"."id" FROM "authsrv_project" AS "project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) @@ -198,11 +200,11 @@ func TestUpdateUser(t *testing.T) { WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`UPDATE "authsrv_projectaccountnamespacerole" AS "projectaccountnamespacerole" SET trash = TRUE WHERE`). WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectQuery(`SELECT "resourcerole"."id" FROM "authsrv_resourcerole" AS "resourcerole"`). - WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) + mock.ExpectQuery(`SELECT "resourcerole"."id".* FROM "authsrv_resourcerole" AS "resourcerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "name", "scope"}).AddRow(pruuid, "role-name", "project")) mock.ExpectQuery(`SELECT "project"."id" FROM "authsrv_project" AS "project"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(pruuid)) - mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountnamespacerole"`). + mock.ExpectQuery(`INSERT INTO "authsrv_projectaccountresourcerole"`). WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) mock.ExpectCommit()