feat: select idp and auto register (#2336)

* faet: auto regsiter config on idp

* feat: auto register on login

* feat: auto register on register

* feat: redirect to selected identity provider

* fix: test

* fix: test

* fix: user by id request org id

* fix: migration version and test

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2021-09-10 09:49:49 +02:00 committed by GitHub
parent 79fb8aa37a
commit e4bdaf26b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 272 additions and 111 deletions

View File

@ -30,5 +30,6 @@ In addition to the standard compliant scopes we utilize the following scopes.
| `urn:zitadel:iam:org:project:id:{projectid}:aud` | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token |
| urn:zitadel:iam:user:metadata | `urn:zitadel:iam:user:metadata` | By adding this scope, the metadata of the user will be included in the token. The values are base64 encoded. |
| urn:zitadel:iam:user:resourceowner | `urn:zitadel:iam:user:resourceowner` | By adding this scope, the resourceowner (id, name, primary_domain) of the user will be included in the token. |
| urn:zitadel:iam:org:idp:id:{idp_id} | `urn:zitadel:iam:org:idp:id:76625965177954913` | By adding this scope the user will directly be redirected to the identity provider to authenticate. Make sure you also send the primary domain scope if a custom login policy is configured. Otherwise the system will not be able to identify the identity provider. |
> If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request

View File

@ -1201,6 +1201,7 @@ This is an empty request
| scopes | repeated string | - | |
| display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true<br /> |
| username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true<br /> |
| auto_register | bool | - | |
@ -2887,6 +2888,7 @@ This is an empty request
| idp_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true<br /> |
| auto_register | bool | - | |

View File

@ -22,6 +22,7 @@ title: zitadel/idp.proto
| styling_type | IDPStylingType | - | |
| owner | IDPOwnerType | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.oidc_config | OIDCConfig | - | |
| auto_register | bool | - | |

View File

@ -3154,6 +3154,7 @@ This is an empty request
| scopes | repeated string | - | |
| display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true<br /> |
| username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true<br /> |
| auto_register | bool | - | |
@ -7379,6 +7380,7 @@ This is an empty request
| idp_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true<br /> |
| auto_register | bool | - | |

View File

@ -16,6 +16,7 @@ func addOIDCIDPRequestToDomain(req *admin_pb.AddOIDCIDPRequest) *domain.IDPConfi
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
Type: domain.IDPConfigTypeOIDC,
AutoRegister: req.AutoRegister,
}
}
@ -35,6 +36,7 @@ func updateIDPToDomain(req *admin_pb.UpdateIDPRequest) *domain.IDPConfig {
IDPConfigID: req.IdpId,
Name: req.Name,
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
AutoRegister: req.AutoRegister,
}
}

View File

@ -28,6 +28,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
Scopes: []string{"email", "profile"},
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
AutoRegister: true,
},
},
},
@ -101,6 +102,7 @@ func Test_updateIDPToDomain(t *testing.T) {
IdpId: "13523",
Name: "new name",
StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE,
AutoRegister: true,
},
},
},

View File

@ -22,13 +22,14 @@ func ModelIDPViewToPb(idp *iam_model.IDPConfigView) *idp_pb.IDP {
State: ModelIDPStateToPb(idp.State),
Name: idp.Name,
StylingType: ModelIDPStylingTypeToPb(idp.StylingType),
AutoRegister: idp.AutoRegister,
Owner: ModelIDPProviderTypeToPb(idp.IDPProviderType),
Config: ModelIDPViewToConfigPb(idp),
Details: obj_grpc.ToViewDetailsPb(
idp.Sequence,
idp.CreationDate,
idp.ChangeDate,
"", //TODO: backend
idp.AggregateID,
),
}
}
@ -39,8 +40,9 @@ func IDPViewToPb(idp *domain.IDPConfigView) *idp_pb.IDP {
State: IDPStateToPb(idp.State),
Name: idp.Name,
StylingType: IDPStylingTypeToPb(idp.StylingType),
AutoRegister: idp.AutoRegister,
Config: IDPViewToConfigPb(idp),
Details: obj_grpc.ToViewDetailsPb(idp.Sequence, idp.CreationDate, idp.ChangeDate, ""), //TODO: resource owner in view
Details: obj_grpc.ToViewDetailsPb(idp.Sequence, idp.CreationDate, idp.ChangeDate, idp.AggregateID),
}
return mapped
}

