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/aws/aws-sdk-go v1.34.24 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
github.com/caos/logging v0.0.2 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/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cockroachdb/cockroach-go/v2 v2.0.7 github.com/cockroachdb/cockroach-go/v2 v2.0.7
github.com/envoyproxy/protoc-gen-validate v0.4.1 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/go.uuid v1.2.0 // indirect
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
github.com/kevinburke/twilio-go v0.0.0-20200810163702-320748330fac 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/lib/pq v1.8.0
github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // 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/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 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= 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.12.0 h1:BcEwgeq8fpum2hdc47ZDOTwnc4KdHpos1NRbE1JcHN8=
github.com/caos/oidc v0.10.0/go.mod h1:RREtWSRzH/mXQXJkxB63mFDZ/RUNyzoU6czd6UJfvJI= github.com/caos/oidc v0.12.0/go.mod h1:R9UKITZmSo5vNhSLUYcTDH8pAaV2xwPASXg8wpEs2xQ=
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/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= 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=
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.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 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.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/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/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 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.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 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM= 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-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 h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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-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 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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 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-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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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) member := new(iam_model.IAMMemberView)
switch event.Type { switch event.Type {
case model.IAMMemberAdded: case model.IAMMemberAdded:
member.AppendEvent(event) err = member.AppendEvent(event)
m.fillData(member) if err != nil {
return err
}
err = m.fillData(member)
case model.IAMMemberChanged: case model.IAMMemberChanged:
err := member.SetData(event) err := member.SetData(event)
if err != nil { if err != nil {
@ -63,7 +66,7 @@ func (m *IamMember) processIamMember(event *models.Event) (err error) {
if err != nil { if err != nil {
return err return err
} }
member.AppendEvent(event) err = member.AppendEvent(event)
case model.IAMMemberRemoved: case model.IAMMemberRemoved:
err := member.SetData(event) err := member.SetData(event)
if err != nil { if err != nil {

View File

@ -35,7 +35,10 @@ func (o *Org) Reduce(event *es_models.Event) error {
switch event.Type { switch event.Type {
case model.OrgAdded: case model.OrgAdded:
org.AppendEvent(event) err := org.AppendEvent(event)
if err != nil {
return err
}
case model.OrgChanged: case model.OrgChanged:
err := org.SetData(event) err := org.SetData(event)
if err != nil { 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 { func oidcConfigFromModel(config *proj_model.OIDCConfig) *management.OIDCConfig {
return &management.OIDCConfig{ return &management.OIDCConfig{
RedirectUris: config.RedirectUris, RedirectUris: config.RedirectUris,
ResponseTypes: oidcResponseTypesFromModel(config.ResponseTypes), ResponseTypes: oidcResponseTypesFromModel(config.ResponseTypes),
GrantTypes: oidcGrantTypesFromModel(config.GrantTypes), GrantTypes: oidcGrantTypesFromModel(config.GrantTypes),
ApplicationType: oidcApplicationTypeFromModel(config.ApplicationType), ApplicationType: oidcApplicationTypeFromModel(config.ApplicationType),
ClientId: config.ClientID, ClientId: config.ClientID,
ClientSecret: config.ClientSecretString, ClientSecret: config.ClientSecretString,
AuthMethodType: oidcAuthMethodTypeFromModel(config.AuthMethodType), AuthMethodType: oidcAuthMethodTypeFromModel(config.AuthMethodType),
PostLogoutRedirectUris: config.PostLogoutRedirectUris, PostLogoutRedirectUris: config.PostLogoutRedirectUris,
Version: oidcVersionFromModel(config.OIDCVersion), Version: oidcVersionFromModel(config.OIDCVersion),
NoneCompliant: config.Compliance.NoneCompliant, NoneCompliant: config.Compliance.NoneCompliant,
ComplianceProblems: complianceProblemsToLocalizedMessages(config.Compliance.Problems), ComplianceProblems: complianceProblemsToLocalizedMessages(config.Compliance.Problems),
DevMode: config.DevMode, DevMode: config.DevMode,
AccessTokenType: oidcTokenTypeFromModel(config.AccessTokenType),
AccessTokenRoleAssertion: config.AccessTokenRoleAssertion,
IdTokenRoleAssertion: config.IDTokenRoleAssertion,
} }
} }
func oidcConfigFromApplicationViewModel(app *proj_model.ApplicationView) *management.OIDCConfig { func oidcConfigFromApplicationViewModel(app *proj_model.ApplicationView) *management.OIDCConfig {
return &management.OIDCConfig{ return &management.OIDCConfig{
RedirectUris: app.OIDCRedirectUris, RedirectUris: app.OIDCRedirectUris,
ResponseTypes: oidcResponseTypesFromModel(app.OIDCResponseTypes), ResponseTypes: oidcResponseTypesFromModel(app.OIDCResponseTypes),
GrantTypes: oidcGrantTypesFromModel(app.OIDCGrantTypes), GrantTypes: oidcGrantTypesFromModel(app.OIDCGrantTypes),
ApplicationType: oidcApplicationTypeFromModel(app.OIDCApplicationType), ApplicationType: oidcApplicationTypeFromModel(app.OIDCApplicationType),
ClientId: app.OIDCClientID, ClientId: app.OIDCClientID,
AuthMethodType: oidcAuthMethodTypeFromModel(app.OIDCAuthMethodType), AuthMethodType: oidcAuthMethodTypeFromModel(app.OIDCAuthMethodType),
PostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris, PostLogoutRedirectUris: app.OIDCPostLogoutRedirectUris,
Version: oidcVersionFromModel(app.OIDCVersion), Version: oidcVersionFromModel(app.OIDCVersion),
NoneCompliant: app.NoneCompliant, NoneCompliant: app.NoneCompliant,
ComplianceProblems: complianceProblemsToLocalizedMessages(app.ComplianceProblems), ComplianceProblems: complianceProblemsToLocalizedMessages(app.ComplianceProblems),
DevMode: app.DevMode, 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, Name: app.Name,
Type: proj_model.AppTypeOIDC, Type: proj_model.AppTypeOIDC,
OIDCConfig: &proj_model.OIDCConfig{ OIDCConfig: &proj_model.OIDCConfig{
OIDCVersion: oidcVersionToModel(app.Version), OIDCVersion: oidcVersionToModel(app.Version),
RedirectUris: app.RedirectUris, RedirectUris: app.RedirectUris,
ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes), ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes),
GrantTypes: oidcGrantTypesToModel(app.GrantTypes), GrantTypes: oidcGrantTypesToModel(app.GrantTypes),
ApplicationType: oidcApplicationTypeToModel(app.ApplicationType), ApplicationType: oidcApplicationTypeToModel(app.ApplicationType),
AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType), AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType),
PostLogoutRedirectUris: app.PostLogoutRedirectUris, PostLogoutRedirectUris: app.PostLogoutRedirectUris,
DevMode: app.DevMode, 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{ ObjectRoot: models.ObjectRoot{
AggregateID: app.ProjectId, AggregateID: app.ProjectId,
}, },
AppID: app.ApplicationId, AppID: app.ApplicationId,
RedirectUris: app.RedirectUris, RedirectUris: app.RedirectUris,
ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes), ResponseTypes: oidcResponseTypesToModel(app.ResponseTypes),
GrantTypes: oidcGrantTypesToModel(app.GrantTypes), GrantTypes: oidcGrantTypesToModel(app.GrantTypes),
ApplicationType: oidcApplicationTypeToModel(app.ApplicationType), ApplicationType: oidcApplicationTypeToModel(app.ApplicationType),
AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType), AuthMethodType: oidcAuthMethodTypeToModel(app.AuthMethodType),
PostLogoutRedirectUris: app.PostLogoutRedirectUris, PostLogoutRedirectUris: app.PostLogoutRedirectUris,
DevMode: app.DevMode, 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 { func oidcVersionFromModel(version proj_model.OIDCVersion) management.OIDCVersion {
switch version { switch version {
case proj_model.OIDCVersionV1: case proj_model.OIDCVersionV1:

View File

@ -12,7 +12,7 @@ import (
) )
func (s *Server) CreateProject(ctx context.Context, in *management.ProjectCreateRequest) (*management.Project, error) { 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 { if err != nil {
return nil, err 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") logging.Log("GRPC-di7rw").OnError(err).Debug("unable to parse timestamp")
return &management.Project{ return &management.Project{
Id: project.AggregateID, Id: project.AggregateID,
State: projectStateFromModel(project.State), State: projectStateFromModel(project.State),
CreationDate: creationDate, CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
Name: project.Name, Name: project.Name,
Sequence: project.Sequence, 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") logging.Log("GRPC-sope3").OnError(err).Debug("unable to parse timestamp")
return &management.ProjectView{ return &management.ProjectView{
ProjectId: project.ProjectID, ProjectId: project.ProjectID,
State: projectStateFromModel(project.State), State: projectStateFromModel(project.State),
CreationDate: creationDate, CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
Name: project.Name, Name: project.Name,
Sequence: project.Sequence, Sequence: project.Sequence,
ResourceOwner: project.ResourceOwner, 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 { func projectUpdateToModel(project *management.ProjectUpdateRequest) *proj_model.Project {
return &proj_model.Project{ return &proj_model.Project{
ObjectRoot: models.ObjectRoot{ ObjectRoot: models.ObjectRoot{
AggregateID: project.Id, 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, Email: grant.Email,
ProjectName: grant.ProjectName, ProjectName: grant.ProjectName,
OrgName: grant.OrgName, OrgName: grant.OrgName,
OrgDomain: grant.OrgDomain, OrgDomain: grant.OrgPrimaryDomain,
RoleKeys: grant.RoleKeys, RoleKeys: grant.RoleKeys,
UserId: grant.UserID, UserId: grant.UserID,
ProjectId: grant.ProjectID, ProjectId: grant.ProjectID,

View File

@ -3,6 +3,7 @@ package oidc
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
@ -11,6 +12,7 @@ import (
"github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model"
grant_model "github.com/caos/zitadel/internal/usergrant/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 { if !ok {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id") 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) authRequest := CreateAuthRequestToBusiness(ctx, req, userAgentID, userID)
resp, err := o.repo.CreateAuthRequest(ctx, authRequest) resp, err := o.repo.CreateAuthRequest(ctx, authRequest)
if err != nil { 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 { func (o *OPStorage) SaveNewKeyPair(ctx context.Context) error {
return o.repo.GenerateSigningKeyPair(ctx, o.signingKeyAlgorithm) 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 ( import (
"context" "context"
"strings"
"golang.org/x/text/language" "golang.org/x/text/language"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
@ -15,6 +16,7 @@ import (
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
proj_model "github.com/caos/zitadel/internal/project/model" proj_model "github.com/caos/zitadel/internal/project/model"
user_model "github.com/caos/zitadel/internal/user/model" user_model "github.com/caos/zitadel/internal/user/model"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
) )
const ( const (
@ -24,6 +26,9 @@ const (
scopePhone = "phone" scopePhone = "phone"
scopeAddress = "address" scopeAddress = "address"
ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:"
ClaimProjectRoles = "urn:zitadel:iam:org:project:roles"
oidcCtx = "oidc" oidcCtx = "oidc"
) )
@ -35,7 +40,15 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Clie
if client.State != proj_model.AppStateActive { if client.State != proj_model.AppStateActive {
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active") 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) { 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) return o.repo.AuthorizeOIDCApplication(ctx, id, secret)
} }
func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (*oidc.Userinfo, error) { func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (oidc.UserInfo, error) {
token, err := o.repo.TokenByID(ctx, tokenID, subject) token, err := o.repo.TokenByID(ctx, subject, tokenID)
if err != nil { if err != nil {
return nil, err return nil, errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
} }
if token.ApplicationID != "" { if token.ApplicationID != "" {
app, err := o.repo.ApplicationByClientID(ctx, 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 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) user, err := o.repo.UserByID(ctx, userID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
userInfo := new(oidc.Userinfo) userInfo := oidc.NewUserInfo()
roles := make([]string, 0)
for _, scope := range scopes { for _, scope := range scopes {
switch scope { switch scope {
case scopeOpenID: case oidc.ScopeOpenID:
userInfo.Subject = user.ID userInfo.SetSubject(user.ID)
case scopeEmail: case oidc.ScopeEmail:
if user.HumanView == nil { if user.HumanView == nil {
continue continue
} }
userInfo.Email = user.Email userInfo.SetEmail(user.Email, user.IsEmailVerified)
userInfo.EmailVerified = user.IsEmailVerified case oidc.ScopeProfile:
case scopeProfile: userInfo.SetPreferredUsername(user.PreferredLoginName)
userInfo.PreferredUsername = user.PreferredLoginName userInfo.SetUpdatedAt(user.ChangeDate)
userInfo.UpdatedAt = user.ChangeDate
if user.HumanView != nil { if user.HumanView != nil {
userInfo.Name = user.DisplayName userInfo.SetName(user.DisplayName)
userInfo.FamilyName = user.LastName userInfo.SetFamilyName(user.LastName)
userInfo.GivenName = user.FirstName userInfo.SetGivenName(user.FirstName)
userInfo.Nickname = user.NickName userInfo.SetNickname(user.NickName)
userInfo.Gender = oidc.Gender(getGender(user.Gender)) userInfo.SetGender(oidc.Gender(getGender(user.Gender)))
userInfo.Locale, err = language.Parse(user.PreferredLanguage) locale, _ := language.Parse(user.PreferredLanguage)
userInfo.SetLocale(locale)
} else { } else {
userInfo.Name = user.MachineView.Name userInfo.SetName(user.MachineView.Name)
} }
case scopePhone: case oidc.ScopePhone:
if user.HumanView == nil { if user.HumanView == nil {
continue continue
} }
userInfo.PhoneNumber = user.Phone userInfo.SetPhone(user.Phone, user.IsPhoneVerified)
userInfo.PhoneNumberVerified = user.IsPhoneVerified case oidc.ScopeAddress:
case scopeAddress:
if user.HumanView == nil { if user.HumanView == nil {
continue continue
} }
if user.StreetAddress == "" && user.Locality == "" && user.Region == "" && user.PostalCode == "" && user.Country == "" { if user.StreetAddress == "" && user.Locality == "" && user.Region == "" && user.PostalCode == "" && user.Country == "" {
continue continue
} }
userInfo.Address = &oidc.UserinfoAddress{ userInfo.SetAddress(oidc.NewUserInfoAddress(user.StreetAddress, user.Locality, user.Region, user.PostalCode, user.Country, ""))
StreetAddress: user.StreetAddress,
Locality: user.Locality,
Region: user.Region,
PostalCode: user.PostalCode,
Country: user.Country,
}
default: 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 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 { func getGender(gender user_model.Gender) string {
switch gender { switch gender {
case user_model.GenderFemale: case user_model.GenderFemale:

View File

@ -1,9 +1,9 @@
package oidc package oidc
import ( import (
"github.com/caos/oidc/pkg/oidc"
"time" "time"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
@ -15,13 +15,20 @@ type Client struct {
defaultLoginURL string defaultLoginURL string
defaultAccessTokenLifetime time.Duration defaultAccessTokenLifetime time.Duration
defaultIdTokenLifetime 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 { if !app.IsOIDC {
return nil, errors.ThrowInvalidArgument(nil, "OIDC-d5bhD", "client is not a proper oidc application") 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 { func (c *Client) ApplicationType() op.ApplicationType {
@ -56,6 +63,18 @@ func (c *Client) DevMode() bool {
return c.ApplicationView.DevMode 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 { func (c *Client) AccessTokenLifetime() time.Duration {
return c.defaultAccessTokenLifetime //PLANNED: impl from real client return c.defaultAccessTokenLifetime //PLANNED: impl from real client
} }
@ -65,7 +84,18 @@ func (c *Client) IDTokenLifetime() time.Duration {
} }
func (c *Client) AccessTokenType() op.AccessTokenType { 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 { func authMethodToOIDC(authType model.OIDCAuthMethodType) op.AuthMethod {

View File

@ -2,28 +2,30 @@ package eventstore
import ( import (
"context" "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" "time"
"github.com/caos/logging" "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/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/model"
cache "github.com/caos/zitadel/internal/auth_request/repository" cache "github.com/caos/zitadel/internal/auth_request/repository"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models" 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" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/id"
org_model "github.com/caos/zitadel/internal/org/model" 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" 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_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
user_view_model "github.com/caos/zitadel/internal/user/repository/view/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 { type AuthRequestRepo struct {
@ -38,6 +40,7 @@ type AuthRequestRepo struct {
OrgViewProvider orgViewProvider OrgViewProvider orgViewProvider
LoginPolicyViewProvider loginPolicyViewProvider LoginPolicyViewProvider loginPolicyViewProvider
IDPProviderViewProvider idpProviderViewProvider IDPProviderViewProvider idpProviderViewProvider
UserGrantProvider userGrantProvider
IdGenerator id.Generator IdGenerator id.Generator
@ -76,6 +79,11 @@ type orgViewProvider interface {
OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) 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 { func (repo *AuthRequestRepo) Health(ctx context.Context) error {
if err := repo.UserEvents.Health(ctx); err != nil { if err := repo.UserEvents.Health(ctx); err != nil {
return err return err
@ -89,14 +97,18 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
return nil, err return nil, err
} }
request.ID = reqID request.ID = reqID
ids, err := repo.View.AppIDsFromProjectByClientID(ctx, request.ApplicationID) app, err := repo.View.ApplicationByClientID(ctx, request.ApplicationID)
if err != nil { if err != nil {
return nil, err 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 != "" { if request.LoginHint != "" {
err = repo.checkLoginName(ctx, request, 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) err = repo.AuthRequests.SaveAuthRequest(ctx, request)
if err != nil { if err != nil {
@ -541,6 +553,15 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
} }
//PLANNED: consent step //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 return append(steps, &model.RedirectToCallbackStep{}), nil
} }
@ -780,3 +801,23 @@ func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*model.ExternalUser, i
} }
return true 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" es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model" org_model "github.com/caos/zitadel/internal/org/model"
org_view_model "github.com/caos/zitadel/internal/org/repository/view/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_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing" user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
user_view_model "github.com/caos/zitadel/internal/user/repository/view/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{} 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") 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) { func TestAuthRequestRepo_nextSteps(t *testing.T) {
type fields struct { type fields struct {
UserEvents *user_event.UserEventstore UserEvents *user_event.UserEventstore
@ -166,6 +185,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider userViewProvider userViewProvider userViewProvider
userEventProvider userEventProvider userEventProvider userEventProvider
orgViewProvider orgViewProvider orgViewProvider orgViewProvider
userGrantProvider userGrantProvider
PasswordCheckLifeTime time.Duration PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration ExternalLoginCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration MfaInitSkippedLifeTime time.Duration
@ -424,10 +444,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour, ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * 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{}}, []model.NextStep{&model.RedirectToCallbackStep{}},
nil, nil,
}, },
@ -460,10 +481,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * 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{}}, []model.NextStep{&model.RedirectToCallbackStep{}},
nil, nil,
}, },
@ -590,10 +612,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * 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{}}, []model.NextStep{&model.RedirectToCallbackStep{}},
nil, nil,
}, },
@ -611,10 +634,61 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * 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{}}, []model.NextStep{&model.RedirectToCallbackStep{}},
nil, nil,
}, },
@ -679,6 +753,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
UserViewProvider: tt.fields.userViewProvider, UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider, UserEventProvider: tt.fields.userEventProvider,
OrgViewProvider: tt.fields.orgViewProvider, OrgViewProvider: tt.fields.orgViewProvider,
UserGrantProvider: tt.fields.userGrantProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime, ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, 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) err = app.AppendEvent(event)
case es_model.ApplicationRemoved: case es_model.ApplicationRemoved:
err := app.SetData(event) err = app.SetData(event)
if err != nil { if err != nil {
return err return err
} }
return p.view.DeleteApplication(app.ID, event.Sequence) 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: default:
return p.view.ProcessedApplicationSequence(event.Sequence) 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}, &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}}, &PasswordComplexityPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("PasswordComplexityPolicy"), errorCount}},
&OrgIAMPolicy{handler: handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), 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) { func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) {
grant.OrgName = org.Name 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 { 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: case org_es_model.OrgMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence) return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence)
case org_es_model.OrgChanged: case org_es_model.OrgChanged:
err = m.updateOrgName(event) return m.updateOrgName(event)
default: default:
return m.view.ProcessedUserMembershipSequence(event.Sequence) return m.view.ProcessedUserMembershipSequence(event.Sequence)
} }
@ -178,7 +178,7 @@ func (m *UserMembership) processProject(event *models.Event) (err error) {
case proj_es_model.ProjectGrantMemberRemoved: case proj_es_model.ProjectGrantMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence) return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence)
case proj_es_model.ProjectChanged: case proj_es_model.ProjectChanged:
err = m.updateProjectDisplayName(event) return m.updateProjectDisplayName(event)
case proj_es_model.ProjectRemoved: case proj_es_model.ProjectRemoved:
return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence) return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence)
case proj_es_model.ProjectGrantRemoved: 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 { 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) 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, OrgViewProvider: view,
IDPProviderViewProvider: view, IDPProviderViewProvider: view,
LoginPolicyViewProvider: view, LoginPolicyViewProvider: view,
UserGrantProvider: view,
IdGenerator: idGenerator, IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
ExternalLoginCheckLifeTime: 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, View: view,
ProjectEvents: project, ProjectEvents: project,
}, },
eventstore.UserSessionRepo{ eventstore.UserSessionRepo{
View: view, 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) 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) { func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request) return view.SearchApplications(v.Db, applicationTable, request)
} }
func (v *View) PutApplication(project *model.ApplicationView) error { func (v *View) PutApplication(app *model.ApplicationView) error {
err := view.PutApplication(v.Db, applicationTable, project) err := view.PutApplication(v.Db, applicationTable, app)
if err != nil { if err != nil {
return err 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 { 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) 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) { func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) {
return v.latestSequence(applicationTable) 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) { func (v *View) ApplicationByClientID(_ context.Context, clientID string) (*model.ApplicationView, error) {
req := &proj_model.ApplicationSearchRequest{ return view.ApplicationByOIDCClientID(v.Db, applicationTable, clientID)
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
} }
func (v *View) AppIDsFromProjectByClientID(ctx context.Context, clientID string) ([]string, error) { 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 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 { 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 { if err != nil {
return err 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 AuthRequestRepository
TokenRepository TokenRepository
ApplicationRepository ApplicationRepository
ProjectRepository
KeyRepository KeyRepository
UserSessionRepository UserSessionRepository
UserGrantRepository UserGrantRepository

View File

@ -22,6 +22,7 @@ const (
NextStepLinkUsers NextStepLinkUsers
NextStepExternalNotFoundOption NextStepExternalNotFoundOption
NextStepExternalLogin NextStepExternalLogin
NextStepGrantRequired
) )
type UserSessionState int32 type UserSessionState int32
@ -127,6 +128,12 @@ func (s *LinkUsersStep) Type() NextStepType {
return NextStepLinkUsers return NextStepLinkUsers
} }
type GrantRequiredStep struct{}
func (s *GrantRequiredStep) Type() NextStepType {
return NextStepGrantRequired
}
type RedirectToCallbackStep struct{} type RedirectToCallbackStep struct{}
func (s *RedirectToCallbackStep) Type() NextStepType { 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 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) ctxData := authz.GetCtxData(ctx)
iam, err := repo.IAMEvents.IAMByID(ctx, repo.IAMID) iam, err := repo.IAMEvents.IAMByID(ctx, repo.IAMID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
project := &proj_model.Project{Name: name}
return repo.ProjectEvents.CreateProject(ctx, project, iam.GlobalOrgID == ctxData.OrgID) 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 err
} }
return p.view.DeleteApplication(app.ID, event.Sequence) 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: case es_model.ProjectRemoved:
return p.view.DeleteApplicationsByProjectID(event.AggregateID) return p.view.DeleteApplicationsByProjectID(event.AggregateID)
default: 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) { func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) {
grant.OrgName = org.Name 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 { 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: case org_es_model.OrgMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence) return m.view.DeleteUserMembership(member.UserID, event.AggregateID, event.AggregateID, usr_model.MemberTypeOrganisation, event.Sequence)
case org_es_model.OrgChanged: case org_es_model.OrgChanged:
err = m.updateOrgDisplayName(event) return m.updateOrgDisplayName(event)
default: default:
return m.view.ProcessedUserMembershipSequence(event.Sequence) return m.view.ProcessedUserMembershipSequence(event.Sequence)
} }
@ -166,7 +166,7 @@ func (m *UserMembership) processProject(event *models.Event) (err error) {
case proj_es_model.ProjectGrantMemberRemoved: case proj_es_model.ProjectGrantMemberRemoved:
return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence) return m.view.DeleteUserMembership(member.UserID, event.AggregateID, member.ObjectID, usr_model.MemberTypeProjectGrant, event.Sequence)
case proj_es_model.ProjectChanged: case proj_es_model.ProjectChanged:
err = m.updateProjectDisplayName(event) return m.updateProjectDisplayName(event)
case proj_es_model.ProjectRemoved: case proj_es_model.ProjectRemoved:
return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence) return m.view.DeleteUserMembershipsByAggregateID(event.AggregateID, event.Sequence)
case proj_es_model.ProjectGrantRemoved: 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 { 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) 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) return view.ApplicationByID(v.Db, applicationTable, projectID, appID)
} }
func (v *View) ApplicationsByProjectID(ProjectID string) ([]*model.ApplicationView, error) { func (v *View) ApplicationsByProjectID(projectID string) ([]*model.ApplicationView, error) {
return view.ApplicationsByProjectID(v.Db, applicationTable, ProjectID) return view.ApplicationsByProjectID(v.Db, applicationTable, projectID)
} }
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) { func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, uint64, error) {
return view.SearchApplications(v.Db, applicationTable, request) return view.SearchApplications(v.Db, applicationTable, request)
} }
func (v *View) PutApplication(project *model.ApplicationView) error { func (v *View) PutApplication(app *model.ApplicationView) error {
err := view.PutApplication(v.Db, applicationTable, project) err := view.PutApplication(v.Db, applicationTable, app)
if err != nil { if err != nil {
return err 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 { 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) return v.ProcessedApplicationSequence(eventSequence)
} }
func (v *View) DeleteApplicationsByProjectID(ProjectID string) error { func (v *View) DeleteApplicationsByProjectID(projectID string) error {
return view.DeleteApplicationsByProjectID(v.Db, applicationTable, ProjectID) return view.DeleteApplicationsByProjectID(v.Db, applicationTable, projectID)
} }
func (v *View) GetLatestApplicationSequence() (*repository.CurrentSequence, error) { 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 { 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 { if err != nil {
return err return err
} }

View File

@ -8,7 +8,7 @@ import (
type ProjectRepository interface { type ProjectRepository interface {
ProjectByID(ctx context.Context, id string) (*model.ProjectView, error) 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) UpdateProject(ctx context.Context, project *model.Project) (*model.Project, error)
DeactivateProject(ctx context.Context, id string) (*model.Project, error) DeactivateProject(ctx context.Context, id string) (*model.Project, error)
ReactivateProject(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 { type ApplicationView struct {
ID string ID string
ProjectID string ProjectID string
Name string Name string
CreationDate time.Time CreationDate time.Time
ChangeDate time.Time ChangeDate time.Time
State AppState State AppState
ProjectRoleAssertion bool
ProjectRoleCheck bool
IsOIDC bool IsOIDC bool
OIDCVersion OIDCVersion OIDCVersion OIDCVersion
@ -27,6 +29,9 @@ type ApplicationView struct {
ComplianceProblems []string ComplianceProblems []string
DevMode bool DevMode bool
OriginAllowList []string OriginAllowList []string
AccessTokenType OIDCTokenType
IDTokenRoleAssertion bool
AccessTokenRoleAssertion bool
Sequence uint64 Sequence uint64
} }

View File

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

View File

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

View File

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

View File

@ -321,7 +321,7 @@ func (es *ProjectEventstore) AddProjectRoles(ctx context.Context, roles ...*proj
} }
for _, role := range roles { for _, role := range roles {
if !role.IsValid() { 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) existingProject, err := es.ProjectByID(ctx, roles[0].AggregateID)

View File

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

View File

@ -2,7 +2,9 @@ package model
import ( import (
"encoding/json" "encoding/json"
"github.com/caos/logging" "github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
) )
@ -13,12 +15,14 @@ const (
type Project struct { type Project struct {
es_models.ObjectRoot es_models.ObjectRoot
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
State int32 `json:"-"` ProjectRoleAssertion bool `json:"projectRoleAssertion,omitempty"`
Members []*ProjectMember `json:"-"` ProjectRoleCheck bool `json:"projectRoleCheck,omitempty"`
Roles []*ProjectRole `json:"-"` State int32 `json:"-"`
Applications []*Application `json:"-"` Members []*ProjectMember `json:"-"`
Grants []*ProjectGrant `json:"-"` Roles []*ProjectRole `json:"-"`
Applications []*Application `json:"-"`
Grants []*ProjectGrant `json:"-"`
} }
func GetProject(projects []*Project, id string) (int, *Project) { 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 { if changed.Name != "" && p.Name != changed.Name {
changes["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 return changes
} }
@ -44,13 +54,15 @@ func ProjectFromModel(project *model.Project) *Project {
apps := AppsFromModel(project.Applications) apps := AppsFromModel(project.Applications)
grants := GrantsFromModel(project.Grants) grants := GrantsFromModel(project.Grants)
return &Project{ return &Project{
ObjectRoot: project.ObjectRoot, ObjectRoot: project.ObjectRoot,
Name: project.Name, Name: project.Name,
State: int32(project.State), ProjectRoleAssertion: project.ProjectRoleAssertion,
Members: members, ProjectRoleCheck: project.ProjectRoleCheck,
Roles: roles, State: int32(project.State),
Applications: apps, Members: members,
Grants: grants, Roles: roles,
Applications: apps,
Grants: grants,
} }
} }
@ -60,13 +72,15 @@ func ProjectToModel(project *Project) *model.Project {
apps := AppsToModel(project.Applications) apps := AppsToModel(project.Applications)
grants := GrantsToModel(project.Grants) grants := GrantsToModel(project.Grants)
return &model.Project{ return &model.Project{
ObjectRoot: project.ObjectRoot, ObjectRoot: project.ObjectRoot,
Name: project.Name, Name: project.Name,
State: model.ProjectState(project.State), ProjectRoleAssertion: project.ProjectRoleAssertion,
Members: members, ProjectRoleCheck: project.ProjectRoleCheck,
Roles: roles, State: model.ProjectState(project.State),
Applications: apps, Members: members,
Grants: grants, 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) 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 { func DeleteApplication(db *gorm.DB, table, appID string) error {
delete := repository.PrepareDeleteByKey(table, model.ApplicationSearchKey(proj_model.AppSearchKeyAppID), appID) delete := repository.PrepareDeleteByKey(table, model.ApplicationSearchKey(proj_model.AppSearchKeyAppID), appID)
return delete(db) return delete(db)

View File

@ -23,12 +23,14 @@ const (
) )
type ApplicationView struct { type ApplicationView struct {
ID string `json:"appId" gorm:"column:id;primary_key"` ID string `json:"appId" gorm:"column:id;primary_key"`
ProjectID string `json:"-" gorm:"column:project_id"` ProjectID string `json:"-" gorm:"column:project_id"`
Name string `json:"name" gorm:"column:app_name"` Name string `json:"name" gorm:"column:app_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"` CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"` ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:app_state"` 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"` IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCVersion int32 `json:"oidcVersion" gorm:"column:oidc_version"` OIDCVersion int32 `json:"oidcVersion" gorm:"column:oidc_version"`
@ -43,58 +45,24 @@ type ApplicationView struct {
ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"` ComplianceProblems pq.StringArray `json:"-" gorm:"column:compliance_problems"`
DevMode bool `json:"devMode" gorm:"column:dev_mode"` DevMode bool `json:"devMode" gorm:"column:dev_mode"`
OriginAllowList pq.StringArray `json:"-" gorm:"column:origin_allow_list"` 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"` 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 { func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView {
return &model.ApplicationView{ return &model.ApplicationView{
ID: app.ID, ID: app.ID,
ProjectID: app.ProjectID, ProjectID: app.ProjectID,
Name: app.Name, Name: app.Name,
State: model.AppState(app.State), State: model.AppState(app.State),
Sequence: app.Sequence, Sequence: app.Sequence,
CreationDate: app.CreationDate, CreationDate: app.CreationDate,
ChangeDate: app.ChangeDate, ChangeDate: app.ChangeDate,
ProjectRoleAssertion: app.ProjectRoleAssertion,
ProjectRoleCheck: app.ProjectRoleCheck,
IsOIDC: app.IsOIDC, IsOIDC: app.IsOIDC,
OIDCVersion: model.OIDCVersion(app.OIDCVersion), OIDCVersion: model.OIDCVersion(app.OIDCVersion),
@ -109,6 +77,9 @@ func ApplicationViewToModel(app *ApplicationView) *model.ApplicationView {
ComplianceProblems: app.ComplianceProblems, ComplianceProblems: app.ComplianceProblems,
DevMode: app.DevMode, DevMode: app.DevMode,
OriginAllowList: app.OriginAllowList, 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: case es_model.ApplicationRemoved:
return view.SetData(event) return view.SetData(event)
case es_model.ProjectChanged:
return a.AppendEvent(event)
case es_model.ProjectRemoved: case es_model.ProjectRemoved:
return a.AppendEvent(event) return a.AppendEvent(event)
default: default:
@ -187,6 +160,8 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
} }
a.setCompliance() a.setCompliance()
return a.setOriginAllowList() return a.setOriginAllowList()
case es_model.ProjectChanged:
return a.SetData(event)
case es_model.ApplicationDeactivated: case es_model.ApplicationDeactivated:
a.State = int32(model.AppStateInactive) a.State = int32(model.AppStateInactive)
case es_model.ApplicationReactivated: case es_model.ApplicationReactivated:

View File

@ -2,12 +2,14 @@ package model
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/caos/logging" "github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"time"
) )
const ( const (
@ -17,36 +19,42 @@ const (
) )
type ProjectView struct { type ProjectView struct {
ProjectID string `json:"-" gorm:"column:project_id;primary_key"` ProjectID string `json:"-" gorm:"column:project_id;primary_key"`
Name string `json:"name" gorm:"column:project_name"` Name string `json:"name" gorm:"column:project_name"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"` CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"` ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:project_state"` State int32 `json:"-" gorm:"column:project_state"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"` ResourceOwner string `json:"-" gorm:"column:resource_owner"`
Sequence uint64 `json:"-" gorm:"column:sequence"` 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 { func ProjectFromModel(project *model.ProjectView) *ProjectView {
return &ProjectView{ return &ProjectView{
ProjectID: project.ProjectID, ProjectID: project.ProjectID,
Name: project.Name, Name: project.Name,
ChangeDate: project.ChangeDate, ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate, CreationDate: project.CreationDate,
State: int32(project.State), State: int32(project.State),
ResourceOwner: project.ResourceOwner, ResourceOwner: project.ResourceOwner,
Sequence: project.Sequence, ProjectRoleAssertion: project.ProjectRoleAssertion,
ProjectRoleCheck: project.ProjectRoleCheck,
Sequence: project.Sequence,
} }
} }
func ProjectToModel(project *ProjectView) *model.ProjectView { func ProjectToModel(project *ProjectView) *model.ProjectView {
return &model.ProjectView{ return &model.ProjectView{
ProjectID: project.ProjectID, ProjectID: project.ProjectID,
Name: project.Name, Name: project.Name,
ChangeDate: project.ChangeDate, ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate, CreationDate: project.CreationDate,
State: model.ProjectState(project.State), State: model.ProjectState(project.State),
ResourceOwner: project.ResourceOwner, ResourceOwner: project.ResourceOwner,
Sequence: project.Sequence, 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 { func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *model.ExternalUser {
displayName := tokens.IDTokenClaims.PreferredUsername displayName := tokens.IDTokenClaims.GetPreferredUsername()
if displayName == "" && tokens.IDTokenClaims.Email != "" { if displayName == "" && tokens.IDTokenClaims.GetEmail() != "" {
displayName = tokens.IDTokenClaims.Email displayName = tokens.IDTokenClaims.GetEmail()
} }
switch idpConfig.OIDCIDPDisplayNameMapping { switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail: case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" { if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
displayName = tokens.IDTokenClaims.Email displayName = tokens.IDTokenClaims.GetEmail()
} }
} }
externalUser := &model.ExternalUser{ externalUser := &model.ExternalUser{
IDPConfigID: idpConfig.IDPConfigID, IDPConfigID: idpConfig.IDPConfigID,
ExternalUserID: tokens.IDTokenClaims.Subject, ExternalUserID: tokens.IDTokenClaims.GetSubject(),
PreferredUsername: tokens.IDTokenClaims.PreferredUsername, PreferredUsername: tokens.IDTokenClaims.GetPreferredUsername(),
DisplayName: displayName, DisplayName: displayName,
FirstName: tokens.IDTokenClaims.GivenName, FirstName: tokens.IDTokenClaims.GetGivenName(),
LastName: tokens.IDTokenClaims.FamilyName, LastName: tokens.IDTokenClaims.GetFamilyName(),
NickName: tokens.IDTokenClaims.Nickname, NickName: tokens.IDTokenClaims.GetNickname(),
Email: tokens.IDTokenClaims.Email, Email: tokens.IDTokenClaims.GetEmail(),
IsEmailVerified: tokens.IDTokenClaims.EmailVerified, IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(),
} }
if tokens.IDTokenClaims.PhoneNumber != "" { if tokens.IDTokenClaims.GetPhoneNumber() != "" {
externalUser.Phone = tokens.IDTokenClaims.PhoneNumber externalUser.Phone = tokens.IDTokenClaims.GetPhoneNumber()
externalUser.IsPhoneVerified = tokens.IDTokenClaims.PhoneNumberVerified externalUser.IsPhoneVerified = tokens.IDTokenClaims.IsPhoneNumberVerified()
} }
return externalUser return externalUser
} }

View File

@ -1,8 +1,13 @@
package handler package handler
import ( import (
"net/http"
"strings"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp" "github.com/caos/oidc/pkg/rp"
"golang.org/x/text/language"
http_mw "github.com/caos/zitadel/internal/api/http/middleware" http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/model"
caos_errors "github.com/caos/zitadel/internal/errors" caos_errors "github.com/caos/zitadel/internal/errors"
@ -10,8 +15,6 @@ import (
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
org_model "github.com/caos/zitadel/internal/org/model" org_model "github.com/caos/zitadel/internal/org/model"
usr_model "github.com/caos/zitadel/internal/user/model" usr_model "github.com/caos/zitadel/internal/user/model"
"net/http"
"strings"
) )
func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) { 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) { 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 { switch idpConfig.OIDCUsernameMapping {
case iam_model.OIDCMappingFieldEmail: case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" { if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
username = tokens.IDTokenClaims.Email username = tokens.IDTokenClaims.GetEmail()
} }
} }
@ -128,35 +131,35 @@ func (l *Login) mapTokenToLoginUserAndExternalIDP(orgIamPolicy *iam_model.OrgIAM
UserName: username, UserName: username,
Human: &usr_model.Human{ Human: &usr_model.Human{
Profile: &usr_model.Profile{ Profile: &usr_model.Profile{
FirstName: tokens.IDTokenClaims.GivenName, FirstName: tokens.IDTokenClaims.GetGivenName(),
LastName: tokens.IDTokenClaims.FamilyName, LastName: tokens.IDTokenClaims.GetFamilyName(),
PreferredLanguage: tokens.IDTokenClaims.Locale, PreferredLanguage: language.Tag(tokens.IDTokenClaims.GetLocale()),
NickName: tokens.IDTokenClaims.Nickname, NickName: tokens.IDTokenClaims.GetNickname(),
}, },
Email: &usr_model.Email{ Email: &usr_model.Email{
EmailAddress: tokens.IDTokenClaims.Email, EmailAddress: tokens.IDTokenClaims.GetEmail(),
IsEmailVerified: tokens.IDTokenClaims.EmailVerified, IsEmailVerified: tokens.IDTokenClaims.IsEmailVerified(),
}, },
}, },
} }
if tokens.IDTokenClaims.PhoneNumber != "" { if tokens.IDTokenClaims.GetPhoneNumber() != "" {
user.Phone = &usr_model.Phone{ user.Phone = &usr_model.Phone{
PhoneNumber: tokens.IDTokenClaims.PhoneNumber, PhoneNumber: tokens.IDTokenClaims.GetPhoneNumber(),
IsPhoneVerified: tokens.IDTokenClaims.PhoneNumberVerified, IsPhoneVerified: tokens.IDTokenClaims.IsPhoneNumberVerified(),
} }
} }
displayName := tokens.IDTokenClaims.PreferredUsername displayName := tokens.IDTokenClaims.GetPreferredUsername()
switch idpConfig.OIDCIDPDisplayNameMapping { switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail: case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" { if tokens.IDTokenClaims.IsEmailVerified() && tokens.IDTokenClaims.GetEmail() != "" {
displayName = tokens.IDTokenClaims.Email displayName = tokens.IDTokenClaims.GetEmail()
} }
} }
externalIDP := &usr_model.ExternalIDP{ externalIDP := &usr_model.ExternalIDP{
IDPConfigID: idpConfig.IDPConfigID, IDPConfigID: idpConfig.IDPConfigID,
UserID: tokens.IDTokenClaims.Subject, UserID: tokens.IDTokenClaims.GetSubject(),
DisplayName: displayName, DisplayName: displayName,
} }
return user, externalIDP 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) l.renderExternalNotFoundOption(w, r, authReq, err)
case *model.ExternalLoginStep: case *model.ExternalLoginStep:
l.handleExternalLoginStep(w, r, authReq, step.SelectedIDPConfigID) 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: default:
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible")) l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
} }

View File

@ -187,6 +187,7 @@ Errors:
AuthRequest: AuthRequest:
NotFound: AuthRequest konnte nicht gefunden werden NotFound: AuthRequest konnte nicht gefunden werden
UserAgentNotCorresponding: User Agent stimmt nicht überein UserAgentNotCorresponding: User Agent stimmt nicht überein
RequestTypeNotSupported: Requesttyp wird nicht unterstürzt
User: User:
NotFound: Benutzer konnte nicht gefunden werden NotFound: Benutzer konnte nicht gefunden werden
NotMatchingUserID: User stimm nicht mit User in Auth Request überein NotMatchingUserID: User stimm nicht mit User in Auth Request überein
@ -226,5 +227,6 @@ Errors:
ExternalIDP: ExternalIDP:
IDPTypeNotImplemented: IDP Typ ist nicht implementiert IDPTypeNotImplemented: IDP Typ ist nicht implementiert
NotAllowed: Externer Login Provider ist nicht erlaubt 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) optional: (optional)

View File

@ -187,6 +187,7 @@ Errors:
AuthRequest: AuthRequest:
NotFound: Could not find authrequest NotFound: Could not find authrequest
UserAgentNotCorresponding: User Agent does not correspond UserAgentNotCorresponding: User Agent does not correspond
RequestTypeNotSupported: Request type is not supported
User: User:
NotFound: User could not be found NotFound: User could not be found
NotMatchingUserID: User and user in authrequest don't match NotMatchingUserID: User and user in authrequest don't match
@ -226,6 +227,7 @@ Errors:
ExternalIDP: ExternalIDP:
IDPTypeNotImplemented: IDP Type is not implemented IDPTypeNotImplemented: IDP Type is not implemented
NotAllowed: External Login Provider not allowed 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) optional: (optional)

View File

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

View File

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

View File

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

View File

@ -5,11 +5,12 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/lib/pq"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/usergrant/model" "github.com/caos/zitadel/internal/usergrant/model"
es_model "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
"github.com/lib/pq"
) )
const ( const (
@ -24,19 +25,20 @@ const (
) )
type UserGrantView struct { type UserGrantView struct {
ID string `json:"-" gorm:"column:id;primary_key"` ID string `json:"-" gorm:"column:id;primary_key"`
ResourceOwner string `json:"-" gorm:"resource_owner"` ResourceOwner string `json:"-" gorm:"resource_owner"`
UserID string `json:"userId" gorm:"user_id"` UserID string `json:"userId" gorm:"user_id"`
ProjectID string `json:"projectId" gorm:"column:project_id"` ProjectID string `json:"projectId" gorm:"column:project_id"`
GrantID string `json:"grantId" gorm:"column:grant_id"` GrantID string `json:"grantId" gorm:"column:grant_id"`
UserName string `json:"-" gorm:"column:user_name"` UserName string `json:"-" gorm:"column:user_name"`
FirstName string `json:"-" gorm:"column:first_name"` FirstName string `json:"-" gorm:"column:first_name"`
LastName string `json:"-" gorm:"column:last_name"` LastName string `json:"-" gorm:"column:last_name"`
DisplayName string `json:"-" gorm:"column:display_name"` DisplayName string `json:"-" gorm:"column:display_name"`
Email string `json:"-" gorm:"column:email"` Email string `json:"-" gorm:"column:email"`
ProjectName string `json:"-" gorm:"column:project_name"` ProjectName string `json:"-" gorm:"column:project_name"`
OrgName string `json:"-" gorm:"column:org_name"` OrgName string `json:"-" gorm:"column:org_name"`
RoleKeys pq.StringArray `json:"roleKeys" gorm:"column:role_keys"` 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"` CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"` ChangeDate time.Time `json:"-" gorm:"column:change_date"`
@ -47,22 +49,23 @@ type UserGrantView struct {
func UserGrantToModel(grant *UserGrantView) *model.UserGrantView { func UserGrantToModel(grant *UserGrantView) *model.UserGrantView {
return &model.UserGrantView{ return &model.UserGrantView{
ID: grant.ID, ID: grant.ID,
ResourceOwner: grant.ResourceOwner, ResourceOwner: grant.ResourceOwner,
UserID: grant.UserID, UserID: grant.UserID,
ProjectID: grant.ProjectID, ProjectID: grant.ProjectID,
ChangeDate: grant.ChangeDate, ChangeDate: grant.ChangeDate,
CreationDate: grant.CreationDate, CreationDate: grant.CreationDate,
State: model.UserGrantState(grant.State), State: model.UserGrantState(grant.State),
UserName: grant.UserName, UserName: grant.UserName,
FirstName: grant.FirstName, FirstName: grant.FirstName,
LastName: grant.LastName, LastName: grant.LastName,
DisplayName: grant.DisplayName, DisplayName: grant.DisplayName,
Email: grant.Email, Email: grant.Email,
ProjectName: grant.ProjectName, ProjectName: grant.ProjectName,
OrgName: grant.OrgName, OrgName: grant.OrgName,
RoleKeys: grant.RoleKeys, OrgPrimaryDomain: grant.OrgPrimaryDomain,
Sequence: grant.Sequence, 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 return nil
} }
@ -6462,6 +6466,10 @@ func (m *ProjectUpdateRequest) Validate() error {
} }
} }
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil return nil
} }
@ -6659,6 +6667,10 @@ func (m *ProjectView) Validate() error {
// no validation rules for Sequence // no validation rules for Sequence
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil return nil
} }
@ -6998,6 +7010,10 @@ func (m *Project) Validate() error {
// no validation rules for Sequence // no validation rules for Sequence
// no validation rules for ProjectRoleAssertion
// no validation rules for ProjectRoleCheck
return nil return nil
} }
@ -8850,6 +8866,12 @@ func (m *OIDCConfig) Validate() error {
// no validation rules for DevMode // no validation rules for DevMode
// no validation rules for AccessTokenType
// no validation rules for AccessTokenRoleAssertion
// no validation rules for IdTokenRoleAssertion
return nil return nil
} }
@ -8937,6 +8959,12 @@ func (m *OIDCApplicationCreate) Validate() error {
// no validation rules for DevMode // no validation rules for DevMode
// no validation rules for AccessTokenType
// no validation rules for AccessTokenRoleAssertion
// no validation rules for IdTokenRoleAssertion
return nil return nil
} }
@ -9024,6 +9052,12 @@ func (m *OIDCConfigUpdate) Validate() error {
// no validation rules for DevMode // no validation rules for DevMode
// no validation rules for AccessTokenType
// no validation rules for AccessTokenRoleAssertion
// no validation rules for IdTokenRoleAssertion
return nil return nil
} }

View File

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