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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 10894 additions and 18297 deletions

3
go.mod
View File

@ -15,7 +15,7 @@ require (
github.com/aws/aws-sdk-go v1.34.24 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
github.com/caos/logging v0.0.2
github.com/caos/oidc v0.11.1
github.com/caos/oidc v0.12.0
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cockroachdb/cockroach-go/v2 v2.0.7
github.com/envoyproxy/protoc-gen-validate v0.4.1
@ -37,7 +37,6 @@ require (
github.com/kevinburke/go.uuid v1.2.0 // indirect
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
github.com/kevinburke/twilio-go v0.0.0-20200810163702-320748330fac
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
github.com/lib/pq v1.8.0
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect

19
go.sum
View File

@ -71,12 +71,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/oidc v0.10.0 h1:/GKyQgKHvkc2jNCXzmdJs9hCXDYdArCMm3d5FXaiNWA=
github.com/caos/oidc v0.10.0/go.mod h1:RREtWSRzH/mXQXJkxB63mFDZ/RUNyzoU6czd6UJfvJI=
github.com/caos/oidc v0.11.0 h1:larwR0ur4hcHXkMtlXbHApv4DUr5yu/zbxBeGjcpTXk=
github.com/caos/oidc v0.11.0/go.mod h1:R9UKITZmSo5vNhSLUYcTDH8pAaV2xwPASXg8wpEs2xQ=
github.com/caos/oidc v0.11.1 h1:7Nkup+fiU/zZVN61BfGOTzuloD7aOdqA3V9eNrJv5xc=
github.com/caos/oidc v0.11.1/go.mod h1:R9UKITZmSo5vNhSLUYcTDH8pAaV2xwPASXg8wpEs2xQ=
github.com/caos/oidc v0.12.0 h1:BcEwgeq8fpum2hdc47ZDOTwnc4KdHpos1NRbE1JcHN8=
github.com/caos/oidc v0.12.0/go.mod h1:R9UKITZmSo5vNhSLUYcTDH8pAaV2xwPASXg8wpEs2xQ=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -280,8 +276,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -349,8 +343,6 @@ github.com/shopspring/decimal v0.0.0-20200419222939-1884f454f8ea/go.mod h1:DKyhr
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
@ -409,8 +401,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -484,8 +474,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -546,9 +534,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zr
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a h1:bhXnJ7fn2SiL+C8iOWPfNBJKDTjUByftpPW7b9CX94U=
golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -52,8 +52,11 @@ func (m *IamMember) processIamMember(event *models.Event) (err error) {
member := new(iam_model.IAMMemberView)
switch event.Type {
case model.IAMMemberAdded:
member.AppendEvent(event)
m.fillData(member)
err = member.AppendEvent(event)
if err != nil {
return err
}
err = m.fillData(member)
case model.IAMMemberChanged:
err := member.SetData(event)
if err != nil {
@ -63,7 +66,7 @@ func (m *IamMember) processIamMember(event *models.Event) (err error) {
if err != nil {
return err
}
member.AppendEvent(event)
err = member.AppendEvent(event)
case model.IAMMemberRemoved:
err := member.SetData(event)
if err != nil {

View File

@ -35,7 +35,10 @@ func (o *Org) Reduce(event *es_models.Event) error {
switch event.Type {
case model.OrgAdded:
org.AppendEvent(event)
err := org.AppendEvent(event)
if err != nil {
return err
}
case model.OrgChanged:
err := org.SetData(event)
if err != nil {

View File

@ -44,34 +44,40 @@ func appConfigFromModel(app *proj_model.Application) management.AppConfig {
func oidcConfigFromModel(config *proj_model.OIDCConfig) *management.OIDCConfig {
return &management.OIDCConfig{
RedirectUris: config.RedirectUris,
ResponseTypes: oidcResponseTypesFromModel(config.ResponseTypes),
GrantTypes: oidcGrantTypesFromModel(config.GrantTypes),
ApplicationType: oidcApplicationTypeFromModel(config.ApplicationType),
ClientId: config.ClientID,
ClientSecret: config.ClientSecretString,
AuthMethodType: oidcAuthMethodTypeFromModel(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris,
Version: oidcVersionFromModel(config.OIDCVersion),
NoneCompliant: config.Compliance.NoneCompliant,
ComplianceProblems: complianceProblemsToLocalizedMessages(config.Compliance.Problems),
DevMode: config.DevMode,
RedirectUris: config.RedirectUris,
ResponseTypes: oidcResponseTypesFromModel(config.ResponseTypes),
GrantTypes: oidcGrantTypesFromModel(config.GrantTypes),
ApplicationType: oidcApplicationTypeFromModel(config.ApplicationType),
ClientId: config.ClientID,
ClientSecret: config.ClientSecretString,
AuthMethodType: oidcAuthMethodTypeFromModel(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris,
Version: oidcVersionFromModel(config.OIDCVersion),
NoneCompliant: config.Compliance.NoneCompliant,
ComplianceProblems: complianceProblemsToLocalizedMessages(config.Compliance.Problems),
DevMode: config.DevMode,
AccessTokenType: oidcTokenTypeFromModel(config.AccessTokenType),
AccessTokenRoleAssertion: config.AccessTokenRoleAssertion,
IdTokenRoleAssertion: config.IDTokenRoleAssertion,
}
}
func oidcConfigFromApplicationViewModel(app *proj_model.ApplicationView) *management.OIDCConfig {
return &management.OIDCConfig{
RedirectUris: app.OIDCRedirectUris,
ResponseTypes: oidcResponseTypesFromModel(app.OIDCResponseTypes),
GrantTypes: oidcGrantTypesFromModel(app.OIDCGrantTypes),
ApplicationType: oidcApplicationTypeFromModel(app.OIDCApplicationType),
ClientId: app.OIDCClientID,
AuthMethodType: oidcAuthMethodTypeFromModel(app.OIDCAuthMethodType),
PostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
Version: oidcVersionFromModel(app.OIDCVersion),
NoneCompliant: app.NoneCompliant,
ComplianceProblems: complianceProblemsToLocalizedMessages(app.ComplianceProblems),
DevMode: app.DevMode,
RedirectUris: app.OIDCRedirectUris,
ResponseTypes: oidcResponseTypesFromModel(app.OIDCResponseTypes),
GrantTypes: oidcGrantTypesFromModel(app.OIDCGrantTypes),
ApplicationType: oidcApplicationTypeFromModel(app.OIDCApplicationType),
ClientId: app.OIDCClientID,
AuthMethodType: oidcAuthMethodTypeFromModel(app.OIDCAuthMethodType),
PostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
Version: oidcVersionFromModel(app.OIDCVersion),
NoneCompliant: app.NoneCompliant,
ComplianceProblems: complianceProblemsToLocalizedMessages(app.ComplianceProblems),
DevMode: app.DevMode,
AccessTokenType: oidcTokenTypeFromModel(app.AccessTokenType),
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
IdTokenRoleAssertion: app.IDTokenRoleAssertion,
}
}
@ -92,14 +98,17 @@ func oidcAppCreateToModel(app *management.OIDCApplicationCreate) *proj_model.App
Name: app.Name,
Type: proj_model.AppTypeOIDC,
OIDCConfig: &proj_model.OIDCConfig{
OIDCVersion: oidcVersionToModel(app.Version),
RedirectUris: app.RedirectUris,
ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes),
GrantTypes: oidcGrantTypesToModel(app.GrantTypes),
ApplicationType: oidcApplicationTypeToModel(app.ApplicationType),
AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType),
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
DevMode: app.DevMode,
OIDCVersion: oidcVersionToModel(app.Version),
RedirectUris: app.RedirectUris,
ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes),
GrantTypes: oidcGrantTypesToModel(app.GrantTypes),
ApplicationType: oidcApplicationTypeToModel(app.ApplicationType),
AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType),
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
DevMode: app.DevMode,
AccessTokenType: oidcTokenTypeToModel(app.AccessTokenType),
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
IDTokenRoleAssertion: app.IdTokenRoleAssertion,
},
}
}
@ -119,14 +128,17 @@ func oidcConfigUpdateToModel(app *management.OIDCConfigUpdate) *proj_model.OIDCC
ObjectRoot: models.ObjectRoot{
AggregateID: app.ProjectId,
},
AppID: app.ApplicationId,
RedirectUris: app.RedirectUris,
ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes),
GrantTypes: oidcGrantTypesToModel(app.GrantTypes),
ApplicationType: oidcApplicationTypeToModel(app.ApplicationType),
AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType),
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
DevMode: app.DevMode,
AppID: app.ApplicationId,
RedirectUris: app.RedirectUris,
ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes),
GrantTypes: oidcGrantTypesToModel(app.GrantTypes),
ApplicationType: oidcApplicationTypeToModel(app.ApplicationType),
AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType),
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
DevMode: app.DevMode,
AccessTokenType: oidcTokenTypeToModel(app.AccessTokenType),
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
IDTokenRoleAssertion: app.IdTokenRoleAssertion,
}
}
@ -351,6 +363,28 @@ func oidcAuthMethodTypeFromModel(authType proj_model.OIDCAuthMethodType) managem
}
}
func oidcTokenTypeToModel(tokenType management.OIDCTokenType) proj_model.OIDCTokenType {
switch tokenType {
case management.OIDCTokenType_OIDCTokenType_Bearer:
return proj_model.OIDCTokenTypeBearer
case management.OIDCTokenType_OIDCTokenType_JWT:
return proj_model.OIDCTokenTypeJWT
default:
return proj_model.OIDCTokenTypeBearer
}
}
func oidcTokenTypeFromModel(tokenType proj_model.OIDCTokenType) management.OIDCTokenType {
switch tokenType {
case proj_model.OIDCTokenTypeBearer:
return management.OIDCTokenType_OIDCTokenType_Bearer
case proj_model.OIDCTokenTypeJWT:
return management.OIDCTokenType_OIDCTokenType_JWT
default:
return management.OIDCTokenType_OIDCTokenType_Bearer
}
}
func oidcVersionFromModel(version proj_model.OIDCVersion) management.OIDCVersion {
switch version {
case proj_model.OIDCVersionV1:

View File

@ -12,7 +12,7 @@ import (
)
func (s *Server) CreateProject(ctx context.Context, in *management.ProjectCreateRequest) (*management.Project, error) {
project, err := s.project.CreateProject(ctx, in.Name)
project, err := s.project.CreateProject(ctx, projectCreateToModel(in))
if err != nil {
return nil, err
}

View File

@ -22,12 +22,14 @@ func projectFromModel(project *proj_model.Project) *management.Project {
logging.Log("GRPC-di7rw").OnError(err).Debug("unable to parse timestamp")
return &management.Project{
Id: project.AggregateID,
State: projectStateFromModel(project.State),
CreationDate: creationDate,
ChangeDate: changeDate,
Name: project.Name,
Sequence: project.Sequence,
Id: project.AggregateID,
State: projectStateFromModel(project.State),
CreationDate: creationDate,
ChangeDate: changeDate,
Name: project.Name,
Sequence: project.Sequence,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
}
}
@ -60,13 +62,15 @@ func projectViewFromModel(project *proj_model.ProjectView) *management.ProjectVi
logging.Log("GRPC-sope3").OnError(err).Debug("unable to parse timestamp")
return &management.ProjectView{
ProjectId: project.ProjectID,
State: projectStateFromModel(project.State),
CreationDate: creationDate,
ChangeDate: changeDate,
Name: project.Name,
Sequence: project.Sequence,
ResourceOwner: project.ResourceOwner,
ProjectId: project.ProjectID,
State: projectStateFromModel(project.State),
CreationDate: creationDate,
ChangeDate: changeDate,
Name: project.Name,
Sequence: project.Sequence,
ResourceOwner: project.ResourceOwner,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
}
}
@ -117,12 +121,22 @@ func projectStateFromModel(state proj_model.ProjectState) management.ProjectStat
}
}
func projectCreateToModel(project *management.ProjectCreateRequest) *proj_model.Project {
return &proj_model.Project{
Name: project.Name,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
}
}
func projectUpdateToModel(project *management.ProjectUpdateRequest) *proj_model.Project {
return &proj_model.Project{
ObjectRoot: models.ObjectRoot{
AggregateID: project.Id,
},
Name: project.Name,
Name: project.Name,
ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
}
}