View File

@ -35,6 +35,7 @@ func updateIDPToDomain(req *mgmt_pb.UpdateOrgIDPRequest) *domain.IDPConfig {
IDPConfigID: req.IdpId,
Name: req.Name,
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
AutoRegister: req.AutoRegister,
}
}

View File

@ -28,6 +28,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
Scopes: []string{"email", "profile"},
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
AutoRegister: true,
},
},
},
@ -45,6 +46,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
"OIDCConfig.AuthorizationEndpoint",
"OIDCConfig.TokenEndpoint",
"Type", //TODO: default (0) is oidc
"AutoRegister",
)
})
}
@ -101,6 +103,7 @@ func Test_updateIDPToDomain(t *testing.T) {
IdpId: "13523",
Name: "new name",
StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE,
AutoRegister: true,
},
},
},

View File

@ -3,6 +3,7 @@ package oidc
import (
"context"
"net"
"strings"
"time"
"github.com/caos/oidc/pkg/oidc"
@ -10,6 +11,7 @@ import (
"golang.org/x/text/language"
http_utils "github.com/caos/zitadel/internal/api/http"
model2 "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/user/model"
@ -123,6 +125,7 @@ func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest,
PossibleLOAs: ACRValuesToBusiness(authReq.ACRValues),
UiLocales: UILocalesToBusiness(authReq.UILocales),
LoginHint: authReq.LoginHint,
SelectedIDPConfigID: GetSelectedIDPIDFromScopes(authReq.Scopes),
MaxAuthAge: MaxAgeToBusiness(authReq.MaxAge),
UserID: userID,
Request: &domain.AuthRequestOIDC{
@ -196,6 +199,15 @@ func UILocalesToBusiness(tags []language.Tag) []string {
return locales
}
func GetSelectedIDPIDFromScopes(scopes oidc.SpaceDelimitedArray) string {
for _, scope := range scopes {
if strings.HasPrefix(scope, model2.SelectIDPScope) {
return strings.TrimPrefix(scope, model2.SelectIDPScope)
}
}
return ""
}
func MaxAgeToBusiness(maxAge *uint) *time.Duration {
if maxAge == nil {
return nil

View File

@ -106,6 +106,9 @@ func (c *Client) IsScopeAllowed(scope string) bool {
if strings.HasPrefix(scope, authreq_model.ProjectIDScope) {
return true
}
if strings.HasPrefix(scope, authreq_model.SelectIDPScope) {
return true
}
if strings.HasPrefix(scope, ScopeUserMetaData) {
return true
}

View File

@ -133,6 +133,7 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *dom
err = repo.checkLoginName(ctx, request, request.LoginHint)
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID, "traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Debug("login hint invalid")
}
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
if err != nil {
return nil, err
@ -642,7 +643,13 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
if err != nil {
return nil, err
}
if len(users) > 0 || domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) {
if domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) {
steps = append(steps, &domain.SelectUserStep{Users: users})
}
if request.SelectedIDPConfigID != "" {
steps = append(steps, &domain.RedirectToExternalIDPStep{})
}
if len(request.Prompt) == 0 && len(users) > 0 {
steps = append(steps, &domain.SelectUserStep{Users: users})
}
}

View File

@ -305,6 +305,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
[]domain.NextStep{&domain.ExternalNotFoundOptionStep{}},
nil,
},
{
"user not set no active session selected idp, redirect to external idp step",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&domain.AuthRequest{SelectedIDPConfigID: "id"}, false},
[]domain.NextStep{&domain.LoginStep{}, &domain.RedirectToExternalIDPStep{}},
nil,
},
{
"user not set, prompt select account and internal error, internal error",
fields{

View File

@ -23,6 +23,7 @@ const (
OrgDomainPrimaryClaim = "urn:zitadel:iam:org:domain:primary"
ProjectIDScope = "urn:zitadel:iam:org:project:id:"
AudSuffix = ":aud"
SelectIDPScope = "urn:zitadel:iam:org:idp:id:"
)
type AuthRequestOIDC struct {

View File

@ -135,6 +135,7 @@ func writeModelToIDPConfig(wm *IDPConfigWriteModel) *domain.IDPConfig {
Name: wm.Name,
State: wm.State,
StylingType: wm.StylingType,
AutoRegister: wm.AutoRegister,
}
}

View File

@ -37,6 +37,7 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
config.Name,
config.Type,
config.StylingType,
config.AutoRegister,
),
iam_repo.NewIDPOIDCConfigAddedEvent(
ctx,
@ -73,11 +74,11 @@ func (c *Commands) ChangeDefaultIDPConfig(ctx context.Context, config *domain.ID
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "IAM-m0e3r", "Errors.IDPConfig.NotExisting")
}
iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel)
changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, iamAgg, config.IDPConfigID, config.Name, config.StylingType)
changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, iamAgg, config.IDPConfigID, config.Name, config.StylingType, config.AutoRegister)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged")
}
@ -98,7 +99,7 @@ func (c *Commands) DeactivateDefaultIDPConfig(ctx context.Context, idpID string)
return nil, err
}
if existingIDP.State != domain.IDPConfigStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotActive")
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-2n0fs", "Errors.IAM.IDPConfig.NotActive")
}
iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewIDPConfigDeactivatedEvent(ctx, iamAgg, idpID))
@ -173,7 +174,7 @@ func (c *Commands) getIAMIDPConfigByID(ctx context.Context, idpID string) (*doma
return nil, err
}
if !config.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "IAM-p0pFF", "Errors.IDPConfig.NotExisting")
}
return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil
}

