feat: project roles (#843)

* fix logging

* token verification

* feat: assert roles

* feat: add project role assertion on project and token type on app

* id and access token role assertion

* add project role check

* user grant required step in login

* update library

* fix merge

* fix merge

* fix merge

* update oidc library

* fix tests

* add tests for GrantRequiredStep

* add missing field ProjectRoleCheck on project view model

* fix project create

* fix project create
This commit is contained in:
Livio Amstutz
2020-10-16 07:49:38 +02:00
committed by GitHub
parent f5a7a0a09f
commit a321d850ae
57 changed files with 10894 additions and 18297 deletions

View File

@@ -7,12 +7,14 @@ import (
)
type ApplicationView struct {
ID string
ProjectID string
Name string
CreationDate time.Time
ChangeDate time.Time
State AppState
ID string
ProjectID string
Name string
CreationDate time.Time
ChangeDate time.Time
State AppState
ProjectRoleAssertion bool
ProjectRoleCheck bool
IsOIDC bool
OIDCVersion OIDCVersion
@@ -27,6 +29,9 @@ type ApplicationView struct {
ComplianceProblems []string
DevMode bool
OriginAllowList []string
AccessTokenType OIDCTokenType
IDTokenRoleAssertion bool
AccessTokenRoleAssertion bool
Sequence uint64
}

View File

@@ -21,19 +21,22 @@ const (
type OIDCConfig struct {
es_models.ObjectRoot
AppID string
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretString string
RedirectUris []string
ResponseTypes []OIDCResponseType
GrantTypes []OIDCGrantType
ApplicationType OIDCApplicationType
AuthMethodType OIDCAuthMethodType
PostLogoutRedirectUris []string
OIDCVersion OIDCVersion
Compliance *Compliance
DevMode bool
AppID string
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretString string
RedirectUris []string
ResponseTypes []OIDCResponseType
GrantTypes []OIDCGrantType
ApplicationType OIDCApplicationType
AuthMethodType OIDCAuthMethodType
PostLogoutRedirectUris []string
OIDCVersion OIDCVersion
Compliance *Compliance
DevMode bool
AccessTokenType OIDCTokenType
AccessTokenRoleAssertion bool
IDTokenRoleAssertion bool
}
type OIDCVersion int32
@@ -79,6 +82,13 @@ type Compliance struct {
Problems []string
}
type OIDCTokenType int32
const (
OIDCTokenTypeBearer OIDCTokenType = iota
OIDCTokenTypeJWT
)
func (c *OIDCConfig) IsValid() bool {
grantTypes := c.getRequiredGrantTypes()
for _, grantType := range grantTypes {

View File

@@ -1,19 +1,22 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/golang/protobuf/ptypes/timestamp"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
type Project struct {
es_models.ObjectRoot
State ProjectState
Name string
Members []*ProjectMember
Roles []*ProjectRole
Applications []*Application
Grants []*ProjectGrant
State ProjectState
Name string
Members []*ProjectMember
Roles []*ProjectRole
Applications []*Application
Grants []*ProjectGrant
ProjectRoleAssertion bool
ProjectRoleCheck bool
}
type ProjectChanges struct {
Changes []*ProjectChange

View File

@@ -1,18 +1,21 @@
package model
import (
"github.com/caos/zitadel/internal/model"
"time"
"github.com/caos/zitadel/internal/model"
)
type ProjectView struct {
ProjectID string
Name string
CreationDate time.Time
ChangeDate time.Time
State ProjectState
ResourceOwner string
Sequence uint64
ProjectID string
Name string
CreationDate time.Time
ChangeDate time.Time
State ProjectState
ResourceOwner string
ProjectRoleAssertion bool
ProjectRoleCheck bool
Sequence uint64
}
type ProjectViewSearchRequest struct {

View File

@@ -321,7 +321,7 @@ func (es *ProjectEventstore) AddProjectRoles(ctx context.Context, roles ...*proj
}
for _, role := range roles {
if !role.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-idue3", "Errors.Project.MemberInvalid")
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-iduG4", "Errors.Project.RoleInvalid")
}
}
existingProject, err := es.ProjectByID(ctx, roles[0].AggregateID)

View File

@@ -2,26 +2,31 @@ package model
import (
"encoding/json"
"reflect"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"reflect"
)
type OIDCConfig struct {
es_models.ObjectRoot
Version int32 `json:"oidcVersion,omitempty"`
AppID string `json:"appId"`
ClientID string `json:"clientId,omitempty"`
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
RedirectUris []string `json:"redirectUris,omitempty"`
ResponseTypes []int32 `json:"responseTypes,omitempty"`
GrantTypes []int32 `json:"grantTypes,omitempty"`
ApplicationType int32 `json:"applicationType,omitempty"`
AuthMethodType int32 `json:"authMethodType,omitempty"`
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris,omitempty"`
DevMode bool `json:"devMode,omitempty"`
Version int32 `json:"oidcVersion,omitempty"`
AppID string `json:"appId"`
ClientID string `json:"clientId,omitempty"`
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
RedirectUris []string `json:"redirectUris,omitempty"`
ResponseTypes []int32 `json:"responseTypes,omitempty"`
GrantTypes []int32 `json:"grantTypes,omitempty"`
ApplicationType int32 `json:"applicationType,omitempty"`
AuthMethodType int32 `json:"authMethodType,omitempty"`
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris,omitempty"`
DevMode bool `json:"devMode,omitempty"`
AccessTokenType int32 `json:"accessTokenType,omitempty"`
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion,omitempty"`
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"`
}
func (c *OIDCConfig) Changes(changed *OIDCConfig) map[string]interface{} {
@@ -51,6 +56,15 @@ func (c *OIDCConfig) Changes(changed *OIDCConfig) map[string]interface{} {
if c.DevMode != changed.DevMode {
changes["devMode"] = changed.DevMode
}
if c.AccessTokenType != changed.AccessTokenType {
changes["accessTokenType"] = changed.AccessTokenType
}
if c.AccessTokenRoleAssertion != changed.AccessTokenRoleAssertion {
changes["accessTokenRoleAssertion"] = changed.AccessTokenRoleAssertion
}
if c.IDTokenRoleAssertion != changed.IDTokenRoleAssertion {
changes["idTokenRoleAssertion"] = changed.IDTokenRoleAssertion
}
return changes
}
@@ -64,18 +78,21 @@ func OIDCConfigFromModel(config *model.OIDCConfig) *OIDCConfig {
grantTypes[i] = int32(rt)
}
return &OIDCConfig{
ObjectRoot: config.ObjectRoot,
AppID: config.AppID,
Version: int32(config.OIDCVersion),
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
RedirectUris: config.RedirectUris,
ResponseTypes: responseTypes,
GrantTypes: grantTypes,
ApplicationType: int32(config.ApplicationType),
AuthMethodType: int32(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris,
DevMode: config.DevMode,
ObjectRoot: config.ObjectRoot,
AppID: config.AppID,
Version: int32(config.OIDCVersion),
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
RedirectUris: config.RedirectUris,
ResponseTypes: responseTypes,
GrantTypes: grantTypes,
ApplicationType: int32(config.ApplicationType),
AuthMethodType: int32(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris,
DevMode: config.DevMode,
AccessTokenType: int32(config.AccessTokenType),
AccessTokenRoleAssertion: config.AccessTokenRoleAssertion,
IDTokenRoleAssertion: config.IDTokenRoleAssertion,
}
}
@@ -89,18 +106,21 @@ func OIDCConfigToModel(config *OIDCConfig) *model.OIDCConfig {
grantTypes[i] = model.OIDCGrantType(rt)
}
oidcConfig := &model.OIDCConfig{
ObjectRoot: config.ObjectRoot,
AppID: config.AppID,
OIDCVersion: model.OIDCVersion(config.Version),
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
RedirectUris: config.RedirectUris,
ResponseTypes: responseTypes,
GrantTypes: grantTypes,
ApplicationType: model.OIDCApplicationType(config.ApplicationType),
AuthMethodType: model.OIDCAuthMethodType(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris,
DevMode: config.DevMode,
ObjectRoot: config.ObjectRoot,
AppID: config.AppID,
OIDCVersion: model.OIDCVersion(config.Version),
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
RedirectUris: config.RedirectUris,
ResponseTypes: responseTypes,
GrantTypes: grantTypes,
ApplicationType: model.OIDCApplicationType(config.ApplicationType),
AuthMethodType: model.OIDCAuthMethodType(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris,
DevMode: config.DevMode,
AccessTokenType: model.OIDCTokenType(config.AccessTokenType),
AccessTokenRoleAssertion: config.AccessTokenRoleAssertion,
IDTokenRoleAssertion: config.IDTokenRoleAssertion,
}
oidcConfig.FillCompliance()
return oidcConfig

View File

@@ -2,7 +2,9 @@ package model
import (
"encoding/json"
"github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
)
@@ -13,12 +15,14 @@ const (
type Project struct {
es_models.ObjectRoot
Name string `json:"name,omitempty"`
State int32 `json:"-"`
Members []*ProjectMember `json:"-"`
Roles []*ProjectRole `json:"-"`
Applications []*Application `json:"-"`
Grants []*ProjectGrant `json:"-"`
Name string `json:"name,omitempty"`
ProjectRoleAssertion bool `json:"projectRoleAssertion,omitempty"`
ProjectRoleCheck bool `json:"projectRoleCheck,omitempty"`
State int32 `json:"-"`
Members []*ProjectMember `json:"-"`
Roles []*ProjectRole `json:"-"`
Applications []*Application `json:"-"`
Grants []*ProjectGrant `json:"-"`
}
func GetProject(projects []*Project, id string) (int, *Project) {
@@ -35,6 +39,12 @@ func (p *Project) Changes(changed *Project) map[string]interface{} {
if changed.Name != "" && p.Name != changed.Name {
changes["name"] = changed.Name
}
if p.ProjectRoleAssertion != changed.ProjectRoleAssertion {
changes["projectRoleAssertion"] = changed.ProjectRoleAssertion
}
if p.ProjectRoleCheck != changed.ProjectRoleCheck {
changes["projectRoleCheck"] = changed.ProjectRoleCheck
}
return changes
}
@@ -44,13 +54,15 @@ func ProjectFromModel(project *model.Project) *Project {
apps := AppsFromModel(project.Applications)
grants := GrantsFromModel(project.Grants)
return &Project{
ObjectRoot: project.ObjectRoot,
Name: project.Name,
State: int32(project.State),
Members: members,
Roles: roles,
Applications: apps,
Grants: grants,
ObjectRoot: project.ObjectRoot,
Name: project.Name,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
State: int32(project.State),
Members: members,
Roles: roles,
Applications: apps,
Grants: grants,
}
}
@@ -60,13 +72,15 @@ func ProjectToModel(project *Project) *model.Project {
apps := AppsToModel(project.Applications)
grants := GrantsToModel(project.Grants)
return &model.Project{
ObjectRoot: project.ObjectRoot,
Name: project.Name,
State: model.ProjectState(project.State),
Members: members,
Roles: roles,
Applications: apps,
Grants: grants,
ObjectRoot: project.ObjectRoot,
Name: project.Name,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
State: model.ProjectState(project.State),
Members: members,
Roles: roles,
Applications: apps,
Grants: grants,
}
}

View File

@@ -69,6 +69,15 @@ func PutApplication(db *gorm.DB, table string, app *model.ApplicationView) error
return save(db, app)
}
func PutApplications(db *gorm.DB, table string, apps ...*model.ApplicationView) error {
save := repository.PrepareBulkSave(table)
s := make([]interface{}, len(apps))
for i, app := range apps {
s[i] = app
}
return save(db, s...)
}
func DeleteApplication(db *gorm.DB, table, appID string) error {
delete := repository.PrepareDeleteByKey(table, model.ApplicationSearchKey(proj_model.AppSearchKeyAppID), appID)
return delete(db)

View File

@@ -23,12 +23,14 @@ const (
)
type ApplicationView struct {
ID string `json:"appId" gorm:"column:id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"`
Name string `json:"name" gorm:"column:app_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:app_state"`
ID string `json:"appId" gorm:"column:id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"`
Name string `json:"name" gorm:"column:app_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:app_state"`
ProjectRoleAssertion bool `json:"projectRoleAssertion" gorm:"column:project_role_assertion"`
ProjectRoleCheck bool `json:"projectRoleCheck" gorm:"column:project_role_check"`
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCVersion int32 `json:"oidcVersion" gorm:"column:oidc_version"`
@@ -43,58 +45,24 @@ type ApplicationView struct {
ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"`
DevMode bool `json:"devMode" gorm:"column:dev_mode"`
OriginAllowList pq.StringArray `json:"-" gorm:"column:origin_allow_list"`
AccessTokenType int32 `json:"accessTokenType" gorm:"column:access_token_type"`
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion" gorm:"column:access_token_role_assertion"`
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion" gorm:"column:id_token_role_assertion"`
Sequence uint64 `json:"-" gorm:"sequence"`
}
func ApplicationViewFromModel(app *model.ApplicationView) *ApplicationView {
return &ApplicationView{
ID: app.ID,
ProjectID: app.ProjectID,
Name: app.Name,
State: int32(app.State),
Sequence: app.Sequence,
CreationDate: app.CreationDate,
ChangeDate: app.ChangeDate,
IsOIDC: app.IsOIDC,
OIDCClientID: app.OIDCClientID,
OIDCRedirectUris: app.OIDCRedirectUris,
OIDCResponseTypes: OIDCResponseTypesFromModel(app.OIDCResponseTypes),
OIDCGrantTypes: OIDCGrantTypesFromModel(app.OIDCGrantTypes),
OIDCApplicationType: int32(app.OIDCApplicationType),
OIDCAuthMethodType: int32(app.OIDCAuthMethodType),
OIDCPostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList,
}
}
func OIDCResponseTypesFromModel(oidctypes []model.OIDCResponseType) []int64 {
result := make([]int64, len(oidctypes))
for i, t := range oidctypes {
result[i] = int64(t)
}
return result
}
func OIDCGrantTypesFromModel(granttypes []model.OIDCGrantType) []int64 {
result := make([]int64, len(granttypes))
for i, t := range granttypes {
result[i] = int64(t)
}
return result
}
func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView {
return &model.ApplicationView{
ID: app.ID,
ProjectID: app.ProjectID,
Name: app.Name,
State: model.AppState(app.State),
Sequence: app.Sequence,
CreationDate: app.CreationDate,
ChangeDate: app.ChangeDate,
ID: app.ID,
ProjectID: app.ProjectID,
Name: app.Name,
State: model.AppState(app.State),
Sequence: app.Sequence,
CreationDate: app.CreationDate,
ChangeDate: app.ChangeDate,
ProjectRoleAssertion: app.ProjectRoleAssertion,
ProjectRoleCheck: app.ProjectRoleCheck,
IsOIDC: app.IsOIDC,
OIDCVersion: model.OIDCVersion(app.OIDCVersion),
@@ -109,6 +77,9 @@ func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView {
ComplianceProblems: app.ComplianceProblems,
DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList,
AccessTokenType: model.OIDCTokenType(app.AccessTokenType),
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
IDTokenRoleAssertion: app.IDTokenRoleAssertion,
}
}
@@ -152,6 +123,8 @@ func (a *ApplicationView) AppendEventIfMyApp(event *models.Event) (err error) {
}
case es_model.ApplicationRemoved:
return view.SetData(event)
case es_model.ProjectChanged:
return a.AppendEvent(event)
case es_model.ProjectRemoved:
return a.AppendEvent(event)
default:
@@ -187,6 +160,8 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
}
a.setCompliance()
return a.setOriginAllowList()
case es_model.ProjectChanged:
return a.SetData(event)
case es_model.ApplicationDeactivated:
a.State = int32(model.AppStateInactive)
case es_model.ApplicationReactivated:

View File

@@ -2,12 +2,14 @@ package model
import (
"encoding/json"
"time"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"time"
)
const (
@@ -17,36 +19,42 @@ const (
)
type ProjectView struct {
ProjectID string `json:"-" gorm:"column:project_id;primary_key"`
Name string `json:"name" gorm:"column:project_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:project_state"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
ProjectID string `json:"-" gorm:"column:project_id;primary_key"`
Name string `json:"name" gorm:"column:project_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:project_state"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
ProjectRoleAssertion bool `json:"projectRoleAssertion" gorm:"column:project_role_assertion"`
ProjectRoleCheck bool `json:"projectRoleCheck" gorm:"column:project_role_check"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func ProjectFromModel(project *model.ProjectView) *ProjectView {
return &ProjectView{
ProjectID: project.ProjectID,
Name: project.Name,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
State: int32(project.State),
ResourceOwner: project.ResourceOwner,
Sequence: project.Sequence,
ProjectID: project.ProjectID,
Name: project.Name,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
State: int32(project.State),
ResourceOwner: project.ResourceOwner,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
Sequence: project.Sequence,
}
}
func ProjectToModel(project *ProjectView) *model.ProjectView {
return &model.ProjectView{
ProjectID: project.ProjectID,
Name: project.Name,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
State: model.ProjectState(project.State),
ResourceOwner: project.ResourceOwner,
Sequence: project.Sequence,
ProjectID: project.ProjectID,
Name: project.Name,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
State: model.ProjectState(project.State),
ResourceOwner: project.ResourceOwner,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
Sequence: project.Sequence,
}
}