View File

@ -135,7 +135,7 @@ func userGrantViewFromModel(grant *grant_model.UserGrantView) *management.UserGr
Email: grant.Email,
ProjectName: grant.ProjectName,
OrgName: grant.OrgName,
OrgDomain: grant.OrgDomain,
OrgDomain: grant.OrgPrimaryDomain,
RoleKeys: grant.RoleKeys,
UserId: grant.UserID,
ProjectId: grant.ProjectID,

View File

@ -3,6 +3,7 @@ package oidc
import (
"context"
"fmt"
"strings"
"time"
"github.com/caos/oidc/pkg/oidc"
@ -11,6 +12,7 @@ import (
"github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
)
@ -19,6 +21,14 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
if !ok {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id")
}
app, err := o.repo.ApplicationByClientID(ctx, req.ClientID)
if err != nil {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-AEG4d", "Errors.Internal")
}
req.Scopes, err = o.assertProjectRoleScopes(app, req.Scopes)
if err != nil {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-Gqrfg", "Errors.Internal")
}
authRequest := CreateAuthRequestToBusiness(ctx, req, userAgentID, userID)
resp, err := o.repo.CreateAuthRequest(ctx, authRequest)
if err != nil {
@ -102,3 +112,22 @@ func (o *OPStorage) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error)
func (o *OPStorage) SaveNewKeyPair(ctx context.Context) error {
return o.repo.GenerateSigningKeyPair(ctx, o.signingKeyAlgorithm)
}
func (o *OPStorage) assertProjectRoleScopes(app *proj_model.ApplicationView, scopes []string) ([]string, error) {
if !app.ProjectRoleAssertion {
return scopes, nil
}
for _, scope := range scopes {
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
return scopes, nil
}
}
roles, err := o.repo.ProjectRolesByProjectID(app.ProjectID)
if err != nil {
return nil, err
}
for _, role := range roles {
scopes = append(scopes, ScopeProjectRolePrefix+role.Key)
}
return scopes, nil
}

View File

