mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:47:32 +00:00
feat: Identity brokering (#730)
* feat: add/ remove external idps * feat: external idp add /remove * fix: auth proto * fix: handle login * feat: loginpolicy on authrequest * feat: idp providers on login * feat: link external idp * fix: check login policy on check username * feat: add mapping fields for idp config * feat: use user org id if existing * feat: use user org id if existing * feat: register external user * feat: register external user * feat: user linking * feat: user linking * feat: design external login * feat: design external login * fix: tests * fix: regenerate login design * feat: next step test linking process * feat: next step test linking process * feat: cascade remove external idps on user * fix: tests * fix: tests * feat: external idp requsts on users * fix: generate protos * feat: login styles * feat: login styles * fix: link user * fix: register user on specifig org * fix: user linking * fix: register external, linking auto * fix: remove unnecessary request from proto * fix: tests * fix: new oidc package * fix: migration version * fix: policy permissions * Update internal/ui/login/static/i18n/en.yaml Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/ui/login/static/i18n/en.yaml Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/ui/login/handler/renderer.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/ui/login/handler/renderer.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: pr requests * Update internal/ui/login/handler/link_users_handler.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: pr requests * fix: pr requests * fix: pr requests * fix: login name size * fix: profile image light * fix: colors * fix: pr requests * fix: remove redirect uri validator * fix: remove redirect uri validator Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
@@ -13,10 +13,12 @@ func createOidcIdpToModel(idp *admin.OidcIdpConfigCreate) *iam_model.IDPConfig {
|
||||
LogoSrc: idp.LogoSrc,
|
||||
Type: iam_model.IDPConfigTypeOIDC,
|
||||
OIDCConfig: &iam_model.OIDCIDPConfig{
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -31,11 +33,13 @@ func updateIdpToModel(idp *admin.IdpUpdate) *iam_model.IDPConfig {
|
||||
|
||||
func updateOidcIdpToModel(idp *admin.OidcIdpConfigUpdate) *iam_model.OIDCIDPConfig {
|
||||
return &iam_model.OIDCIDPConfig{
|
||||
IDPConfigID: idp.IdpId,
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IDPConfigID: idp.IdpId,
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,9 +109,11 @@ func idpConfigViewFromModel(idp *iam_model.IDPConfigView) *admin.IdpView_OidcCon
|
||||
|
||||
func oidcIdpConfigViewFromModel(idp *iam_model.IDPConfigView) *admin.OidcIdpConfigView {
|
||||
return &admin.OidcIdpConfigView{
|
||||
ClientId: idp.OIDCClientID,
|
||||
Issuer: idp.OIDCIssuer,
|
||||
Scopes: idp.OIDCScopes,
|
||||
ClientId: idp.OIDCClientID,
|
||||
Issuer: idp.OIDCIssuer,
|
||||
Scopes: idp.OIDCScopes,
|
||||
IdpDisplayNameMapping: oidcMappingFieldFromModel(idp.OIDCIDPDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldFromModel(idp.OIDCUsernameMapping),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +128,28 @@ func idpConfigStateFromModel(state iam_model.IDPConfigState) admin.IdpState {
|
||||
}
|
||||
}
|
||||
|
||||
func oidcMappingFieldFromModel(field iam_model.OIDCMappingField) admin.OIDCMappingField {
|
||||
switch field {
|
||||
case iam_model.OIDCMappingFieldPreferredLoginName:
|
||||
return admin.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME
|
||||
case iam_model.OIDCMappingFieldEmail:
|
||||
return admin.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL
|
||||
default:
|
||||
return admin.OIDCMappingField_OIDCMAPPINGFIELD_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func oidcMappingFieldToModel(field admin.OIDCMappingField) iam_model.OIDCMappingField {
|
||||
switch field {
|
||||
case admin.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME:
|
||||
return iam_model.OIDCMappingFieldPreferredLoginName
|
||||
case admin.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL:
|
||||
return iam_model.OIDCMappingFieldEmail
|
||||
default:
|
||||
return iam_model.OIDCMappingFieldUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func idpConfigSearchRequestToModel(request *admin.IdpSearchRequest) *iam_model.IDPConfigSearchRequest {
|
||||
return &iam_model.IDPConfigSearchRequest{
|
||||
Limit: request.Limit,
|
||||
|
@@ -122,6 +122,19 @@ func (s *Server) ChangeMyPassword(ctx context.Context, request *auth.PasswordCha
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) SearchMyExternalIDPs(ctx context.Context, request *auth.ExternalIDPSearchRequest) (*auth.ExternalIDPSearchResponse, error) {
|
||||
externalIDP, err := s.repo.SearchMyExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return externalIDPSearchResponseFromModel(externalIDP), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMyExternalIDP(ctx context.Context, request *auth.ExternalIDPRemoveRequest) (*empty.Empty, error) {
|
||||
err := s.repo.RemoveMyExternalIDP(ctx, externalIDPRemoveToModel(ctx, request))
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) GetMyPasswordComplexityPolicy(ctx context.Context, _ *empty.Empty) (*auth.PasswordComplexityPolicy, error) {
|
||||
policy, err := s.repo.GetMyPasswordComplexityPolicy(ctx)
|
||||
if err != nil {
|
||||
|
@@ -3,7 +3,6 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/text/language"
|
||||
@@ -242,6 +241,69 @@ func updateAddressToModel(ctx context.Context, address *auth.UpdateUserAddressRe
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPSearchRequestToModel(request *auth.ExternalIDPSearchRequest) *usr_model.ExternalIDPSearchRequest {
|
||||
return &usr_model.ExternalIDPSearchRequest{
|
||||
Limit: request.Limit,
|
||||
Offset: request.Offset,
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPRemoveToModel(ctx context.Context, idp *auth.ExternalIDPRemoveRequest) *usr_model.ExternalIDP {
|
||||
return &usr_model.ExternalIDP{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: authz.GetCtxData(ctx).UserID},
|
||||
IDPConfigID: idp.IdpConfigId,
|
||||
UserID: idp.ExternalUserId,
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPResponseFromModel(idp *usr_model.ExternalIDP) *auth.ExternalIDPResponse {
|
||||
return &auth.ExternalIDPResponse{
|
||||
IdpConfigId: idp.IDPConfigID,
|
||||
UserId: idp.UserID,
|
||||
DisplayName: idp.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPSearchResponseFromModel(response *usr_model.ExternalIDPSearchResponse) *auth.ExternalIDPSearchResponse {
|
||||
viewTimestamp, err := ptypes.TimestampProto(response.Timestamp)
|
||||
logging.Log("GRPC-3h8is").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &auth.ExternalIDPSearchResponse{
|
||||
Offset: response.Offset,
|
||||
Limit: response.Limit,
|
||||
TotalResult: response.TotalResult,
|
||||
ProcessedSequence: response.Sequence,
|
||||
ViewTimestamp: viewTimestamp,
|
||||
Result: externalIDPViewsFromModel(response.Result),
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPViewsFromModel(externalIDPs []*usr_model.ExternalIDPView) []*auth.ExternalIDPView {
|
||||
converted := make([]*auth.ExternalIDPView, len(externalIDPs))
|
||||
for i, externalIDP := range externalIDPs {
|
||||
converted[i] = externalIDPViewFromModel(externalIDP)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func externalIDPViewFromModel(externalIDP *usr_model.ExternalIDPView) *auth.ExternalIDPView {
|
||||
creationDate, err := ptypes.TimestampProto(externalIDP.CreationDate)
|
||||
logging.Log("GRPC-Sj8dw").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
changeDate, err := ptypes.TimestampProto(externalIDP.ChangeDate)
|
||||
logging.Log("GRPC-Nf8ue").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &auth.ExternalIDPView{
|
||||
UserId: externalIDP.UserID,
|
||||
IdpConfigId: externalIDP.IDPConfigID,
|
||||
ExternalUserId: externalIDP.ExternalUserID,
|
||||
ExternalUserDisplayName: externalIDP.UserDisplayName,
|
||||
IdpName: externalIDP.IDPName,
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
}
|
||||
}
|
||||
|
||||
func otpFromModel(otp *usr_model.OTP) *auth.MfaOtpResponse {
|
||||
return &auth.MfaOtpResponse{
|
||||
UserId: otp.AggregateID,
|
||||
|
@@ -13,10 +13,12 @@ func createOidcIdpToModel(idp *management.OidcIdpConfigCreate) *iam_model.IDPCon
|
||||
LogoSrc: idp.LogoSrc,
|
||||
Type: iam_model.IDPConfigTypeOIDC,
|
||||
OIDCConfig: &iam_model.OIDCIDPConfig{
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -31,11 +33,13 @@ func updateIdpToModel(idp *management.IdpUpdate) *iam_model.IDPConfig {
|
||||
|
||||
func updateOidcIdpToModel(idp *management.OidcIdpConfigUpdate) *iam_model.OIDCIDPConfig {
|
||||
return &iam_model.OIDCIDPConfig{
|
||||
IDPConfigID: idp.IdpId,
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IDPConfigID: idp.IdpId,
|
||||
ClientID: idp.ClientId,
|
||||
ClientSecretString: idp.ClientSecret,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IDPDisplayNameMapping: oidcMappingFieldToModel(idp.IdpDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldToModel(idp.UsernameMapping),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +93,11 @@ func idpConfigFromModel(idp *iam_model.IDPConfig) *management.Idp_OidcConfig {
|
||||
|
||||
func oidcIdpConfigFromModel(idp *iam_model.OIDCIDPConfig) *management.OidcIdpConfig {
|
||||
return &management.OidcIdpConfig{
|
||||
ClientId: idp.ClientID,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
ClientId: idp.ClientID,
|
||||
Issuer: idp.Issuer,
|
||||
Scopes: idp.Scopes,
|
||||
IdpDisplayNameMapping: oidcMappingFieldFromModel(idp.IDPDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldFromModel(idp.UsernameMapping),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +112,11 @@ func idpConfigViewFromModel(idp *iam_model.IDPConfigView) *management.IdpView_Oi
|
||||
|
||||
func oidcIdpConfigViewFromModel(idp *iam_model.IDPConfigView) *management.OidcIdpConfigView {
|
||||
return &management.OidcIdpConfigView{
|
||||
ClientId: idp.OIDCClientID,
|
||||
Issuer: idp.OIDCIssuer,
|
||||
Scopes: idp.OIDCScopes,
|
||||
ClientId: idp.OIDCClientID,
|
||||
Issuer: idp.OIDCIssuer,
|
||||
Scopes: idp.OIDCScopes,
|
||||
IdpDisplayNameMapping: oidcMappingFieldFromModel(idp.OIDCIDPDisplayNameMapping),
|
||||
UsernameMapping: oidcMappingFieldFromModel(idp.OIDCUsernameMapping),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,3 +189,25 @@ func idpConfigsFromView(viewIdps []*iam_model.IDPConfigView) []*management.IdpVi
|
||||
}
|
||||
return idps
|
||||
}
|
||||
|
||||
func oidcMappingFieldFromModel(field iam_model.OIDCMappingField) management.OIDCMappingField {
|
||||
switch field {
|
||||
case iam_model.OIDCMappingFieldPreferredLoginName:
|
||||
return management.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME
|
||||
case iam_model.OIDCMappingFieldEmail:
|
||||
return management.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL
|
||||
default:
|
||||
return management.OIDCMappingField_OIDCMAPPINGFIELD_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func oidcMappingFieldToModel(field management.OIDCMappingField) iam_model.OIDCMappingField {
|
||||
switch field {
|
||||
case management.OIDCMappingField_OIDCMAPPINGFIELD_PREFERRED_USERNAME:
|
||||
return iam_model.OIDCMappingFieldPreferredLoginName
|
||||
case management.OIDCMappingField_OIDCMAPPINGFIELD_EMAIL:
|
||||
return iam_model.OIDCMappingFieldEmail
|
||||
default:
|
||||
return iam_model.OIDCMappingFieldUnspecified
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/pkg/grpc/management"
|
||||
@@ -196,6 +195,19 @@ func (s *Server) SetInitialPassword(ctx context.Context, request *management.Pas
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) SearchUserExternalIDPs(ctx context.Context, request *management.ExternalIDPSearchRequest) (*management.ExternalIDPSearchResponse, error) {
|
||||
externalIDP, err := s.user.SearchExternalIDPs(ctx, externalIDPSearchRequestToModel(request))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return externalIDPSearchResponseFromModel(externalIDP), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveExternalIDP(ctx context.Context, request *management.ExternalIDPRemoveRequest) (*empty.Empty, error) {
|
||||
err := s.user.RemoveExternalIDP(ctx, externalIDPRemoveToModel(request))
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) GetUserMfas(ctx context.Context, userID *management.UserID) (*management.MultiFactors, error) {
|
||||
mfas, err := s.user.UserMfas(ctx, userID.Id)
|
||||
if err != nil {
|
||||
|
@@ -2,8 +2,8 @@ package management
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
@@ -66,6 +66,62 @@ func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password {
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPSearchRequestToModel(request *management.ExternalIDPSearchRequest) *usr_model.ExternalIDPSearchRequest {
|
||||
return &usr_model.ExternalIDPSearchRequest{
|
||||
Limit: request.Limit,
|
||||
Offset: request.Offset,
|
||||
Queries: []*usr_model.ExternalIDPSearchQuery{{Key: usr_model.ExternalIDPSearchKeyUserID, Method: model.SearchMethodEquals, Value: request.UserId}},
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPRemoveToModel(idp *management.ExternalIDPRemoveRequest) *usr_model.ExternalIDP {
|
||||
return &usr_model.ExternalIDP{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: idp.UserId},
|
||||
IDPConfigID: idp.IdpConfigId,
|
||||
UserID: idp.ExternalUserId,
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPSearchResponseFromModel(response *usr_model.ExternalIDPSearchResponse) *management.ExternalIDPSearchResponse {
|
||||
viewTimestamp, err := ptypes.TimestampProto(response.Timestamp)
|
||||
logging.Log("GRPC-3h8is").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.ExternalIDPSearchResponse{
|
||||
Offset: response.Offset,
|
||||
Limit: response.Limit,
|
||||
TotalResult: response.TotalResult,
|
||||
ProcessedSequence: response.Sequence,
|
||||
ViewTimestamp: viewTimestamp,
|
||||
Result: externalIDPViewsFromModel(response.Result),
|
||||
}
|
||||
}
|
||||
|
||||
func externalIDPViewsFromModel(externalIDPs []*usr_model.ExternalIDPView) []*management.ExternalIDPView {
|
||||
converted := make([]*management.ExternalIDPView, len(externalIDPs))
|
||||
for i, externalIDP := range externalIDPs {
|
||||
converted[i] = externalIDPViewFromModel(externalIDP)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func externalIDPViewFromModel(externalIDP *usr_model.ExternalIDPView) *management.ExternalIDPView {
|
||||
creationDate, err := ptypes.TimestampProto(externalIDP.CreationDate)
|
||||
logging.Log("GRPC-Fdu8s").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
changeDate, err := ptypes.TimestampProto(externalIDP.ChangeDate)
|
||||
logging.Log("GRPC-Was7u").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &management.ExternalIDPView{
|
||||
UserId: externalIDP.UserID,
|
||||
IdpConfigId: externalIDP.IDPConfigID,
|
||||
ExternalUserId: externalIDP.ExternalUserID,
|
||||
ExternalUserDisplayName: externalIDP.UserDisplayName,
|
||||
IdpName: externalIDP.IDPName,
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
}
|
||||
}
|
||||
|
||||
func userSearchRequestsToModel(project *management.UserSearchRequest) *usr_model.UserSearchRequest {
|
||||
return &usr_model.UserSearchRequest{
|
||||
Offset: project.Offset,
|
||||
|
Reference in New Issue
Block a user