From 1dce43d607ddd2622218d2fdbbcfbc9149146838 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 22 Mar 2022 13:40:46 +0530 Subject: [PATCH 1/5] Update role definition for casbin --- pkg/auth/v3/auth.go | 1 + pkg/service/group.go | 83 +++++++++++++++++----------------- pkg/service/group_test.go | 31 +++++++------ pkg/service/role.go | 5 +-- pkg/service/user.go | 95 ++++++++++++++++++++------------------- pkg/service/user_test.go | 24 +++++----- 6 files changed, 123 insertions(+), 116 deletions(-) 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() From f7ac37ab6f2b0073b67125ac7ea4b511ad04356e Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 22 Mar 2022 13:51:46 +0530 Subject: [PATCH 2/5] Initial support for authorization --- pkg/auth/v3/auth.go | 21 +++++++++++++++++- pkg/auth/v3/interceptor.go | 35 ++++++++++++++++++++++++----- pkg/auth/v3/service.go | 45 ++++++++++++++++++++++++++++++-------- pkg/service/group.go | 4 ++-- pkg/service/user.go | 2 +- 5 files changed, 89 insertions(+), 18 deletions(-) diff --git a/pkg/auth/v3/auth.go b/pkg/auth/v3/auth.go index 9565b89..1ec9c17 100644 --- a/pkg/auth/v3/auth.go +++ b/pkg/auth/v3/auth.go @@ -6,8 +6,11 @@ import ( "github.com/RafayLabs/rcloud-base/pkg/enforcer" logv2 "github.com/RafayLabs/rcloud-base/pkg/log" "github.com/RafayLabs/rcloud-base/pkg/service" + "github.com/RafayLabs/rcloud-base/pkg/enforcer" kclient "github.com/ory/kratos-client-go" "github.com/uptrace/bun" + "gorm.io/driver/postgres" + "gorm.io/gorm" ) var _log = logv2.GetLogger() @@ -27,6 +30,7 @@ type Option struct { type authContext struct { kc *kclient.APIClient ks service.ApiKeyService + as service.AuthzService } // NewAuthContext setup authentication and authorization dependencies. @@ -36,6 +40,8 @@ func NewAuthContext(db *bun.DB) authContext { kratosScheme string kratosAddr string ) + // TODO: https://github.com/RafayLabs/prompt/pull/3#issuecomment-1073557206 + // Where exactly should we be getting these values from? if v, ok := os.LookupEnv("KRATOS_SCHEME"); ok { kratosScheme = v } else { @@ -51,5 +57,18 @@ func NewAuthContext(db *bun.DB) authContext { kratosConfig.Servers[0].URL = kratosScheme + "://" + kratosAddr kc = kclient.NewAPIClient(kratosConfig) - return authContext{kc: kc, ks: service.NewApiKeyService(db)} + gormDb, err := gorm.Open( + postgres.New(postgres.Config{Conn: db.DB}), + &gorm.Config{}, + ) + if err != nil { + _log.Fatalw("unable to create db connection", "error", err) + } + enforcer, err := enforcer.NewCasbinEnforcer(gormDb).Init() + if err != nil { + _log.Fatalw("unable to init enforcer", "error", err) + } + as := service.NewAuthzService(db, enforcer) + + return authContext{kc: kc, as: as, ks: service.NewApiKeyService(db)} } diff --git a/pkg/auth/v3/interceptor.go b/pkg/auth/v3/interceptor.go index 628fbb7..f8875f1 100644 --- a/pkg/auth/v3/interceptor.go +++ b/pkg/auth/v3/interceptor.go @@ -2,9 +2,11 @@ package authv3 import ( context "context" + "reflect" + "strings" "github.com/RafayLabs/rcloud-base/pkg/gateway" - commonpbv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3" + commonv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -21,6 +23,27 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc } } + // We have to get the value of org, and project (namespace in + // future) as we will be using this inorder to authorize the + // user's access to different resources + reqValue := reflect.ValueOf(req).Elem() + field := reqValue.FieldByName("Metadata") + var org string + var project string + if field != (reflect.Value{}) { + org = field.Interface().(*commonv3.Metadata).Organization + project = field.Interface().(*commonv3.Metadata).Project + } + + // overrides for picking up info when not in default metadata locations + // XXX: This requires any new items which does not follow metadata convention added here + switch strings.Split(info.FullMethod, "/")[1] { + case "rafay.dev.rpc.v3.Project": + project = field.Interface().(*commonv3.Metadata).Name + case "rafay.dev.rpc.v3.Organization": + org = field.Interface().(*commonv3.Metadata).Name + } + md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, status.Error(codes.InvalidArgument, "grpc metadata not exist") @@ -43,11 +66,13 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc if len(md.Get("grpcgateway-cookie")) != 0 { cookie = md.Get("grpcgateway-cookie")[0] } - acReq := &commonpbv3.IsRequestAllowedRequest{ + acReq := &commonv3.IsRequestAllowedRequest{ Url: url, Method: method, XSessionToken: token, Cookie: cookie, + Org: org, + Project: project, } res, err := ac.IsRequestAllowed(ctx, nil, acReq) if err != nil { @@ -57,12 +82,12 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc s := res.GetStatus() switch s { - case commonpbv3.RequestStatus_RequestAllowed: + case commonv3.RequestStatus_RequestAllowed: ctx := NewSessionContext(ctx, res.SessionData) return handler(ctx, req) - case commonpbv3.RequestStatus_RequestMethodOrURLNotAllowed: + case commonv3.RequestStatus_RequestMethodOrURLNotAllowed: return nil, status.Error(codes.PermissionDenied, res.GetReason()) - case commonpbv3.RequestStatus_RequestNotAuthenticated: + case commonv3.RequestStatus_RequestNotAuthenticated: return nil, status.Error(codes.Unauthenticated, res.GetReason()) } diff --git a/pkg/auth/v3/service.go b/pkg/auth/v3/service.go index d3b9119..873a6aa 100644 --- a/pkg/auth/v3/service.go +++ b/pkg/auth/v3/service.go @@ -7,6 +7,7 @@ import ( "strings" rpcv3 "github.com/RafayLabs/rcloud-base/proto/rpc/user" + authzv1 "github.com/RafayLabs/rcloud-base/proto/types/authz" commonv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3" "github.com/spacemonkeygo/httpsig" ) @@ -25,10 +26,14 @@ func (ac *authContext) IsRequestAllowed(ctx context.Context, httpreq *http.Reque } // Authenticate request - err := ac.authenticate(ctx, httpreq, req, res) + err, succ := ac.authenticate(ctx, httpreq, req, res) if err != nil { return nil, err } + // Don't bother checking authorization if athentication failed + if !succ { + return res, nil + } // Authorize request err = ac.authorize(ctx, req, res) @@ -41,14 +46,14 @@ func (ac *authContext) IsRequestAllowed(ctx context.Context, httpreq *http.Reque // authenticate validate whether the request is from a legitimate user // and populate relevant information in res. -func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, req *commonv3.IsRequestAllowedRequest, res *commonv3.IsRequestAllowedResponse) error { +func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, req *commonv3.IsRequestAllowedRequest, res *commonv3.IsRequestAllowedResponse) (error, bool) { if len(req.XApiKey) > 0 && len(req.XSessionToken) == 0 { resp, err := ac.ks.GetByKey(ctx, &rpcv3.ApiKeyRequest{ Id: req.XApiKey, }) if err != nil { _log.Infow("unable to get api key", "key", req.XApiKey, "error", err) - return ErrInvalidAPIKey + return ErrInvalidAPIKey, false } var kg httpsig.KeyGetterFunc = func(id string) interface{} { return []byte(resp.Secret) @@ -58,7 +63,7 @@ func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, verifier.SetRequiredHeaders([]string{"content-md5", "date", "host", "nonce"}) err = verifier.Verify(httpreq) if err != nil { - return ErrInvalidSignature + return ErrInvalidSignature, false } res.Status = commonv3.RequestStatus_RequestAllowed res.SessionData.Username = resp.Name @@ -73,9 +78,9 @@ func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, if strings.Contains(err.Error(), "401 Unauthorized") { res.Status = commonv3.RequestStatus_RequestNotAuthenticated res.Reason = "no or invalid credentials" - return nil + return nil, false } else { - return err + return err, false } } if session.GetActive() { @@ -90,11 +95,33 @@ func (ac *authContext) authenticate(ctx context.Context, httpreq *http.Request, res.Reason = "no active session" } } - return nil + return nil, true } -// authorize performs authorization of the request and populate -// relevant information in res. +// authorize performs authorization of the request func (ac *authContext) authorize(ctx context.Context, req *commonv3.IsRequestAllowedRequest, res *commonv3.IsRequestAllowedResponse) error { + // user,namespace,project,org,url(perm),method + // ones that don't have value should be "*" + proj := req.Project + if proj == "" { + proj = "*" + } + org := req.Org + if org == "" { + org = "*" + } + er := authzv1.EnforceRequest{ + Params: []string{"u:" + res.SessionData.Username, "*", proj, org, req.Url, req.Method}, + } + authenticated, err := ac.as.Enforce(ctx, &er) + + if err != nil { + return err + } + if !authenticated.Res { + res.Status = commonv3.RequestStatus_RequestMethodOrURLNotAllowed + res.Reason = "not authorized to perform action" + return nil + } return nil } diff --git a/pkg/service/group.go b/pkg/service/group.go index 4325f27..5a1474d 100644 --- a/pkg/service/group.go +++ b/pkg/service/group.go @@ -82,7 +82,7 @@ func (s *groupService) createGroupRoleRelations(ctx context.Context, db bun.IDB, var ps []*authzv1.Policy for _, pnr := range projectNamespaceRoles { role := pnr.GetRole() - entity, err := pg.GetByName(ctx, db, role, &models.Role{}) + entity, err := dao.GetByName(ctx, db, role, &models.Role{}) if err != nil { return &userv3.Group{}, fmt.Errorf("unable to find role '%v'", role) } @@ -145,7 +145,7 @@ func (s *groupService) createGroupRoleRelations(ctx context.Context, db bun.IDB, if project == "" { return &userv3.Group{}, fmt.Errorf("no project name provided for role '%v'", roleName) } - projectId, err := pg.GetProjectId(ctx, s.db, project) + projectId, err := dao.GetProjectId(ctx, s.db, project) if err != nil { return &userv3.Group{}, fmt.Errorf("unable to find project '%v'", project) } diff --git a/pkg/service/user.go b/pkg/service/user.go index 430ab55..6044290 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -179,7 +179,7 @@ func (s *userService) createUserRoleRelations(ctx context.Context, db bun.IDB, u if project == "" { return &userv3.User{}, fmt.Errorf("no project name provided for role '%v'", roleName) } - projectId, err := pg.GetProjectId(ctx, db, project) + projectId, err := dao.GetProjectId(ctx, db, project) if err != nil { return user, fmt.Errorf("unable to find project '%v'", project) } From 51a246c2f5b7389d30c8ea414c137c8168047ddd Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 22 Mar 2022 13:52:00 +0530 Subject: [PATCH 3/5] Update master.rest to send auth token --- master.rest | 99 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/master.rest b/master.rest index d38e5c4..e70d204 100644 --- a/master.rest +++ b/master.rest @@ -1,7 +1,8 @@ :host = http://localhost:11000 -:partner = important-partner -:org = hello -:project = hello +:token = qJeyfD9IthujxqhbaEWOCkX8S3cjwvjk +:partner = partner0 +:org = org0 +:project = project-uno :description = this is my first project :opts.urlScope_user = user/b2e4162c-60df-4fd7-b8fd-8fd3e4d6e533 :opts.urlScope_org = organization/0d95d65f-3ce9-4340-ac05-77f71084d0a6 @@ -24,6 +25,7 @@ opts.urlScope_cluster = cluster/eff85770-1a9e-42bc-824b-d0ff5a0f792c # Create Project POST :host/auth/v3/partner/:partner/organization/:organization/project Content-Type: application/yaml +X-Session-Token: :token metadata: partner: :partner @@ -35,14 +37,17 @@ spec: # Get all projects GET :host/auth/v3/partner/:partner/organization/:organization/projects Content-Type: application/yaml +X-Session-Token: :token # Get single project GET :host/auth/v3/partner/:partner/organization/:organization/project/:project Content-Type: application/yaml +X-Session-Token: :token # Update project info PUT :host/auth/v3/partner/:partner/organization/:organization/project/:project Content-Type: application/yaml +X-Session-Token: :token metadata: partner: :partner @@ -61,6 +66,7 @@ DELETE :host/auth/v3/partner/:partner/organization/:organization/project/:projec # Publish kubeconfig setting PUT :host/v2/sentry/kubeconfig/:opts.urlScope_user/setting Content-Type: application/yaml +X-Session-Token: :token opts: organization: :orgid @@ -78,6 +84,7 @@ GET :host/v2/sentry/kubeconfig/:opts.urlScope_user/setting # Publish kubeconfig setting PUT :host/v2/sentry/kubeconfig/:opts.urlScope_org/setting Content-Type: application/yaml +X-Session-Token: :token opts: organization: :orgid @@ -103,6 +110,7 @@ GET :host/v2/sentry/kubeconfig/user?opts.selector=&opts.account=c6974c2e-ef14-41 # Revoke kubeconfig for user POST :host/v2/sentry/kubeconfig/revoke Content-Type: application/yaml +X-Session-Token: :token opts: organization: cc02bd83-89d8-4c76-a7dc-06854f93e21d @@ -111,10 +119,12 @@ opts: # Kubeconfig get cluster settings GET :host/v2/sentry/kubectl/:opts.urlScope_cluster/settings?opts.organization=cc02bd83-89d8-4c76-a7dc-06854f93e21d +X-Session-Token: :token # Publish kubectl settings PUT :host/v2/sentry/kubectl/:opts.urlScope_cluster/settings Content-Type: application/yaml +X-Session-Token: :token opts: organization: cc02bd83-89d8-4c76-a7dc-06854f93e21d @@ -129,6 +139,7 @@ GET :host/v2/sentry/authorization/user?userCN=a=b2e4162c-60df-4fd7-b8fd-8fd3e4d6 # Create Location POST :host/v3/partner/:partner/location Content-Type: application/yaml +X-Session-Token: :token metadata: name: :location @@ -143,14 +154,17 @@ spec: # Get all location GET :host/v3/partner/:partner/location Content-Type: application/yaml +X-Session-Token: :token # Get single location GET :host/v3/partner/:partner/location/:location Content-Type: application/yaml +X-Session-Token: :token # Update location info PUT :host/v3/partner/:partner/location/:location Content-Type: application/yaml +X-Session-Token: :token metadata: name: :location @@ -171,6 +185,7 @@ DELETE :host/v3/partner/:partner/location/:location # Create Cluster POST :host/infra/v3/project/:project/cluster Content-Type: application/yaml +X-Session-Token: :token metadata: name: :cluster @@ -190,14 +205,17 @@ spec: # Get all clusters GET :host/infra/v3/project/:project/cluster Content-Type: application/yaml +X-Session-Token: :token # Get single cluster GET :host/infra/v3/project/:project/cluster/:cluster Content-Type: application/yaml +X-Session-Token: :token # Update cluster info PUT :host/infra/v3/project/:project/cluster/:cluster Content-Type: application/yaml +X-Session-Token: :token apiVersion: infra.k8smgmt.io/v3 kind: Cluster @@ -230,6 +248,7 @@ DELETE :host/infra/v3/project/:project/cluster/:cluster # Create user POST :host/auth/v3/users Content-Type: application/yaml +X-Session-Token: :token metadata: partner: :partner @@ -242,6 +261,7 @@ spec: # Create user with roles POST :host/auth/v3/users Content-Type: application/yaml +X-Session-Token: :token metadata: partner: :partner @@ -251,21 +271,14 @@ spec: firstName: John lastName: Doe projectNamespaceRoles: - - role: NAMESPACE_ADMIN + - role: :role namespace: :namespace project: :project -# Get all users -GET :host/auth/v3/users -Content-Type: application/yaml - -# Get single user -GET :host/auth/v3/user/:user -Content-Type: application/yaml - # Update user info PUT :host/auth/v3/user/:user Content-Type: application/yaml +X-Session-Token: :token metadata: partner: :partner @@ -275,10 +288,22 @@ spec: firstName: John lastName: Cena projectNamespaceRoles: - - role: ADMIN + - role: :role + +# Get all users +GET :host/auth/v3/users +Content-Type: application/yaml +X-Session-Token: :token + +# Get single user +GET :host/auth/v3/user/:user +Content-Type: application/yaml +X-Session-Token: :token + # Delete single user DELETE :host/auth/v3/user/:user +X-Session-Token: :token ## Groups @@ -304,6 +329,7 @@ spec: # Create group no namespace and project POST :host/auth/v3/partner/:partner/organization/:org/groups Content-Type: application/yaml +X-Session-Token: :token metadata: name: :group @@ -320,6 +346,7 @@ spec: # Update group to no namespace PUT :host/auth/v3/partner/:partner/organization/:org/group/:group Content-Type: application/yaml +X-Session-Token: :token metadata: name: :group @@ -337,20 +364,24 @@ spec: # Get all groups GET :host/auth/v3/partner/:partner/organization/:org/groups Content-Type: application/yaml +X-Session-Token: :token # Get a group GET :host/auth/v3/partner/:partner/organization/:org/group/:group Content-Type: application/yaml +X-Session-Token: :token # Delete a group DELETE :host/auth/v3/partner/:partner/organization/:org/group/:group Content-Type: application/yaml +X-Session-Token: :token ## Roles # Create empty role POST :host/auth/v3/partner/:partner/organization/:org/roles Content-Type: application/yaml +X-Session-Token: :token metadata: name: :role @@ -364,6 +395,7 @@ spec: # Create role POST :host/auth/v3/partner/:partner/organization/:org/roles Content-Type: application/yaml +X-Session-Token: :token metadata: name: :role @@ -372,7 +404,7 @@ metadata: organization: :org spec: isGlobal: true - scope: cluster + scope: system rolepermissions: - account.read - ops_star.all @@ -380,6 +412,7 @@ spec: # Update role PUT :host/auth/v3/partner/:partner/organization/:org/role/:role Content-Type: application/yaml +X-Session-Token: :token metadata: name: :role @@ -388,21 +421,24 @@ metadata: organization: :org spec: isGlobal: true - scope: cluster + scope: system rolepermissions: - - account.read + - ops_star.all # Get all roles GET :host/auth/v3/partner/:partner/organization/:org/roles Content-Type: application/yaml +X-Session-Token: :token # Get a role GET :host/auth/v3/partner/:partner/organization/:org/role/:role Content-Type: application/yaml +X-Session-Token: :token # Delete a role DELETE :host/auth/v3/partner/:partner/organization/:org/role/:role Content-Type: application/yaml +X-Session-Token: :token ## Rolepermission @@ -410,6 +446,7 @@ Content-Type: application/yaml # Get all rolepermissions GET :host/auth/v3/rolepermissions Content-Type: application/yaml +X-Session-Token: :token ## Audit @@ -421,6 +458,7 @@ GET :host/event/v1/auditlog?filter.timefrom=now-1h # Create partner POST :host/auth/v3/partner Content-Type: application/yaml +X-Session-Token: :token metadata: name: :partner @@ -428,23 +466,39 @@ metadata: spec: host: "https://important.org" -# List organizations -GET :host/auth/v3/partner/:partner/organizations +# Get partner +GET :host/auth/v3/partner/:partner +Content-Type: application/yaml +X-Session-Token: :token + +## Organization # Create organization POST :host/auth/v3/partner/:partner/organization Content-Type: application/yaml +X-Session-Token: :token metadata: partner: :partner name: :org - description: "Very first organization" + description: "Very first organizataion" spec: active: true +# List organizations +GET :host/auth/v3/partner/:partner/organizations +Content-Type: application/yaml +X-Session-Token: :token + +# Get organization +GET :host/auth/v3/partner/:partner/organization/:org +Content-Type: application/yaml +X-Session-Token: :token + # Create project POST :host/auth/v3/partner/:partner/organization/:org/project Content-Type: application/yaml +X-Session-Token: :token metadata: name: :project @@ -452,6 +506,11 @@ metadata: spec: active: true +# Get project +GET :host/auth/v3/partner/:partner/organization/:org/project/:project +Content-Type: application/yaml +X-Session-Token: :token + # Delete project DELETE :host/auth/v3/partner/:partner/organization/:org/project/:project - +X-Session-Token: :token From abb56481a33a12080c4bbc990cfe04916c3c7637 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Tue, 22 Mar 2022 14:04:37 +0530 Subject: [PATCH 4/5] Auth middleware for use in prompt --- internal/dao/project.go | 29 +++++++++++++++++++++++++++ pkg/auth/v3/middleware.go | 42 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 internal/dao/project.go diff --git a/internal/dao/project.go b/internal/dao/project.go new file mode 100644 index 0000000..c06b782 --- /dev/null +++ b/internal/dao/project.go @@ -0,0 +1,29 @@ +package dao + +import ( + "context" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +func GetProjectOrganization(ctx context.Context, db bun.IDB, id uuid.UUID) (string, string, error) { + // Could possibly union them later for some speedup + type projectOrg struct { + Project string + Organization string + } + var r projectOrg + err := db.NewSelect().Table("authsrv_project"). + ColumnExpr("authsrv_project.name as project"). + ColumnExpr("authsrv_organization.name as organization"). + Join(`JOIN authsrv_organization ON authsrv_project.organization_id=authsrv_organization.id`). + Where("authsrv_project.id = ?", id). + Where("authsrv_project.trash = ?", false). + Where("authsrv_organization.trash = ?", false). + Scan(ctx, &r) + if err != nil { + return "", "", err + } + return r.Project, r.Organization, nil +} diff --git a/pkg/auth/v3/middleware.go b/pkg/auth/v3/middleware.go index 2ec8daa..3eae9b8 100644 --- a/pkg/auth/v3/middleware.go +++ b/pkg/auth/v3/middleware.go @@ -3,19 +3,24 @@ package authv3 import ( "net/http" "regexp" + "strings" + "github.com/RafayLabs/rcloud-base/internal/dao" commonpbv3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3" + "github.com/google/uuid" "github.com/uptrace/bun" "github.com/urfave/negroni" ) type authMiddleware struct { + db *bun.DB ac authContext opt Option } func NewAuthMiddleware(opt Option, db *bun.DB) negroni.Handler { return &authMiddleware{ + db: db, ac: NewAuthContext(db), opt: opt, } @@ -34,12 +39,48 @@ func (am *authMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, nex return } } + // Auth is primarily done via grpc endpoints, this is only used + // for endoints which do not go through grpc As of now, it is just + // prompt. + var proj string + var org string + + if strings.HasPrefix(r.URL.String(), "/v2/debug/prompt/project/") { + // /v2/debug/prompt/project/:project_id/cluster/:cluster_name + splits := strings.Split(r.URL.String(), "/") + if len(splits) > 5 { + projid, err := uuid.Parse(splits[5]) + if err != nil { + _log.Errorf("Failed to authenticate: unable to parse project uuid") + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + // What gets sent for project is the id unlike most other + // api routes, so we have to fetch the name as well as the + // org info for casbin + proj, org, err = dao.GetProjectOrganization(r.Context(), am.db, projid) + if err != nil { + _log.Errorf("Failed to authenticate: unable to find project") + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + } + } else { + // The middleware to only used with routes which does not have + // a grpc and so fail for any other requests. + _log.Errorf("Failed to authenticate: not a prompt request") + http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + req := &commonpbv3.IsRequestAllowedRequest{ Url: r.URL.String(), Method: r.Method, XSessionToken: r.Header.Get("X-Session-Token"), XApiKey: r.Header.Get("X-RAFAY-API-KEYID"), Cookie: r.Header.Get("Cookie"), + Project: proj, + Org: org, } res, err := am.ac.IsRequestAllowed(r.Context(), r, req) if err != nil { @@ -64,5 +105,4 @@ func (am *authMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, nex // status is unknown http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return } From c89741aefbafd1d6f2c7077643d039d1cd639802 Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Thu, 24 Mar 2022 12:15:14 +0530 Subject: [PATCH 5/5] Don't use reflect to get resource Metadata --- pkg/auth/v3/auth.go | 1 - pkg/auth/v3/interceptor.go | 31 +++++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pkg/auth/v3/auth.go b/pkg/auth/v3/auth.go index 1ec9c17..849bfcb 100644 --- a/pkg/auth/v3/auth.go +++ b/pkg/auth/v3/auth.go @@ -6,7 +6,6 @@ import ( "github.com/RafayLabs/rcloud-base/pkg/enforcer" logv2 "github.com/RafayLabs/rcloud-base/pkg/log" "github.com/RafayLabs/rcloud-base/pkg/service" - "github.com/RafayLabs/rcloud-base/pkg/enforcer" kclient "github.com/ory/kratos-client-go" "github.com/uptrace/bun" "gorm.io/driver/postgres" diff --git a/pkg/auth/v3/interceptor.go b/pkg/auth/v3/interceptor.go index f8875f1..043206d 100644 --- a/pkg/auth/v3/interceptor.go +++ b/pkg/auth/v3/interceptor.go @@ -2,7 +2,6 @@ package authv3 import ( context "context" - "reflect" "strings" "github.com/RafayLabs/rcloud-base/pkg/gateway" @@ -13,6 +12,10 @@ import ( "google.golang.org/grpc/status" ) +type hasMetadata interface { + GetMetadata() *commonv3.Metadata +} + func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { // TODO: Optimize authentication for a session/gRPC @@ -26,22 +29,22 @@ func (ac authContext) NewAuthUnaryInterceptor(opt Option) grpc.UnaryServerInterc // We have to get the value of org, and project (namespace in // future) as we will be using this inorder to authorize the // user's access to different resources - reqValue := reflect.ValueOf(req).Elem() - field := reqValue.FieldByName("Metadata") var org string var project string - if field != (reflect.Value{}) { - org = field.Interface().(*commonv3.Metadata).Organization - project = field.Interface().(*commonv3.Metadata).Project - } + resource, ok := req.(hasMetadata) + if ok { + meta := resource.GetMetadata() + org = meta.Organization + project = meta.Project - // overrides for picking up info when not in default metadata locations - // XXX: This requires any new items which does not follow metadata convention added here - switch strings.Split(info.FullMethod, "/")[1] { - case "rafay.dev.rpc.v3.Project": - project = field.Interface().(*commonv3.Metadata).Name - case "rafay.dev.rpc.v3.Organization": - org = field.Interface().(*commonv3.Metadata).Name + // overrides for picking up info when not in default metadata locations + // XXX: This requires any new items which does not follow metadata convention added here + switch strings.Split(info.FullMethod, "/")[1] { + case "rafay.dev.rpc.v3.Project": + project = meta.Name + case "rafay.dev.rpc.v3.Organization": + org = meta.Name + } } md, ok := metadata.FromIncomingContext(ctx)