@ -2,6 +2,7 @@ package oidc
import (
"context"
"strings"
"golang.org/x/text/language"
"gopkg.in/square/go-jose.v2"
@ -15,6 +16,7 @@ import (
"github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model"
user_model "github.com/caos/zitadel/internal/user/model"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
)
const (
@ -24,6 +26,9 @@ const (
scopePhone = "phone"
scopeAddress = "address"
ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:"
ClaimProjectRoles = "urn:zitadel:iam:org:project:roles"
oidcCtx = "oidc"
)
@ -35,7 +40,15 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Clie
if client.State != proj_model.AppStateActive {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
}
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime)
projectRoles, err := o.repo.ProjectRolesByProjectID(client.ProjectID)
if err != nil {
return nil, err
}
allowedScopes := make([]string, len(projectRoles))
for i, role := range projectRoles {
allowedScopes[i] = ScopeProjectRolePrefix + role.Key
}
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, allowedScopes)
}
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error) {
@ -65,10 +78,10 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr
return o.repo.AuthorizeOIDCApplication(ctx, id, secret)
}
func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (*oidc.Userinfo, error) {
token, err := o.repo.TokenByID(ctx, tokenID, subject)
func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (oidc.UserInfo, error) {
token, err := o.repo.TokenByID(ctx, subject, tokenID)
if err != nil {
return nil, err
return nil, errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
}
if token.ApplicationID != "" {
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
@ -79,65 +92,126 @@ func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject,
return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
}
}
return o.GetUserinfoFromScopes(ctx, token.UserID, token.Scopes)
return o.GetUserinfoFromScopes(ctx, token.UserID, token.ApplicationID, token.Scopes)
}
func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID string, scopes []string) (*oidc.Userinfo, error) {
func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID, applicationID string, scopes []string) (oidc.UserInfo, error) {
user, err := o.repo.UserByID(ctx, userID)
if err != nil {
return nil, err
}
userInfo := new(oidc.Userinfo)
userInfo := oidc.NewUserInfo()
roles := make([]string, 0)
for _, scope := range scopes {
switch scope {
case scopeOpenID:
userInfo.Subject = user.ID
case scopeEmail:
case oidc.ScopeOpenID:
userInfo.SetSubject(user.ID)
case oidc.ScopeEmail:
if user.HumanView == nil {
continue
}
userInfo.Email = user.Email
userInfo.EmailVerified = user.IsEmailVerified
case scopeProfile:
userInfo.PreferredUsername = user.PreferredLoginName
userInfo.UpdatedAt = user.ChangeDate
userInfo.SetEmail(user.Email, user.IsEmailVerified)
case oidc.ScopeProfile:
userInfo.SetPreferredUsername(user.PreferredLoginName)
userInfo.SetUpdatedAt(user.ChangeDate)
if user.HumanView != nil {
userInfo.Name = user.DisplayName
userInfo.FamilyName = user.LastName
userInfo.GivenName = user.FirstName
userInfo.Nickname = user.NickName
userInfo.Gender = oidc.Gender(getGender(user.Gender))
userInfo.Locale, err = language.Parse(user.PreferredLanguage)
userInfo.SetName(user.DisplayName)
userInfo.SetFamilyName(user.LastName)
userInfo.SetGivenName(user.FirstName)
userInfo.SetNickname(user.NickName)
userInfo.SetGender(oidc.Gender(getGender(user.Gender)))
locale, _ := language.Parse(user.PreferredLanguage)
userInfo.SetLocale(locale)
} else {
userInfo.Name = user.MachineView.Name
userInfo.SetName(user.MachineView.Name)
}
case scopePhone:
case oidc.ScopePhone:
if user.HumanView == nil {
continue
}
userInfo.PhoneNumber = user.Phone
userInfo.PhoneNumberVerified = user.IsPhoneVerified
case scopeAddress:
userInfo.SetPhone(user.Phone, user.IsPhoneVerified)
case oidc.ScopeAddress:
if user.HumanView == nil {
continue
}
if user.StreetAddress == "" && user.Locality == "" && user.Region == "" && user.PostalCode == "" && user.Country == "" {
continue
}
userInfo.Address = &oidc.UserinfoAddress{
StreetAddress: user.StreetAddress,
Locality: user.Locality,
Region: user.Region,
PostalCode: user.PostalCode,
Country: user.Country,
}
userInfo.SetAddress(oidc.NewUserInfoAddress(user.StreetAddress, user.Locality, user.Region, user.PostalCode, user.Country, ""))
default:
userInfo.Authorizations = append(userInfo.Authorizations, scope)
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
}
}
}
if len(roles) == 0 || applicationID == "" {
return userInfo, nil
}
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
if err != nil {
return nil, err
}
if len(projectRoles) > 0 {
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
}
return userInfo, nil
}
func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, applicationID string, scopes []string) (claims map[string]interface{}, err error) {
roles := make([]string, 0)
for _, scope := range scopes {
if strings.HasPrefix(scope, ScopeProjectRolePrefix) {
roles = append(roles, strings.TrimPrefix(scope, ScopeProjectRolePrefix))
}
}
if len(roles) == 0 || applicationID == "" {
return nil, nil
}
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
if err != nil {
return nil, err
}
if len(projectRoles) > 0 {
claims = map[string]interface{}{ClaimProjectRoles: projectRoles}
}
return claims, err
}
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (map[string]map[string]string, error) {
app, err := o.repo.ApplicationByClientID(ctx, applicationID)
if err != nil {
return nil, err
}
grants, err := o.repo.UserGrantsByProjectAndUserID(app.ProjectID, userID)
if err != nil {
return nil, err
}
projectRoles := make(map[string]map[string]string)
for _, requestedRole := range requestedRoles {
for _, grant := range grants {
checkGrantedRoles(projectRoles, grant, requestedRole)
}
}
return projectRoles, nil
}
func checkGrantedRoles(roles map[string]map[string]string, grant *grant_model.UserGrantView, requestedRole string) {
for _, grantedRole := range grant.RoleKeys {
if requestedRole == grantedRole {
appendRole(roles, grantedRole, grant.ResourceOwner, grant.OrgPrimaryDomain)
}
}
}
func appendRole(roles map[string]map[string]string, role, orgID, orgPrimaryDomain string) {
if roles[role] == nil {
roles[role] = make(map[string]string, 0)
}
roles[role][orgID] = orgPrimaryDomain
}
func getGender(gender user_model.Gender) string {
switch gender {
case user_model.GenderFemale:

View File

@ -1,9 +1,9 @@
package oidc
import (
"github.com/caos/oidc/pkg/oidc"
"time"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/errors"
@ -15,13 +15,20 @@ type Client struct {
defaultLoginURL string
defaultAccessTokenLifetime time.Duration
defaultIdTokenLifetime time.Duration
allowedScopes []string
}
func ClientFromBusiness(app *model.ApplicationView, defaultLoginURL string, defaultAccessTokenLifetime, defaultIdTokenLifetime time.Duration) (op.Client, error) {
func ClientFromBusiness(app *model.ApplicationView, defaultLoginURL string, defaultAccessTokenLifetime, defaultIdTokenLifetime time.Duration, allowedScopes []string) (op.Client, error) {
if !app.IsOIDC {
return nil, errors.ThrowInvalidArgument(nil, "OIDC-d5bhD", "client is not a proper oidc application")
}
return &Client{ApplicationView: app, defaultLoginURL: defaultLoginURL, defaultAccessTokenLifetime: defaultAccessTokenLifetime, defaultIdTokenLifetime: defaultIdTokenLifetime}, nil
return &Client{
ApplicationView: app,
defaultLoginURL: defaultLoginURL,
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
defaultIdTokenLifetime: defaultIdTokenLifetime,
allowedScopes: allowedScopes},
nil
}
func (c *Client) ApplicationType() op.ApplicationType {
@ -56,6 +63,18 @@ func (c *Client) DevMode() bool {
return c.ApplicationView.DevMode
}
func (c *Client) AllowedScopes() []string {
return c.allowedScopes
}
func (c *Client) AssertAdditionalIdTokenScopes() bool {
return c.IDTokenRoleAssertion
}
func (c *Client) AssertAdditionalAccessTokenScopes() bool {
return c.AccessTokenRoleAssertion
}
func (c *Client) AccessTokenLifetime() time.Duration {
return c.defaultAccessTokenLifetime //PLANNED: impl from real client
}
@ -65,7 +84,18 @@ func (c *Client) IDTokenLifetime() time.Duration {
}
func (c *Client) AccessTokenType() op.AccessTokenType {
return op.AccessTokenTypeBearer //PLANNED: impl from real client
return accessTokenTypeToOIDC(c.ApplicationView.AccessTokenType)
}
func accessTokenTypeToOIDC(tokenType model.OIDCTokenType) op.AccessTokenType {
switch tokenType {
case model.OIDCTokenTypeBearer:
return op.AccessTokenTypeBearer
case model.OIDCTokenTypeJWT:
return op.AccessTokenTypeJWT
default:
return op.AccessTokenTypeBearer
}
}
func authMethodToOIDC(authType model.OIDCAuthMethodType) op.AuthMethod {

View File

@ -2,28 +2,30 @@ package eventstore
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/eventstore/sdk"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model"
cache "github.com/caos/zitadel/internal/auth_request/repository"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/sdk"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/id"
org_model "github.com/caos/zitadel/internal/org/model"
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_view_model "github.com/caos/zitadel/internal/org/repository/view/model"
project_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
user_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
grant_view_model "github.com/caos/zitadel/internal/usergrant/repository/view/model"
)
type AuthRequestRepo struct {
@ -38,6 +40,7 @@ type AuthRequestRepo struct {
OrgViewProvider orgViewProvider
LoginPolicyViewProvider loginPolicyViewProvider
IDPProviderViewProvider idpProviderViewProvider
UserGrantProvider userGrantProvider
IdGenerator id.Generator
@ -76,6 +79,11 @@ type orgViewProvider interface {
OrgByPrimaryDomain(string) (*org_view_model.OrgView, error)
}
type userGrantProvider interface {
ApplicationByClientID(context.Context, string) (*project_view_model.ApplicationView, error)
UserGrantsByProjectAndUserID(string, string) ([]*grant_view_model.UserGrantView, error)
}
func (repo *AuthRequestRepo) Health(ctx context.Context) error {
if err := repo.UserEvents.Health(ctx); err != nil {
return err
@ -89,14 +97,18 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
return nil, err
}
request.ID = reqID
ids, err := repo.View.AppIDsFromProjectByClientID(ctx, request.ApplicationID)
app, err := repo.View.ApplicationByClientID(ctx, request.ApplicationID)
if err != nil {
return nil, err
}
request.Audience = ids
appIDs, err := repo.View.AppIDsFromProjectID(ctx, app.ProjectID)
if err != nil {
return nil, err
}
request.Audience = appIDs
if request.LoginHint != "" {
err = repo.checkLoginName(ctx, request, request.LoginHint)
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID).Debug("login hint invalid")
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID).OnError(err).Debug("login hint invalid")
}
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
if err != nil {
@ -541,6 +553,15 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
}
//PLANNED: consent step
missing, err := userGrantRequired(ctx, request, user, repo.UserGrantProvider)
if err != nil {
return nil, err
}
if missing {
return append(steps, &model.GrantRequiredStep{}), nil
}
return append(steps, &model.RedirectToCallbackStep{}), nil
}
@ -780,3 +801,23 @@ func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*model.ExternalUser, i
}
return true
}
func userGrantRequired(ctx context.Context, request *model.AuthRequest, user *user_model.UserView, userGrantProvider userGrantProvider) (_ bool, err error) {
var app *project_view_model.ApplicationView
switch request.Request.Type() {
case model.AuthRequestTypeOIDC:
app, err = userGrantProvider.ApplicationByClientID(ctx, request.ApplicationID)
if err != nil {
return false, err
}
default:
return false, errors.ThrowPreconditionFailed(nil, "EVENT-dfrw2", "Errors.AuthRequest.RequestTypeNotSupported")
}
if !app.ProjectRoleCheck {
return false, nil
}
grants, err := userGrantProvider.UserGrantsByProjectAndUserID(app.ProjectID, user.ID)
if err != nil {
return false, err
}
return len(grants) == 0, nil
}

View File

@ -15,10 +15,12 @@ import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
org_view_model "github.com/caos/zitadel/internal/org/repository/view/model"
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
user_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
grant_view_model "github.com/caos/zitadel/internal/usergrant/repository/view/model"
)
type mockViewNoUserSession struct{}
@ -157,6 +159,23 @@ func (m *mockViewErrOrg) OrgByPrimaryDomain(string) (*org_view_model.OrgView, er
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
type mockUserGrants struct {
roleCheck bool
userGrants int
}
func (m *mockUserGrants) ApplicationByClientID(ctx context.Context, s string) (*proj_view_model.ApplicationView, error) {
return &proj_view_model.ApplicationView{ProjectRoleCheck: m.roleCheck}, nil
}
func (m *mockUserGrants) UserGrantsByProjectAndUserID(s string, s2 string) ([]*grant_view_model.UserGrantView, error) {
var grants []*grant_view_model.UserGrantView
if m.userGrants > 0 {
grants = make([]*grant_view_model.UserGrantView, m.userGrants)
}
return grants, nil
}
func TestAuthRequestRepo_nextSteps(t *testing.T) {
type fields struct {
UserEvents *user_event.UserEventstore
@ -166,6 +185,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider userViewProvider
userEventProvider userEventProvider
orgViewProvider orgViewProvider
userGrantProvider userGrantProvider
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
@ -424,10 +444,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", Request: &model.AuthRequestOIDC{}}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@ -460,10 +481,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
MfaSoftwareCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", Request: &model.AuthRequestOIDC{}}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@ -590,10 +612,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{UserID: "UserID", Request: &model.AuthRequestOIDC{}}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@ -611,10 +634,61 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone}, true},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone, Request: &model.AuthRequestOIDC{}}, true},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
{
"prompt none, checkLoggedIn true, authenticated and required user grants missing, grant required step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{
roleCheck: true,
userGrants: 0,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone, Request: &model.AuthRequestOIDC{}}, true},
[]model.NextStep{&model.GrantRequiredStep{}},
nil,
},
{
"prompt none, checkLoggedIn true, authenticated and required user grants exist, redirect to callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{
roleCheck: true,
userGrants: 2,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone, Request: &model.AuthRequestOIDC{}}, true},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@ -679,6 +753,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider,
OrgViewProvider: tt.fields.orgViewProvider,
UserGrantProvider: tt.fields.userGrantProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,

View File

@ -0,0 +1,21 @@
package eventstore
import (
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/project/model"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
)
type ProjectRepo struct {
View *view.View
ProjectEvents *proj_event.ProjectEventstore
}
func (a *ApplicationRepo) ProjectRolesByProjectID(projectID string) ([]*model.ProjectRoleView, error) {
roles, err := a.View.ProjectRolesByProjectID(projectID)
if err != nil {
return nil, err
}
return proj_view_model.ProjectRolesToModel(roles), nil
}

View File

@ -52,11 +52,27 @@ func (p *Application) Reduce(event *models.Event) (err error) {
}
err = app.AppendEvent(event)
case es_model.ApplicationRemoved:
err := app.SetData(event)
err = app.SetData(event)
if err != nil {
return err
}
return p.view.DeleteApplication(app.ID, event.Sequence)
case es_model.ProjectChanged:
apps, err := p.view.ApplicationsByProjectID(event.AggregateID)
if err != nil {
return err
}
if len(apps) == 0 {
return p.view.ProcessedApplicationSequence(event.Sequence)
}
for _, app := range apps {
if err := app.AppendEvent(event); err != nil {
return err
}
}
return p.view.PutApplications(apps, event.Sequence)
case es_model.ProjectRemoved:
return p.view.DeleteApplicationsByProjectID(event.AggregateID)
default:
return p.view.ProcessedApplicationSequence(event.Sequence)
}

View File

@ -60,6 +60,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
&ExternalIDP{handler: handler{view, bulkLimit, configs.cycleDuration("ExternalIDP"), errorCount}, systemDefaults: systemDefaults, orgEvents: repos.OrgEvents, iamEvents: repos.IamEvents},
&PasswordComplexityPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordComplexityPolicy"), errorCount}},
&OrgIAMPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount}},
&ProjectRole{handler: handler{view, bulkLimit, configs.cycleDuration("ProjectRole"), errorCount}, projectEvents: repos.ProjectEvents},
}
}