View File

@ -100,6 +100,7 @@ func (wm *IAMIDPConfigWriteModel) NewChangedEvent(
configID,
name string,
stylingType domain.IDPConfigStylingType,
autoRegister bool,
) (*iam.IDPConfigChangedEvent, bool) {
changes := make([]idpconfig.IDPConfigChanges, 0)
@ -111,6 +112,9 @@ func (wm *IAMIDPConfigWriteModel) NewChangedEvent(
if stylingType.Valid() && wm.StylingType != stylingType {
changes = append(changes, idpconfig.ChangeStyleType(stylingType))
}
if wm.AutoRegister != autoRegister {
changes = append(changes, idpconfig.ChangeAutoRegister(autoRegister))
}
if len(changes) == 0 {
return nil, false
}

View File

@ -68,6 +68,7 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -101,6 +102,7 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) {
config: &domain.IDPConfig{
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
AutoRegister: true,
OIDCConfig: &domain.OIDCIDPConfig{
ClientID: "clientid1",
Issuer: "issuer",
@ -123,6 +125,7 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) {
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
State: domain.IDPConfigStateActive,
AutoRegister: true,
},
},
},
@ -212,6 +215,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -237,7 +241,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultIDPConfigChangedEvent(context.Background(), "config1", "name1", "name2", domain.IDPConfigStylingTypeUnspecified),
newDefaultIDPConfigChangedEvent(context.Background(), "config1", "name1", "name2", domain.IDPConfigStylingTypeUnspecified, false),
),
},
uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name1", "IAM")),
@ -251,6 +255,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) {
IDPConfigID: "config1",
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
AutoRegister: false,
},
},
res: res{
@ -263,6 +268,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) {
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
State: domain.IDPConfigStateActive,
AutoRegister: false,
},
},
},
@ -286,7 +292,7 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) {
}
}
func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, newName string, stylingType domain.IDPConfigStylingType) *iam.IDPConfigChangedEvent {
func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, newName string, stylingType domain.IDPConfigStylingType, autoRegister bool) *iam.IDPConfigChangedEvent {
event, _ := iam.NewIDPConfigChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
configID,
@ -294,6 +300,7 @@ func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, new
[]idpconfig.IDPConfigChanges{
idpconfig.ChangeName(newName),
idpconfig.ChangeStyleType(stylingType),
idpconfig.ChangeAutoRegister(autoRegister),
},
)
return event

View File

@ -84,6 +84,7 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -138,6 +139,7 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -193,6 +195,7 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(

View File

@ -344,6 +344,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
),
@ -391,6 +392,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
),

View File

