mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-01 20:02:15 +00:00
fix: improve permission checks (#682)
* separate roles for global org * remove old user grant permissions * allow context permissions Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
@@ -47,18 +47,12 @@ InternalAuthZ:
|
|||||||
- "project.app.read"
|
- "project.app.read"
|
||||||
- "project.app.write"
|
- "project.app.write"
|
||||||
- "project.app.delete"
|
- "project.app.delete"
|
||||||
- "project.user.grant.read"
|
|
||||||
- "project.user.grant.write"
|
|
||||||
- "project.user.grant.delete"
|
|
||||||
- "project.grant.read"
|
- "project.grant.read"
|
||||||
- "project.grant.write"
|
- "project.grant.write"
|
||||||
- "project.grant.delete"
|
- "project.grant.delete"
|
||||||
- "project.grant.member.read"
|
- "project.grant.member.read"
|
||||||
- "project.grant.member.write"
|
- "project.grant.member.write"
|
||||||
- "project.grant.member.delete"
|
- "project.grant.member.delete"
|
||||||
- "project.grant.user.grant.read"
|
|
||||||
- "project.grant.user.grant.write"
|
|
||||||
- "project.grant.user.grant.delete"
|
|
||||||
- Role: 'IAM_OWNER_VIEWER'
|
- Role: 'IAM_OWNER_VIEWER'
|
||||||
Permissions:
|
Permissions:
|
||||||
- "iam.read"
|
- "iam.read"
|
||||||
@@ -77,10 +71,8 @@ InternalAuthZ:
|
|||||||
- "project.member.read"
|
- "project.member.read"
|
||||||
- "project.role.read"
|
- "project.role.read"
|
||||||
- "project.app.read"
|
- "project.app.read"
|
||||||
- "project.user.grant.read"
|
|
||||||
- "project.grant.read"
|
- "project.grant.read"
|
||||||
- "project.grant.member.read"
|
- "project.grant.member.read"
|
||||||
- "project.grant.user.grant.read"
|
|
||||||
- Role: 'ORG_OWNER'
|
- Role: 'ORG_OWNER'
|
||||||
Permissions:
|
Permissions:
|
||||||
- "org.read"
|
- "org.read"
|
||||||
@@ -116,18 +108,12 @@ InternalAuthZ:
|
|||||||
- "project.role.delete"
|
- "project.role.delete"
|
||||||
- "project.app.read"
|
- "project.app.read"
|
||||||
- "project.app.write"
|
- "project.app.write"
|
||||||
- "project.user.grant.read"
|
|
||||||
- "project.user.grant.write"
|
|
||||||
- "project.user.grant.delete"
|
|
||||||
- "project.grant.read"
|
- "project.grant.read"
|
||||||
- "project.grant.write"
|
- "project.grant.write"
|
||||||
- "project.grant.delete"
|
- "project.grant.delete"
|
||||||
- "project.grant.member.read"
|
- "project.grant.member.read"
|
||||||
- "project.grant.member.write"
|
- "project.grant.member.write"
|
||||||
- "project.grant.member.delete"
|
- "project.grant.member.delete"
|
||||||
- "project.grant.user.grant.read"
|
|
||||||
- "project.grant.user.grant.write"
|
|
||||||
- "project.grant.user.grant.delete"
|
|
||||||
- Role: 'ORG_OWNER_VIEWER'
|
- Role: 'ORG_OWNER_VIEWER'
|
||||||
Permissions:
|
Permissions:
|
||||||
- "org.read"
|
- "org.read"
|
||||||
@@ -142,7 +128,6 @@ InternalAuthZ:
|
|||||||
- "project.member.read"
|
- "project.member.read"
|
||||||
- "project.role.read"
|
- "project.role.read"
|
||||||
- "project.app.read"
|
- "project.app.read"
|
||||||
- "project.user.grant.read"
|
|
||||||
- "project.grant.read"
|
- "project.grant.read"
|
||||||
- "project.grant.member.read"
|
- "project.grant.member.read"
|
||||||
- "project.grant.user.grant.read"
|
- "project.grant.user.grant.read"
|
||||||
@@ -224,6 +209,37 @@ InternalAuthZ:
|
|||||||
- "user.global.read"
|
- "user.global.read"
|
||||||
- "user.grant.read"
|
- "user.grant.read"
|
||||||
- "user.membership.read"
|
- "user.membership.read"
|
||||||
|
- Role: 'PROJECT_OWNER_GLOBAL'
|
||||||
|
Permissions:
|
||||||
|
- "org.global.read"
|
||||||
|
- "project.read"
|
||||||
|
- "project.write"
|
||||||
|
- "project.delete"
|
||||||
|
- "project.member.read"
|
||||||
|
- "project.member.write"
|
||||||
|
- "project.member.delete"
|
||||||
|
- "project.role.read"
|
||||||
|
- "project.role.write"
|
||||||
|
- "project.role.delete"
|
||||||
|
- "project.app.read"
|
||||||
|
- "project.app.write"
|
||||||
|
- "project.app.delete"
|
||||||
|
- "user.global.read"
|
||||||
|
- "user.grant.read"
|
||||||
|
- "user.grant.write"
|
||||||
|
- "user.grant.delete"
|
||||||
|
- "user.membership.read"
|
||||||
|
- Role: 'PROJECT_OWNER_VIEWER_GLOBAL'
|
||||||
|
Permissions:
|
||||||
|
- "project.read"
|
||||||
|
- "project.member.read"
|
||||||
|
- "project.role.read"
|
||||||
|
- "project.app.read"
|
||||||
|
- "project.grant.read"
|
||||||
|
- "project.grant.member.read"
|
||||||
|
- "user.global.read"
|
||||||
|
- "user.grant.read"
|
||||||
|
- "user.membership.read"
|
||||||
- Role: 'PROJECT_GRANT_OWNER'
|
- Role: 'PROJECT_GRANT_OWNER'
|
||||||
Permissions:
|
Permissions:
|
||||||
- "org.global.read"
|
- "org.global.read"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func mapRoleToPerm(requiredPerm, actualRole string, authConfig Config, requestPe
|
|||||||
allPermissions = append(allPermissions, permWithCtx)
|
allPermissions = append(allPermissions, permWithCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p, _ = SplitPermission(p)
|
||||||
if p == requiredPerm {
|
if p == requiredPerm {
|
||||||
if !ExistsPerm(requestPermissions, permWithCtx) {
|
if !ExistsPerm(requestPermissions, permWithCtx) {
|
||||||
requestPermissions = append(requestPermissions, permWithCtx)
|
requestPermissions = append(requestPermissions, permWithCtx)
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) GetProjectMemberRoles(ctx context.Context, _ *empty.Empty) (*management.ProjectMemberRoles, error) {
|
func (s *Server) GetProjectMemberRoles(ctx context.Context, _ *empty.Empty) (*management.ProjectMemberRoles, error) {
|
||||||
return &management.ProjectMemberRoles{Roles: s.project.GetProjectMemberRoles()}, nil
|
roles, err := s.project.GetProjectMemberRoles(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &management.ProjectMemberRoles{Roles: roles}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SearchProjectMembers(ctx context.Context, in *management.ProjectMemberSearchRequest) (*management.ProjectMemberSearchResponse, error) {
|
func (s *Server) SearchProjectMembers(ctx context.Context, in *management.ProjectMemberSearchRequest) (*management.ProjectMemberSearchResponse, error) {
|
||||||
|
|||||||
@@ -98,6 +98,16 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
iam, err := es_iam.StartIAM(
|
||||||
|
es_iam.IAMConfig{
|
||||||
|
Eventstore: es,
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
},
|
||||||
|
systemDefaults,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
project, err := es_proj.StartProject(
|
project, err := es_proj.StartProject(
|
||||||
es_proj.ProjectConfig{
|
es_proj.ProjectConfig{
|
||||||
@@ -109,16 +119,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
iam, err := es_iam.StartIAM(
|
|
||||||
es_iam.IAMConfig{
|
|
||||||
Eventstore: es,
|
|
||||||
Cache: conf.Eventstore.Cache,
|
|
||||||
},
|
|
||||||
systemDefaults,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
org := es_org.StartOrg(es_org.OrgConfig{Eventstore: es, IAMDomain: conf.Domain}, systemDefaults)
|
org := es_org.StartOrg(es_org.OrgConfig{Eventstore: es, IAMDomain: conf.Domain}, systemDefaults)
|
||||||
|
|
||||||
repos := handler.EventstoreRepos{UserEvents: user, ProjectEvents: project, OrgEvents: org, IamEvents: iam}
|
repos := handler.EventstoreRepos{UserEvents: user, ProjectEvents: project, OrgEvents: org, IamEvents: iam}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/eventstore/models"
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||||
|
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
"github.com/caos/zitadel/internal/management/repository/eventsourcing/view"
|
"github.com/caos/zitadel/internal/management/repository/eventsourcing/view"
|
||||||
global_model "github.com/caos/zitadel/internal/model"
|
global_model "github.com/caos/zitadel/internal/model"
|
||||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||||
@@ -29,8 +30,10 @@ type ProjectRepo struct {
|
|||||||
ProjectEvents *proj_event.ProjectEventstore
|
ProjectEvents *proj_event.ProjectEventstore
|
||||||
UserGrantEvents *usr_grant_event.UserGrantEventStore
|
UserGrantEvents *usr_grant_event.UserGrantEventStore
|
||||||
UserEvents *usr_event.UserEventstore
|
UserEvents *usr_event.UserEventstore
|
||||||
|
IAMEvents *iam_event.IAMEventstore
|
||||||
View *view.View
|
View *view.View
|
||||||
Roles []string
|
Roles []string
|
||||||
|
IAMID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (*proj_model.ProjectView, error) {
|
func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (*proj_model.ProjectView, error) {
|
||||||
@@ -64,8 +67,13 @@ func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (*proj_mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) {
|
func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) {
|
||||||
|
ctxData := authz.GetCtxData(ctx)
|
||||||
|
iam, err := repo.IAMEvents.IAMByID(ctx, repo.IAMID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
project := &proj_model.Project{Name: name}
|
project := &proj_model.Project{Name: name}
|
||||||
return repo.ProjectEvents.CreateProject(ctx, project)
|
return repo.ProjectEvents.CreateProject(ctx, project, iam.GlobalOrgID == ctxData.OrgID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
|
func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
|
||||||
@@ -612,14 +620,22 @@ func (repo *ProjectRepo) SearchProjectGrantMembers(ctx context.Context, request
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProjectRepo) GetProjectMemberRoles() []string {
|
func (repo *ProjectRepo) GetProjectMemberRoles(ctx context.Context) ([]string, error) {
|
||||||
|
iam, err := repo.IAMEvents.IAMByID(ctx, repo.IAMID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
roles := make([]string, 0)
|
roles := make([]string, 0)
|
||||||
|
global := authz.GetCtxData(ctx).OrgID == iam.GlobalOrgID
|
||||||
for _, roleMap := range repo.Roles {
|
for _, roleMap := range repo.Roles {
|
||||||
if strings.HasPrefix(roleMap, "PROJECT") && !strings.HasPrefix(roleMap, "PROJECT_GRANT") {
|
if strings.HasPrefix(roleMap, "PROJECT") && !strings.HasPrefix(roleMap, "PROJECT_GRANT") {
|
||||||
|
if global && !strings.HasSuffix(roleMap, "GLOBAL") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
roles = append(roles, roleMap)
|
roles = append(roles, roleMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return roles
|
return roles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ProjectRepo) GetProjectGrantMemberRoles() []string {
|
func (repo *ProjectRepo) GetProjectGrantMemberRoles() []string {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string) (*EsRe
|
|||||||
return &EsRepository{
|
return &EsRepository{
|
||||||
spooler: spool,
|
spooler: spool,
|
||||||
OrgRepository: eventstore.OrgRepository{conf.SearchLimit, org, user, view, roles, systemDefaults},
|
OrgRepository: eventstore.OrgRepository{conf.SearchLimit, org, user, view, roles, systemDefaults},
|
||||||
ProjectRepo: eventstore.ProjectRepo{es, conf.SearchLimit, project, usergrant, user, view, roles},
|
ProjectRepo: eventstore.ProjectRepo{es, conf.SearchLimit, project, usergrant, user, iam, view, roles, systemDefaults.IamID},
|
||||||
UserRepo: eventstore.UserRepo{conf.SearchLimit, user, policy, org, view, systemDefaults},
|
UserRepo: eventstore.UserRepo{conf.SearchLimit, user, policy, org, view, systemDefaults},
|
||||||
UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, usergrant, view},
|
UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, usergrant, view},
|
||||||
PolicyRepo: eventstore.PolicyRepo{policy},
|
PolicyRepo: eventstore.PolicyRepo{policy},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type ProjectRepository interface {
|
|||||||
ChangeProjectMember(ctx context.Context, member *model.ProjectMember) (*model.ProjectMember, error)
|
ChangeProjectMember(ctx context.Context, member *model.ProjectMember) (*model.ProjectMember, error)
|
||||||
RemoveProjectMember(ctx context.Context, projectID, userID string) error
|
RemoveProjectMember(ctx context.Context, projectID, userID string) error
|
||||||
SearchProjectMembers(ctx context.Context, request *model.ProjectMemberSearchRequest) (*model.ProjectMemberSearchResponse, error)
|
SearchProjectMembers(ctx context.Context, request *model.ProjectMemberSearchRequest) (*model.ProjectMemberSearchResponse, error)
|
||||||
GetProjectMemberRoles() []string
|
GetProjectMemberRoles(ctx context.Context) ([]string, error)
|
||||||
|
|
||||||
AddProjectRole(ctx context.Context, role *model.ProjectRole) (*model.ProjectRole, error)
|
AddProjectRole(ctx context.Context, role *model.ProjectRole) (*model.ProjectRole, error)
|
||||||
ChangeProjectRole(ctx context.Context, role *model.ProjectRole) (*model.ProjectRole, error)
|
ChangeProjectRole(ctx context.Context, role *model.ProjectRole) (*model.ProjectRole, error)
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
projectOwnerRole = "PROJECT_OWNER"
|
projectOwnerRole = "PROJECT_OWNER"
|
||||||
|
projectOwnerGlobalRole = "PROJECT_OWNER_GLOBAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectEventstore struct {
|
type ProjectEventstore struct {
|
||||||
@@ -78,7 +79,7 @@ func (es *ProjectEventstore) ProjectEventsByID(ctx context.Context, id string, s
|
|||||||
return es.FilterEvents(ctx, query)
|
return es.FilterEvents(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
|
func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_model.Project, global bool) (*proj_model.Project, error) {
|
||||||
if !project.IsValid() {
|
if !project.IsValid() {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-IOVCC", "Errors.Project.Invalid")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-IOVCC", "Errors.Project.Invalid")
|
||||||
}
|
}
|
||||||
@@ -89,9 +90,13 @@ func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_mo
|
|||||||
project.AggregateID = id
|
project.AggregateID = id
|
||||||
project.State = proj_model.ProjectStateActive
|
project.State = proj_model.ProjectStateActive
|
||||||
repoProject := model.ProjectFromModel(project)
|
repoProject := model.ProjectFromModel(project)
|
||||||
|
projectRole := projectOwnerRole
|
||||||
|
if global {
|
||||||
|
projectRole = projectOwnerGlobalRole
|
||||||
|
}
|
||||||
member := &model.ProjectMember{
|
member := &model.ProjectMember{
|
||||||
UserID: authz.GetCtxData(ctx).UserID,
|
UserID: authz.GetCtxData(ctx).UserID,
|
||||||
Roles: []string{projectOwnerRole},
|
Roles: []string{projectRole},
|
||||||
}
|
}
|
||||||
|
|
||||||
createAggregate := ProjectCreateAggregate(es.AggregateCreator(), repoProject, member)
|
createAggregate := ProjectCreateAggregate(es.AggregateCreator(), repoProject, member)
|
||||||
|
|||||||
@@ -82,9 +82,11 @@ func TestCreateProject(t *testing.T) {
|
|||||||
es *ProjectEventstore
|
es *ProjectEventstore
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
project *model.Project
|
project *model.Project
|
||||||
|
global bool
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
project *model.Project
|
project *model.Project
|
||||||
|
role string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
errFunc func(err error) bool
|
errFunc func(err error) bool
|
||||||
}
|
}
|
||||||
@@ -102,6 +104,20 @@ func TestCreateProject(t *testing.T) {
|
|||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
project: &model.Project{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Name: "Name"},
|
project: &model.Project{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Name: "Name"},
|
||||||
|
role: projectOwnerRole,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create global project, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateProject(ctrl),
|
||||||
|
ctx: authz.NewMockContext("orgID", "userID"),
|
||||||
|
project: &model.Project{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Name: "Name"},
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
project: &model.Project{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Name: "Name"},
|
||||||
|
role: projectOwnerGlobalRole,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -119,7 +135,7 @@ func TestCreateProject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result, err := tt.args.es.CreateProject(tt.args.ctx, tt.args.project)
|
result, err := tt.args.es.CreateProject(tt.args.ctx, tt.args.project, tt.args.global)
|
||||||
|
|
||||||
if !tt.res.wantErr && result.AggregateID == "" {
|
if !tt.res.wantErr && result.AggregateID == "" {
|
||||||
t.Errorf("result has no id")
|
t.Errorf("result has no id")
|
||||||
@@ -127,6 +143,9 @@ func TestCreateProject(t *testing.T) {
|
|||||||
if !tt.res.wantErr && result.Name != tt.res.project.Name {
|
if !tt.res.wantErr && result.Name != tt.res.project.Name {
|
||||||
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.Name, result.Name)
|
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.Name, result.Name)
|
||||||
}
|
}
|
||||||
|
if !tt.res.wantErr && result.Members[0].Roles[0] != tt.res.role {
|
||||||
|
t.Errorf("got wrong result role: expected: %v, actual: %v ", tt.res.role, result.Members[0].Roles[0])
|
||||||
|
}
|
||||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
t.Errorf("got wrong err: %v ", err)
|
t.Errorf("got wrong err: %v ", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ func (setUp *initializer) project(ctx context.Context, project Project) (*proj_m
|
|||||||
addProject := &proj_model.Project{
|
addProject := &proj_model.Project{
|
||||||
Name: project.Name,
|
Name: project.Name,
|
||||||
}
|
}
|
||||||
return setUp.ProjectEvents.CreateProject(ctx, addProject)
|
return setUp.ProjectEvents.CreateProject(ctx, addProject, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (setUp *initializer) oidcApp(ctx context.Context, project *proj_model.Project, oidc OIDCApp) (*proj_model.Application, error) {
|
func (setUp *initializer) oidcApp(ctx context.Context, project *proj_model.Project, oidc OIDCApp) (*proj_model.Application, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user