View File

@ -0,0 +1,70 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/project/repository/eventsourcing"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
)
type ProjectRole struct {
handler
projectEvents *proj_event.ProjectEventstore
}
const (
projectRoleTable = "auth.project_roles"
)
func (p *ProjectRole) ViewModel() string {
return projectRoleTable
}
func (p *ProjectRole) EventQuery() (*models.SearchQuery, error) {
sequence, err := p.view.GetLatestProjectRoleSequence()
if err != nil {
return nil, err
}
return eventsourcing.ProjectQuery(sequence.CurrentSequence), nil
}
func (p *ProjectRole) Reduce(event *models.Event) (err error) {
role := new(view_model.ProjectRoleView)
switch event.Type {
case es_model.ProjectRoleAdded:
err = role.AppendEvent(event)
case es_model.ProjectRoleChanged:
err = role.SetData(event)
if err != nil {
return err
}
role, err = p.view.ProjectRoleByIDs(event.AggregateID, event.ResourceOwner, role.Key)
if err != nil {
return err
}
err = role.AppendEvent(event)
case es_model.ProjectRoleRemoved:
err = role.SetData(event)
if err != nil {
return err
}
return p.view.DeleteProjectRole(event.AggregateID, event.ResourceOwner, role.Key, event.Sequence)
case es_model.ProjectRemoved:
return p.view.DeleteProjectRolesByProjectID(event.AggregateID)
default:
return p.view.ProcessedProjectRoleSequence(event.Sequence)
}
if err != nil {
return err
}
return p.view.PutProjectRole(role)
}
func (p *ProjectRole) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-lso9w", "id", event.AggregateID).WithError(err).Warn("something went wrong in project role handler")
return spooler.HandleError(event, err, p.view.GetLatestProjectRoleFailedEvent, p.view.ProcessedProjectRoleFailedEvent, p.view.ProcessedProjectRoleSequence, p.errorCountUntilSkip)
}