@ -13,6 +13,7 @@ type IDPConfigWriteModel struct {
ConfigID string
Name string
AutoRegister bool
StylingType domain.IDPConfigStylingType
}
@ -42,6 +43,7 @@ func (rm *IDPConfigWriteModel) reduceConfigAddedEvent(e *idpconfig.IDPConfigAdde
rm.ConfigID = e.ConfigID
rm.Name = e.Name
rm.StylingType = e.StylingType
rm.AutoRegister = e.AutoRegister
rm.State = domain.IDPConfigStateActive
}
@ -52,6 +54,9 @@ func (rm *IDPConfigWriteModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigCh
if e.StylingType != nil && e.StylingType.Valid() {
rm.StylingType = *e.StylingType
}
if e.AutoRegister != nil {
rm.AutoRegister = *e.AutoRegister
}
}
func (rm *IDPConfigWriteModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) {

View File

@ -40,6 +40,7 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
config.Name,
config.Type,
config.StylingType,
config.AutoRegister,
),
org_repo.NewIDPOIDCConfigAddedEvent(
ctx,
@ -69,12 +70,12 @@ func (c *Commands) ChangeIDPConfig(ctx context.Context, config *domain.IDPConfig
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Gh8ds", "Errors.ResourceOwnerMissing")
}
existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, config.IDPConfigID, config.AggregateID)
existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, config.IDPConfigID, resourceOwner)
if err != nil {
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "Org-4M9so", "Errors.Org.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "Org-1J9fs", "Errors.Org.IDPConfig.NotExisting")
}
orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel)
@ -83,7 +84,8 @@ func (c *Commands) ChangeIDPConfig(ctx context.Context, config *domain.IDPConfig
orgAgg,
config.IDPConfigID,
config.Name,
config.StylingType)
config.StylingType,
config.AutoRegister)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4M9vs", "Errors.Org.LabelPolicy.NotChanged")
@ -105,7 +107,7 @@ func (c *Commands) DeactivateIDPConfig(ctx context.Context, idpID, orgID string)
return nil, err
}
if existingIDP.State != domain.IDPConfigStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4M9so", "Errors.Org.IDPConfig.NotActive")
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-BBmd0", "Errors.Org.IDPConfig.NotActive")
}
orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org_repo.NewIDPConfigDeactivatedEvent(ctx, orgAgg, idpID))
@ -185,7 +187,7 @@ func (c *Commands) getOrgIDPConfigByID(ctx context.Context, idpID, orgID string)
return nil, err
}
if !config.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "ORG-4M9so", "Errors.Org.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "ORG-2m90f", "Errors.Org.IDPConfig.NotExisting")
}
return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil
}

View File

@ -100,6 +100,7 @@ func (wm *OrgIDPConfigWriteModel) NewChangedEvent(
configID,
name string,
stylingType domain.IDPConfigStylingType,
autoRegister bool,
) (*org.IDPConfigChangedEvent, bool) {
changes := make([]idpconfig.IDPConfigChanges, 0)
@ -111,6 +112,9 @@ func (wm *OrgIDPConfigWriteModel) NewChangedEvent(
if stylingType.Valid() && wm.StylingType != stylingType {
changes = append(changes, idpconfig.ChangeStyleType(stylingType))
}
if wm.AutoRegister != autoRegister {
changes = append(changes, idpconfig.ChangeAutoRegister(autoRegister))
}
if len(changes) == 0 {
return nil, false
}

View File

@ -52,6 +52,7 @@ func TestCommandSide_AddIDPConfig(t *testing.T) {
config: &domain.IDPConfig{
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
AutoRegister: true,
OIDCConfig: &domain.OIDCIDPConfig{
ClientID: "clientid1",
Issuer: "issuer",
@ -96,6 +97,7 @@ func TestCommandSide_AddIDPConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -130,6 +132,7 @@ func TestCommandSide_AddIDPConfig(t *testing.T) {
config: &domain.IDPConfig{
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
AutoRegister: true,
OIDCConfig: &domain.OIDCIDPConfig{
ClientID: "clientid1",
Issuer: "issuer",
@ -152,6 +155,7 @@ func TestCommandSide_AddIDPConfig(t *testing.T) {
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
State: domain.IDPConfigStateActive,
AutoRegister: true,
},
},
},
@ -260,6 +264,7 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -300,6 +305,7 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) {
IDPConfigID: "config1",
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
AutoRegister: true,
},
},
res: res{
@ -312,6 +318,7 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) {
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
State: domain.IDPConfigStateActive,
AutoRegister: true,
},
},
},

View File

@ -104,6 +104,7 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -159,6 +160,7 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(
@ -215,6 +217,7 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) {
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
true,
),
),
eventFromEventPusher(

View File

@ -659,6 +659,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
),
@ -710,6 +711,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
),

View File

@ -164,6 +164,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
),
@ -213,6 +214,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
),

View File