View File

@ -354,6 +354,12 @@ func (u *UserGrant) fillProjectData(grant *view_model.UserGrantView, project *pr
func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) {
grant.OrgName = org.Name
for _, domain := range org.Domains {
if domain.Primary {
grant.OrgPrimaryDomain = domain.Domain
break
}
}
}
func (u *UserGrant) OnError(event *models.Event, err error) error {

View File

@ -107,7 +107,7 @@ func (m *UserMembership) processOrg(event *models.Event) (err error) {
case org_es_model.OrgMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence)
case org_es_model.OrgChanged:
err = m.updateOrgName(event)
return m.updateOrgName(event)
default:
return m.view.ProcessedUserMembershipSequence(event.Sequence)
}
@ -178,7 +178,7 @@ func (m *UserMembership) processProject(event *models.Event) (err error) {
case proj_es_model.ProjectGrantMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence)
case proj_es_model.ProjectChanged:
err = m.updateProjectDisplayName(event)
return m.updateProjectDisplayName(event)
case proj_es_model.ProjectRemoved:
return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence)
case proj_es_model.ProjectGrantRemoved:
@ -227,6 +227,6 @@ func (m *UserMembership) processUser(event *models.Event) (err error) {
}
func (m *UserMembership) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler")
logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in user membership handler")
return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip)
}

View File

@ -135,6 +135,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
OrgViewProvider: view,
IDPProviderViewProvider: view,
LoginPolicyViewProvider: view,
UserGrantProvider: view,
IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
@ -156,6 +157,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
View: view,
ProjectEvents: project,
},
eventstore.UserSessionRepo{
View: view,
},

View File

@ -18,16 +18,28 @@ func (v *View) ApplicationByID(projectID, appID string) (*model.ApplicationView,
return view.ApplicationByID(v.Db, applicationTable, projectID, appID)
}
func (v *View) ApplicationsByProjectID(projectID string) ([]*model.ApplicationView, error) {
return view.ApplicationsByProjectID(v.Db, applicationTable, projectID)
}
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request)
}
func (v *View) PutApplication(project *model.ApplicationView) error {
err := view.PutApplication(v.Db, applicationTable, project)
func (v *View) PutApplication(app *model.ApplicationView) error {
err := view.PutApplication(v.Db, applicationTable, app)
if err != nil {
return err
}
return v.ProcessedApplicationSequence(project.Sequence)
return v.ProcessedApplicationSequence(app.Sequence)
}
func (v *View) PutApplications(apps []*model.ApplicationView, sequence uint64) error {
err := view.PutApplications(v.Db, applicationTable, apps...)
if err != nil {
return err
}
return v.ProcessedApplicationSequence(sequence)
}
func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
@ -38,6 +50,10 @@ func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
return v.ProcessedApplicationSequence(eventSequence)
}
func (v *View) DeleteApplicationsByProjectID(projectID string) error {
return view.DeleteApplicationsByProjectID(v.Db, applicationTable, projectID)
}
func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) {
return v.latestSequence(applicationTable)
}
@ -55,24 +71,7 @@ func (v *View) ProcessedApplicationFailedEvent(failedEvent *repository.FailedEve
}
func (v *View) ApplicationByClientID(_ context.Context, clientID string) (*model.ApplicationView, error) {
req := &proj_model.ApplicationSearchRequest{
Limit: 1,
Queries: []*proj_model.ApplicationSearchQuery{
{
Key: proj_model.AppSearchKeyOIDCClientID,
Method: global_model.SearchMethodEquals,
Value: clientID,
},
},
}
apps, count, err := view.SearchApplications(v.Db, applicationTable, req)
if err != nil {
return nil, errors.ThrowPreconditionFailed(err, "VIEW-sd6JQ", "cannot find client")
}
if count != 1 {
return nil, errors.ThrowPreconditionFailed(nil, "VIEW-dfw3as", "cannot find client")
}
return apps[0], nil
return view.ApplicationByOIDCClientID(v.Db, applicationTable, clientID)
}
func (v *View) AppIDsFromProjectByClientID(ctx context.Context, clientID string) ([]string, error) {
@ -102,3 +101,27 @@ func (v *View) AppIDsFromProjectByClientID(ctx context.Context, clientID string)
}
return ids, nil
}
func (v *View) AppIDsFromProjectID(ctx context.Context, projectID string) ([]string, error) {
req := &proj_model.ApplicationSearchRequest{
Queries: []*proj_model.ApplicationSearchQuery{
{
Key: proj_model.AppSearchKeyProjectID,
Method: global_model.SearchMethodEquals,
Value: projectID,
},
},
}
apps, _, err := view.SearchApplications(v.Db, applicationTable, req)
if err != nil {
return nil, errors.ThrowPreconditionFailed(err, "VIEW-Gd24q", "cannot find applications")
}
ids := make([]string, 0, len(apps))
for _, app := range apps {
if !app.IsOIDC {
continue
}
ids = append(ids, app.OIDCClientID)
}
return ids, nil
}

View File

@ -0,0 +1,68 @@
package view
import (
proj_model "github.com/caos/zitadel/internal/project/model"
"github.com/caos/zitadel/internal/project/repository/view"
"github.com/caos/zitadel/internal/project/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
)
const (
projectRoleTable = "auth.project_roles"
)
func (v *View) ProjectRoleByIDs(projectID, orgID, key string) (*model.ProjectRoleView, error) {
return view.ProjectRoleByIDs(v.Db, projectRoleTable, projectID, orgID, key)
}
func (v *View) ProjectRolesByProjectID(projectID string) ([]*model.ProjectRoleView, error) {
return view.ProjectRolesByProjectID(v.Db, projectRoleTable, projectID)
}
func (v *View) ResourceOwnerProjectRolesByKey(projectID, resourceowner, key string) ([]*model.ProjectRoleView, error) {
return view.ResourceOwnerProjectRolesByKey(v.Db, projectRoleTable, projectID, resourceowner, key)
}
func (v *View) ResourceOwnerProjectRoles(projectID, resourceowner string) ([]*model.ProjectRoleView, error) {
return view.ResourceOwnerProjectRoles(v.Db, projectRoleTable, projectID, resourceowner)
}
func (v *View) SearchProjectRoles(request *proj_model.ProjectRoleSearchRequest) ([]*model.ProjectRoleView, uint64, error) {
return view.SearchProjectRoles(v.Db, projectRoleTable, request)
}
func (v *View) PutProjectRole(project *model.ProjectRoleView) error {
err := view.PutProjectRole(v.Db, projectRoleTable, project)
if err != nil {
return err
}
return v.ProcessedProjectRoleSequence(project.Sequence)
}
func (v *View) DeleteProjectRole(projectID, orgID, key string, eventSequence uint64) error {
err := view.DeleteProjectRole(v.Db, projectRoleTable, projectID, orgID, key)
if err != nil {
return nil
}
return v.ProcessedProjectRoleSequence(eventSequence)
}
func (v *View) DeleteProjectRolesByProjectID(projectID string) error {
return view.DeleteProjectRolesByProjectID(v.Db, projectRoleTable, projectID)
}
func (v *View) GetLatestProjectRoleSequence() (*repository.CurrentSequence, error) {
return v.latestSequence(projectRoleTable)
}
func (v *View) ProcessedProjectRoleSequence(eventSequence uint64) error {
return v.saveCurrentSequence(projectRoleTable, eventSequence)
}
func (v *View) GetLatestProjectRoleFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
return v.latestFailedEvent(projectRoleTable, sequence)
}
func (v *View) ProcessedProjectRoleFailedEvent(failedEvent *repository.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -36,7 +36,7 @@ func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence
}
func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64) error {
err := view.PutUserMemberships(v.Db, userTable, memberships...)
err := view.PutUserMemberships(v.Db, userMembershipTable, memberships...)
if err != nil {
return err
}

View File

@ -0,0 +1,9 @@
package repository
import (
"github.com/caos/zitadel/internal/project/model"
)
type ProjectRepository interface {
ProjectRolesByProjectID(projectID string) ([]*model.ProjectRoleView, error)
}

View File

@ -10,6 +10,7 @@ type Repository interface {
AuthRequestRepository
TokenRepository
ApplicationRepository
ProjectRepository
KeyRepository
UserSessionRepository
UserGrantRepository

View File

@ -22,6 +22,7 @@ const (
NextStepLinkUsers
NextStepExternalNotFoundOption
NextStepExternalLogin
NextStepGrantRequired
)
type UserSessionState int32
@ -127,6 +128,12 @@ func (s *LinkUsersStep) Type() NextStepType {
return NextStepLinkUsers
}
type GrantRequiredStep struct{}
func (s *GrantRequiredStep) Type() NextStepType {
return NextStepGrantRequired
}
type RedirectToCallbackStep struct{}
func (s *RedirectToCallbackStep) Type() NextStepType {

View File

@ -68,13 +68,12 @@ func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (*proj_mode
return model.ProjectToModel(project), nil
}
func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) {
func (repo *ProjectRepo) CreateProject(ctx context.Context, project *proj_model.Project) (*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}
return repo.ProjectEvents.CreateProject(ctx, project, iam.GlobalOrgID == ctxData.OrgID)
}

View File

@ -57,6 +57,20 @@ func (p *Application) Reduce(event *models.Event) (err error) {
return err
}
return p.view.DeleteApplication(app.ID, event.Sequence)
case es_model.ProjectChanged:
apps, err := p.view.ApplicationsByProjectID(event.AggregateID)
if err != nil {
return err
}
if len(apps) == 0 {
return p.view.ProcessedApplicationSequence(event.Sequence)
}
for _, app := range apps {
if err := app.AppendEvent(event); err != nil {
return err
}
}
return p.view.PutApplications(apps, event.Sequence)
case es_model.ProjectRemoved:
return p.view.DeleteApplicationsByProjectID(event.AggregateID)
default:

View File

@ -180,6 +180,12 @@ func (u *UserGrant) fillProjectData(grant *view_model.UserGrantView, project *pr
func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) {
grant.OrgName = org.Name
for _, domain := range org.Domains {
if domain.Primary {
grant.OrgPrimaryDomain = domain.Domain
break
}
}
}
func (u *UserGrant) OnError(event *models.Event, err error) error {

View File

@ -105,7 +105,7 @@ func (m *UserMembership) processOrg(event *models.Event) (err error) {
case org_es_model.OrgMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence)
case org_es_model.OrgChanged:
err = m.updateOrgDisplayName(event)
return m.updateOrgDisplayName(event)
default:
return m.view.ProcessedUserMembershipSequence(event.Sequence)
}
@ -166,7 +166,7 @@ func (m *UserMembership) processProject(event *models.Event) (err error) {
case proj_es_model.ProjectGrantMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence)
case proj_es_model.ProjectChanged:
err = m.updateProjectDisplayName(event)
return m.updateProjectDisplayName(event)
case proj_es_model.ProjectRemoved:
return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence)
case proj_es_model.ProjectGrantRemoved:
@ -215,6 +215,6 @@ func (m *UserMembership) processUser(event *models.Event) (err error) {
}
func (m *UserMembership) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler")
logging.LogWithFields("SPOOL-Fwer2", "id", event.AggregateID).WithError(err).Warn("something went wrong in user membership handler")
return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip)
}

View File