@ -15,6 +15,7 @@ type IDPConfig struct {
StylingType IDPConfigStylingType
State IDPConfigState
OIDCConfig *OIDCIDPConfig
AutoRegister bool
}
type IDPConfigView struct {
@ -27,6 +28,7 @@ type IDPConfigView struct {
ChangeDate time.Time
Sequence uint64
IDPProviderType IdentityProviderType
AutoRegister bool
IsOIDC bool
OIDCClientID string

View File

@ -27,6 +27,7 @@ const (
NextStepPasswordlessRegistrationPrompt
NextStepRegistration
NextStepProjectRequired
NextStepRedirectToExternalIDP
)
type LoginStep struct{}
@ -67,6 +68,12 @@ const (
UserSessionStateTerminated
)
type RedirectToExternalIDPStep struct{}
func (s *RedirectToExternalIDPStep) Type() NextStepType {
return NextStepRedirectToExternalIDP
}
type InitUserStep struct {
PasswordSet bool
}

View File

@ -13,6 +13,7 @@ type IDPConfigView struct {
IDPConfigID string
Name string
StylingType IDPStylingType
AutoRegister bool
State IDPConfigState
CreationDate time.Time
ChangeDate time.Time

View File

@ -33,6 +33,7 @@ type IDPConfigView struct {
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
IDPState int32 `json:"-" gorm:"column:idp_state"`
IDPProviderType int32 `json:"-" gorm:"column:idp_provider_type"`
AutoRegister bool `json:"autoRegister" gorm:"column:auto_register"`
IsOIDC bool `json:"-" gorm:"column:is_oidc"`
OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"`
@ -54,6 +55,7 @@ func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
State: model.IDPConfigState(idp.IDPState),
Name: idp.Name,
StylingType: model.IDPStylingType(idp.StylingType),
AutoRegister: idp.AutoRegister,
Sequence: idp.Sequence,
CreationDate: idp.CreationDate,
ChangeDate: idp.ChangeDate,

View File

@ -12,6 +12,7 @@ type IDPConfigReadModel struct {
State domain.IDPConfigState
ConfigID string
Name string
AutoRegister bool
StylingType domain.IDPConfigStylingType
ProviderType domain.IdentityProviderType
@ -77,6 +78,7 @@ func (rm *IDPConfigReadModel) reduceConfigAddedEvent(e *idpconfig.IDPConfigAdded
rm.Name = e.Name
rm.StylingType = e.StylingType
rm.State = domain.IDPConfigStateActive
rm.AutoRegister = e.AutoRegister
}
func (rm *IDPConfigReadModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigChangedEvent) {
@ -86,6 +88,9 @@ func (rm *IDPConfigReadModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigCha
if e.StylingType != nil && e.StylingType.Valid() {
rm.StylingType = *e.StylingType
}
if e.AutoRegister != nil {
rm.AutoRegister = *e.AutoRegister
}
}
func (rm *IDPConfigReadModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) {

View File

@ -28,6 +28,7 @@ func NewIDPConfigAddedEvent(
name string,
configType domain.IDPConfigType,
stylingType domain.IDPConfigStylingType,
autoRegister bool,
) *IDPConfigAddedEvent {
return &IDPConfigAddedEvent{
@ -41,6 +42,7 @@ func NewIDPConfigAddedEvent(
name,
configType,
stylingType,
autoRegister,
),
}
}

View File

@ -33,6 +33,7 @@ type IDPConfigAddedEvent struct {
Name string `json:"name,omitempty"`
Typ domain.IDPConfigType `json:"idpType,omitempty"`
StylingType domain.IDPConfigStylingType `json:"stylingType,omitempty"`
AutoRegister bool `json:"autoRegister,omitempty"`
}
func NewIDPConfigAddedEvent(
@ -41,6 +42,7 @@ func NewIDPConfigAddedEvent(
name string,
configType domain.IDPConfigType,
stylingType domain.IDPConfigStylingType,
autoRegister bool,
) *IDPConfigAddedEvent {
return &IDPConfigAddedEvent{
BaseEvent: *base,
@ -48,6 +50,7 @@ func NewIDPConfigAddedEvent(
Name: name,
StylingType: stylingType,
Typ: configType,
AutoRegister: autoRegister,
}
}
@ -78,6 +81,7 @@ type IDPConfigChangedEvent struct {
ConfigID string `json:"idpConfigId"`
Name *string `json:"name,omitempty"`
StylingType *domain.IDPConfigStylingType `json:"stylingType,omitempty"`
AutoRegister *bool `json:"autoRegister,omitempty"`
oldName string `json:"-"`
}
@ -129,6 +133,12 @@ func ChangeStyleType(styleType domain.IDPConfigStylingType) func(*IDPConfigChang
}
}
func ChangeAutoRegister(autoRegister bool) func(*IDPConfigChangedEvent) {
return func(e *IDPConfigChangedEvent) {
e.AutoRegister = &autoRegister
}
}
func IDPConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &IDPConfigChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -28,6 +28,7 @@ func NewIDPConfigAddedEvent(
name string,
configType domain.IDPConfigType,
stylingType domain.IDPConfigStylingType,
autoRegister bool,
) *IDPConfigAddedEvent {
return &IDPConfigAddedEvent{
@ -41,6 +42,7 @@ func NewIDPConfigAddedEvent(
name,
configType,
stylingType,
autoRegister,
),
}
}

View File

@ -160,9 +160,18 @@ func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.R
if errors.IsNotFound(err) {
err = nil
}
if !idpConfig.AutoRegister {
l.renderExternalNotFoundOption(w, r, authReq, err)
return
}
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, err)
return
}
l.handleAutoRegister(w, r, authReq)
return
}
l.renderNextStep(w, r, authReq)
}

View File

@ -119,7 +119,27 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques
return
}
user, externalIDP := l.mapTokenToLoginHumanAndExternalIDP(orgIamPolicy, tokens, idpConfig)
if !idpConfig.AutoRegister {
l.renderExternalRegisterOverview(w, r, authReq, orgIamPolicy, user, externalIDP, nil)
return
}
l.registerExternalUser(w, r, authReq, iam, user, externalIDP)
}
func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.ExternalIDP) {
resourceOwner := iam.GlobalOrgID
memberRoles := []string{domain.RoleOrgProjectCreator}
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
memberRoles = nil
resourceOwner = authReq.RequestedOrgID
}
_, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, externalIDP, memberRoles)
if err != nil {
l.renderRegisterOption(w, r, authReq, err)
return
}
l.renderNextStep(w, r, authReq)
}
func (l *Login) renderExternalRegisterOverview(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *iam_model.OrgIAMPolicyView, human *domain.Human, idp *domain.ExternalIDP, err error) {

View File

@ -223,7 +223,7 @@ func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
if err != nil {
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(err, "APP-sio0W", "could not get authreq"))
l.renderInternalError(w, r, authReq, err)
return
}
if len(authReq.PossibleSteps) == 0 {
@ -257,6 +257,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
l.renderRegisterOption(w, r, authReq, nil)
case *domain.SelectUserStep:
l.renderUserSelection(w, r, authReq, step)
case *domain.RedirectToExternalIDPStep:
l.handleIDP(w, r, authReq, authReq.SelectedIDPConfigID)
case *domain.InitPasswordStep:
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
case *domain.PasswordStep:

View File

@ -0,0 +1,3 @@
ALTER TABLE management.idp_configs ADD COLUMN auto_register BOOLEAN;
ALTER TABLE adminapi.idp_configs ADD COLUMN auto_register BOOLEAN;
ALTER TABLE auth.idp_configs ADD COLUMN auto_register BOOLEAN;

View File

@ -2428,6 +2428,7 @@ message AddOIDCIDPRequest {
description: "definition which field is mapped to the email of the user";
}
];
bool auto_register = 9;
}
message AddOIDCIDPResponse {
@ -2458,6 +2459,7 @@ message UpdateIDPRequest {
description: "some identity providers specify the styling of the button to their login";
}
];
bool auto_register = 4;
}
message UpdateIDPResponse {

View File

@ -38,6 +38,7 @@ message IDP {
oneof config {
OIDCConfig oidc_config = 7;
}
bool auto_register = 8;
}
message IDPUserLink {

View File

@ -4884,6 +4884,7 @@ message AddOrgOIDCIDPRequest {
description: "definition which field is mapped to the email of the user";
}
];
bool auto_register = 9;
}
message AddOrgOIDCIDPResponse {
@ -4928,6 +4929,7 @@ message UpdateOrgIDPRequest {
description: "some identity providers specify the styling of the button to their login";
}
];
bool auto_register = 4;
}
message UpdateOrgIDPResponse {