@ -15,20 +15,28 @@ func (v *View) ApplicationByID(projectID, appID string) (*model.ApplicationView,
return view.ApplicationByID(v.Db, applicationTable, projectID, appID)
}
func (v *View) ApplicationsByProjectID(ProjectID string) ([]*model.ApplicationView, error) {
return view.ApplicationsByProjectID(v.Db, applicationTable, ProjectID)
func (v *View) ApplicationsByProjectID(projectID string) ([]*model.ApplicationView, error) {
return view.ApplicationsByProjectID(v.Db, applicationTable, projectID)
}
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request)
}
func (v *View) PutApplication(project *model.ApplicationView) error {
err := view.PutApplication(v.Db, applicationTable, project)
func (v *View) PutApplication(app *model.ApplicationView) error {
err := view.PutApplication(v.Db, applicationTable, app)
if err != nil {
return err
}
return v.ProcessedApplicationSequence(project.Sequence)
return v.ProcessedApplicationSequence(app.Sequence)
}
func (v *View) PutApplications(apps []*model.ApplicationView, sequence uint64) error {
err := view.PutApplications(v.Db, applicationTable, apps...)
if err != nil {
return err
}
return v.ProcessedApplicationSequence(sequence)
}
func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
@ -39,8 +47,8 @@ func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
return v.ProcessedApplicationSequence(eventSequence)
}
func (v *View) DeleteApplicationsByProjectID(ProjectID string) error {
return view.DeleteApplicationsByProjectID(v.Db, applicationTable, ProjectID)
func (v *View) DeleteApplicationsByProjectID(projectID string) error {
return view.DeleteApplicationsByProjectID(v.Db, applicationTable, projectID)
}
func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) {

View File

@ -32,7 +32,7 @@ func (v *View) PutUserMembership(membership *model.UserMembershipView, sequence
}
func (v *View) BulkPutUserMemberships(memberships []*model.UserMembershipView, sequence uint64) error {
err := view.PutUserMemberships(v.Db, userTable, memberships...)
err := view.PutUserMemberships(v.Db, userMembershipTable, memberships...)
if err != nil {
return err
}

View File

@ -8,7 +8,7 @@ import (
type ProjectRepository interface {
ProjectByID(ctx context.Context, id string) (*model.ProjectView, error)
CreateProject(ctx context.Context, name string) (*model.Project, error)
CreateProject(ctx context.Context, project *model.Project) (*model.Project, error)
UpdateProject(ctx context.Context, project *model.Project) (*model.Project, error)
DeactivateProject(ctx context.Context, id string) (*model.Project, error)
ReactivateProject(ctx context.Context, id string) (*model.Project, error)

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,
}
}

View File

@ -222,32 +222,32 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
}
func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *model.ExternalUser {
displayName := tokens.IDTokenClaims.PreferredUsername
if displayName == "" && tokens.IDTokenClaims.Email != "" {
displayName = tokens.IDTokenClaims.Email
displayName := tokens.IDTokenClaims.GetPreferredUsername()
if displayName == "" && tokens.IDTokenClaims.GetEmail() != "" {
displayName = tokens.IDTokenClaims.GetEmail()
}
switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
displayName = tokens.IDTokenClaims.Email
if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
displayName = tokens.IDTokenClaims.GetEmail()
}
}
externalUser := &model.ExternalUser{
IDPConfigID: idpConfig.IDPConfigID,
ExternalUserID: tokens.IDTokenClaims.Subject,
PreferredUsername: tokens.IDTokenClaims.PreferredUsername,
ExternalUserID: tokens.IDTokenClaims.GetSubject(),
PreferredUsername: tokens.IDTokenClaims.GetPreferredUsername(),
DisplayName: displayName,
FirstName: tokens.IDTokenClaims.GivenName,
LastName: tokens.IDTokenClaims.FamilyName,
NickName: tokens.IDTokenClaims.Nickname,
Email: tokens.IDTokenClaims.Email,
IsEmailVerified: tokens.IDTokenClaims.EmailVerified,
FirstName: tokens.IDTokenClaims.GetGivenName(),
LastName: tokens.IDTokenClaims.GetFamilyName(),
NickName: tokens.IDTokenClaims.GetNickname(),
Email: tokens.IDTokenClaims.GetEmail(),
IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(),
}
if tokens.IDTokenClaims.PhoneNumber != "" {
externalUser.Phone = tokens.IDTokenClaims.PhoneNumber
externalUser.IsPhoneVerified = tokens.IDTokenClaims.PhoneNumberVerified
if tokens.IDTokenClaims.GetPhoneNumber() != "" {
externalUser.Phone = tokens.IDTokenClaims.GetPhoneNumber()
externalUser.IsPhoneVerified = tokens.IDTokenClaims.IsPhoneNumberVerified()
}
return externalUser
}

View File

@ -1,8 +1,13 @@
package handler
import (
"net/http"
"strings"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
"golang.org/x/text/language"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model"
caos_errors "github.com/caos/zitadel/internal/errors"
@ -10,8 +15,6 @@ import (
iam_model "github.com/caos/zitadel/internal/iam/model"
org_model "github.com/caos/zitadel/internal/org/model"
usr_model "github.com/caos/zitadel/internal/user/model"
"net/http"
"strings"
)
func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
@ -109,11 +112,11 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques
}
func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *iam_model.OrgIAMPolicyView, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*usr_model.User, *usr_model.ExternalIDP) {
username := tokens.IDTokenClaims.PreferredUsername
username := tokens.IDTokenClaims.GetPreferredUsername()
switch idpConfig.OIDCUsernameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
username = tokens.IDTokenClaims.Email
if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
username = tokens.IDTokenClaims.GetEmail()
}
}
@ -128,35 +131,35 @@ func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *iam_model.OrgIAM
UserName: username,
Human: &usr_model.Human{
Profile: &usr_model.Profile{
FirstName: tokens.IDTokenClaims.GivenName,
LastName: tokens.IDTokenClaims.FamilyName,
PreferredLanguage: tokens.IDTokenClaims.Locale,
NickName: tokens.IDTokenClaims.Nickname,
FirstName: tokens.IDTokenClaims.GetGivenName(),
LastName: tokens.IDTokenClaims.GetFamilyName(),
PreferredLanguage: language.Tag(tokens.IDTokenClaims.GetLocale()),
NickName: tokens.IDTokenClaims.GetNickname(),
},
Email: &usr_model.Email{
EmailAddress: tokens.IDTokenClaims.Email,
IsEmailVerified: tokens.IDTokenClaims.EmailVerified,
EmailAddress: tokens.IDTokenClaims.GetEmail(),
IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(),
},
},
}
if tokens.IDTokenClaims.PhoneNumber != "" {
if tokens.IDTokenClaims.GetPhoneNumber() != "" {
user.Phone = &usr_model.Phone{
PhoneNumber: tokens.IDTokenClaims.PhoneNumber,
IsPhoneVerified: tokens.IDTokenClaims.PhoneNumberVerified,
PhoneNumber: tokens.IDTokenClaims.GetPhoneNumber(),
IsPhoneVerified: tokens.IDTokenClaims.IsPhoneNumberVerified(),
}
}
displayName := tokens.IDTokenClaims.PreferredUsername
displayName := tokens.IDTokenClaims.GetPreferredUsername()
switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {
displayName = tokens.IDTokenClaims.Email
if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
displayName = tokens.IDTokenClaims.GetEmail()
}
}
externalIDP := &usr_model.ExternalIDP{
IDPConfigID: idpConfig.IDPConfigID,
UserID: tokens.IDTokenClaims.Subject,
UserID: tokens.IDTokenClaims.GetSubject(),
DisplayName: displayName,
}
return user, externalIDP

View File

@ -211,6 +211,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
l.renderExternalNotFoundOption(w, r, authReq, err)
case *model.ExternalLoginStep:
l.handleExternalLoginStep(w, r, authReq, step.SelectedIDPConfigID)
case *model.GrantRequiredStep:
l.renderInternalError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-asb43", "Errors.User.GrantRequired"))
default:
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
}

View File

@ -187,6 +187,7 @@ Errors:
AuthRequest:
NotFound: AuthRequest konnte nicht gefunden werden
UserAgentNotCorresponding: User Agent stimmt nicht überein
RequestTypeNotSupported: Requesttyp wird nicht unterstürzt
User:
NotFound: Benutzer konnte nicht gefunden werden
NotMatchingUserID: User stimm nicht mit User in Auth Request überein
@ -226,5 +227,6 @@ Errors:
ExternalIDP:
IDPTypeNotImplemented: IDP Typ ist nicht implementiert
NotAllowed: Externer Login Provider ist nicht erlaubt
GrantRequired: Der Login an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte melde dich bei deinem Administrator.
optional: (optional)

View File

@ -187,6 +187,7 @@ Errors:
AuthRequest:
NotFound: Could not find authrequest
UserAgentNotCorresponding: User Agent does not correspond
RequestTypeNotSupported: Request type is not supported
User:
NotFound: User could not be found
NotMatchingUserID: User and user in authrequest don't match
@ -226,6 +227,7 @@ Errors:
ExternalIDP:
IDPTypeNotImplemented: IDP Type is not implemented
NotAllowed: External Login Provider not allowed
GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator.
optional: (optional)

View File

@ -1,6 +1,6 @@
{{template "main-top" .}}
<div>
<div class="head">
{{ .ErrType }}
{{ .ErrMessage }}
</div>

View File

@ -44,7 +44,7 @@ const (
type TokenSearchQuery struct {
Key TokenSearchKey
Method model.SearchMethod
Value string
Value interface{}
}
type TokenSearchResponse struct {

View File

@ -7,20 +7,20 @@ import (
)
type UserGrantView struct {
ID string
ResourceOwner string
UserID string
ProjectID string
GrantID string
UserName string
FirstName string
LastName string
DisplayName string
Email string
ProjectName string
OrgName string
OrgDomain string
RoleKeys []string
ID string
ResourceOwner string
UserID string
ProjectID string
GrantID string
UserName string
FirstName string
LastName string
DisplayName string
Email string
ProjectName string
OrgName string
OrgPrimaryDomain string
RoleKeys []string
CreationDate time.Time
ChangeDate time.Time

View File

@ -5,11 +5,12 @@ import (
"time"
"github.com/caos/logging"
"github.com/lib/pq"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/usergrant/model"
es_model "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
"github.com/lib/pq"
)
const (
@ -24,19 +25,20 @@ const (
)
type UserGrantView struct {
ID string `json:"-" gorm:"column:id;primary_key"`
ResourceOwner string `json:"-" gorm:"resource_owner"`
UserID string `json:"userId" gorm:"user_id"`
ProjectID string `json:"projectId" gorm:"column:project_id"`
GrantID string `json:"grantId" gorm:"column:grant_id"`
UserName string `json:"-" gorm:"column:user_name"`
FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"`
Email string `json:"-" gorm:"column:email"`
ProjectName string `json:"-" gorm:"column:project_name"`
OrgName string `json:"-" gorm:"column:org_name"`
RoleKeys pq.StringArray `json:"roleKeys" gorm:"column:role_keys"`
ID string `json:"-" gorm:"column:id;primary_key"`
ResourceOwner string `json:"-" gorm:"resource_owner"`
UserID string `json:"userId" gorm:"user_id"`
ProjectID string `json:"projectId" gorm:"column:project_id"`
GrantID string `json:"grantId" gorm:"column:grant_id"`
UserName string `json:"-" gorm:"column:user_name"`
FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"`
Email string `json:"-" gorm:"column:email"`
ProjectName string `json:"-" gorm:"column:project_name"`
OrgName string `json:"-" gorm:"column:org_name"`
OrgPrimaryDomain string `json:"-" gorm:"column:org_primary_domain"`
RoleKeys pq.StringArray `json:"roleKeys" gorm:"column:role_keys"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
@ -47,22 +49,23 @@ type UserGrantView struct {
func UserGrantToModel(grant *UserGrantView) *model.UserGrantView {
return &model.UserGrantView{
ID: grant.ID,
ResourceOwner: grant.ResourceOwner,
UserID: grant.UserID,
ProjectID: grant.ProjectID,
ChangeDate: grant.ChangeDate,
CreationDate: grant.CreationDate,
State: model.UserGrantState(grant.State),
UserName: grant.UserName,
FirstName: grant.FirstName,
LastName: grant.LastName,
DisplayName: grant.DisplayName,
Email: grant.Email,
ProjectName: grant.ProjectName,
OrgName: grant.OrgName,
RoleKeys: grant.RoleKeys,
Sequence: grant.Sequence,
ID: grant.ID,
ResourceOwner: grant.ResourceOwner,
UserID: grant.UserID,
ProjectID: grant.ProjectID,
ChangeDate: grant.ChangeDate,
CreationDate: grant.CreationDate,
State: model.UserGrantState(grant.State),
UserName: grant.UserName,
FirstName: grant.FirstName,
LastName: grant.LastName,
DisplayName: grant.DisplayName,
Email: grant.Email,
ProjectName: grant.ProjectName,
OrgName: grant.OrgName,
OrgPrimaryDomain: grant.OrgPrimaryDomain,
RoleKeys: grant.RoleKeys,
Sequence: grant.Sequence,
}
}

View File

@ -0,0 +1,40 @@
CREATE TABLE auth.project_roles (
project_id TEXT,
role_key TEXT,
display_name TEXT,
resource_owner TEXT,
org_id TEXT,
group_name TEXT,
creation_date TIMESTAMPTZ,
sequence BIGINT,
PRIMARY KEY (org_id, project_id, role_key)
);
ALTER TABLE authz.user_grants ADD COLUMN org_primary_domain TEXT;
ALTER TABLE auth.user_grants ADD COLUMN org_primary_domain TEXT;
ALTER TABLE management.user_grants ADD COLUMN org_primary_domain TEXT;
ALTER TABLE authz.applications ADD COLUMN access_token_type SMALLINT;
ALTER TABLE auth.applications ADD COLUMN access_token_type SMALLINT;
ALTER TABLE management.applications ADD COLUMN access_token_type SMALLINT;
ALTER TABLE management.projects ADD COLUMN project_role_assertion BOOLEAN;
ALTER TABLE management.projects ADD COLUMN project_role_check BOOLEAN;
ALTER TABLE authz.applications ADD COLUMN project_role_assertion BOOLEAN;
ALTER TABLE auth.applications ADD COLUMN project_role_assertion BOOLEAN;
ALTER TABLE management.applications ADD COLUMN project_role_assertion BOOLEAN;
ALTER TABLE authz.applications ADD COLUMN project_role_check BOOLEAN;
ALTER TABLE auth.applications ADD COLUMN project_role_check BOOLEAN;
ALTER TABLE management.applications ADD COLUMN project_role_check BOOLEAN;
ALTER TABLE authz.applications ADD COLUMN access_token_role_assertion BOOLEAN;
ALTER TABLE auth.applications ADD COLUMN access_token_role_assertion BOOLEAN;
ALTER TABLE management.applications ADD COLUMN access_token_role_assertion BOOLEAN;
ALTER TABLE authz.applications ADD COLUMN id_token_role_assertion BOOLEAN;
ALTER TABLE auth.applications ADD COLUMN id_token_role_assertion BOOLEAN;
ALTER TABLE management.applications ADD COLUMN id_token_role_assertion BOOLEAN;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6381,6 +6381,10 @@ func (m *ProjectCreateRequest) Validate() error {
}
}
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil
}
@ -6462,6 +6466,10 @@ func (m *ProjectUpdateRequest) Validate() error {
}
}
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil
}
@ -6659,6 +6667,10 @@ func (m *ProjectView) Validate() error {
// no validation rules for Sequence
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil
}
@ -6998,6 +7010,10 @@ func (m *Project) Validate() error {
// no validation rules for Sequence
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil
}
@ -8850,6 +8866,12 @@ func (m *OIDCConfig) Validate() error {
// no validation rules for DevMode
// no validation rules for AccessTokenType
// no validation rules for AccessTokenRoleAssertion
// no validation rules for IdTokenRoleAssertion
return nil
}
@ -8937,6 +8959,12 @@ func (m *OIDCApplicationCreate) Validate() error {
// no validation rules for DevMode
// no validation rules for AccessTokenType
// no validation rules for AccessTokenRoleAssertion
// no validation rules for IdTokenRoleAssertion
return nil
}
@ -9024,6 +9052,12 @@ func (m *OIDCConfigUpdate) Validate() error {
// no validation rules for DevMode
// no validation rules for AccessTokenType
// no validation rules for AccessTokenRoleAssertion
// no validation rules for IdTokenRoleAssertion
return nil
}

View File

@ -2180,11 +2180,15 @@ enum OrgMemberSearchKey {
message ProjectCreateRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
bool project_role_assertion = 2;
bool project_role_check = 3;
}
message ProjectUpdateRequest {
string id = 1 [(validate.rules).string = {min_len: 1}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
bool project_role_assertion = 3;
bool project_role_check = 4;
}
message ProjectSearchResponse {
@ -2204,6 +2208,8 @@ message ProjectView {
google.protobuf.Timestamp creation_date = 5;
string resource_owner = 6;
uint64 sequence = 7;
bool project_role_assertion = 8;
bool project_role_check = 9;
}
message ProjectSearchRequest {
@ -2234,6 +2240,8 @@ message Project {
google.protobuf.Timestamp change_date = 4;
google.protobuf.Timestamp creation_date = 5;
uint64 sequence = 6;
bool project_role_assertion = 7;
bool project_role_check = 8;
}
enum ProjectState {
@ -2423,6 +2431,9 @@ message OIDCConfig {
bool none_compliant = 10;
repeated caos.zitadel.api.v1.LocalizedMessage compliance_problems = 11;
bool dev_mode = 12;
OIDCTokenType access_token_type = 13;
bool access_token_role_assertion = 14;
bool id_token_role_assertion = 15;
}
message OIDCApplicationCreate {
@ -2436,12 +2447,20 @@ message OIDCApplicationCreate {
repeated string post_logout_redirect_uris = 8;
OIDCVersion version = 9;
bool dev_mode = 10;
OIDCTokenType access_token_type = 11;
bool access_token_role_assertion = 12;
bool id_token_role_assertion = 13;
}
enum OIDCVersion {
OIDCV1_0 = 0;
}
enum OIDCTokenType {
OIDCTokenType_Bearer = 0;
OIDCTokenType_JWT = 1;
}
message OIDCConfigUpdate {
string project_id = 1 [(validate.rules).string = {min_len: 1}];
string application_id = 2 [(validate.rules).string = {min_len: 1}];
@ -2452,6 +2471,9 @@ message OIDCConfigUpdate {
OIDCAuthMethodType auth_method_type = 7;
repeated string post_logout_redirect_uris = 8;
bool dev_mode = 9;
OIDCTokenType access_token_type = 10;
bool access_token_role_assertion = 11;
bool id_token_role_assertion = 12;
}
enum OIDCResponseType {