mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 15:17:33 +00:00
Merge branch 'dev-container-2' of https://github.com/zitadel/zitadel into dev-container-2
This commit is contained in:
@@ -432,3 +432,78 @@ func TestCreateUser_scopedExternalID(t *testing.T) {
|
|||||||
assert.Equal(tt, "701984", string(md.Metadata.Value))
|
assert.Equal(tt, "701984", string(md.Metadata.Value))
|
||||||
}, retryDuration, tick)
|
}, retryDuration, tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateUser_ignorePasswordOnCreate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ignorePassword string
|
||||||
|
scimErrorType string
|
||||||
|
scimErrorDetail string
|
||||||
|
wantUser *resources.ScimUser
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ignorePasswordOnCreate set to false",
|
||||||
|
ignorePassword: "false",
|
||||||
|
wantErr: true,
|
||||||
|
scimErrorType: "invalidValue",
|
||||||
|
scimErrorDetail: "Password is too short",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignorePasswordOnCreate set to an invalid value",
|
||||||
|
ignorePassword: "random",
|
||||||
|
wantErr: true,
|
||||||
|
scimErrorType: "invalidValue",
|
||||||
|
scimErrorDetail: "Invalid value for metadata key urn:zitadel:scim:ignorePasswordOnCreate: random",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignorePasswordOnCreate set to true",
|
||||||
|
ignorePassword: "true",
|
||||||
|
wantUser: &resources.ScimUser{
|
||||||
|
UserName: "acmeUser1",
|
||||||
|
Name: &resources.ScimUserName{
|
||||||
|
FamilyName: "Ross",
|
||||||
|
GivenName: "Bethany",
|
||||||
|
},
|
||||||
|
Emails: []*resources.ScimEmail{
|
||||||
|
{
|
||||||
|
Value: "user1@example.com",
|
||||||
|
Primary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// create a machine user
|
||||||
|
callingUserId, callingUserPat, err := Instance.CreateMachineUserPATWithMembership(CTX, "ORG_OWNER")
|
||||||
|
require.NoError(t, err)
|
||||||
|
ctx := integration.WithAuthorizationToken(CTX, callingUserPat)
|
||||||
|
|
||||||
|
// set urn:zitadel:scim:ignorePasswordOnCreate metadata for the machine user
|
||||||
|
setAndEnsureMetadata(t, callingUserId, "urn:zitadel:scim:ignorePasswordOnCreate", tt.ignorePassword)
|
||||||
|
|
||||||
|
// create a user with an invalid password
|
||||||
|
createdUser, err := Instance.Client.SCIM.Users.Create(ctx, Instance.DefaultOrg.Id, withUsername(invalidPasswordUserJson, "acmeUser1"))
|
||||||
|
require.Equal(t, tt.wantErr, err != nil)
|
||||||
|
if err != nil {
|
||||||
|
scimErr := scim.RequireScimError(t, http.StatusBadRequest, err)
|
||||||
|
assert.Equal(t, tt.scimErrorType, scimErr.Error.ScimType)
|
||||||
|
assert.Equal(t, tt.scimErrorDetail, scimErr.Error.Detail)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
// ensure the user is really stored and not just returned to the caller
|
||||||
|
fetchedUser, err := Instance.Client.SCIM.Users.Get(CTX, Instance.DefaultOrg.Id, createdUser.ID)
|
||||||
|
require.NoError(ttt, err)
|
||||||
|
assert.True(ttt, test.PartiallyDeepEqual(tt.wantUser, fetchedUser))
|
||||||
|
}, retryDuration, tick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,7 @@ var scimContextKey scimContextKeyType
|
|||||||
|
|
||||||
type ScimContextData struct {
|
type ScimContextData struct {
|
||||||
ProvisioningDomain string
|
ProvisioningDomain string
|
||||||
|
IgnorePasswordOnCreate bool
|
||||||
ExternalIDScopedMetadataKey ScopedKey
|
ExternalIDScopedMetadataKey ScopedKey
|
||||||
bulkIDMapping map[string]string
|
bulkIDMapping map[string]string
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,9 @@ type ScopedKey string
|
|||||||
const (
|
const (
|
||||||
externalIdProvisioningDomainPlaceholder = "{provisioningDomain}"
|
externalIdProvisioningDomainPlaceholder = "{provisioningDomain}"
|
||||||
|
|
||||||
KeyPrefix = "urn:zitadel:scim:"
|
KeyPrefix = "urn:zitadel:scim:"
|
||||||
KeyProvisioningDomain Key = KeyPrefix + "provisioningDomain"
|
KeyProvisioningDomain Key = KeyPrefix + "provisioningDomain"
|
||||||
|
KeyIgnorePasswordOnCreate Key = KeyPrefix + "ignorePasswordOnCreate"
|
||||||
|
|
||||||
KeyExternalId Key = KeyPrefix + "externalId"
|
KeyExternalId Key = KeyPrefix + "externalId"
|
||||||
keyScopedExternalIdTemplate = KeyPrefix + externalIdProvisioningDomainPlaceholder + ":externalId"
|
keyScopedExternalIdTemplate = KeyPrefix + externalIdProvisioningDomainPlaceholder + ":externalId"
|
||||||
|
@@ -3,10 +3,15 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
zhttp "github.com/zitadel/zitadel/internal/api/http/middleware"
|
zhttp "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||||
smetadata "github.com/zitadel/zitadel/internal/api/scim/metadata"
|
smetadata "github.com/zitadel/zitadel/internal/api/scim/metadata"
|
||||||
|
sresources "github.com/zitadel/zitadel/internal/api/scim/resources"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
@@ -29,22 +34,43 @@ func initScimContext(ctx context.Context, q *query.Queries) (context.Context, er
|
|||||||
ctx = smetadata.SetScimContextData(ctx, data)
|
ctx = smetadata.SetScimContextData(ctx, data)
|
||||||
|
|
||||||
userID := authz.GetCtxData(ctx).UserID
|
userID := authz.GetCtxData(ctx).UserID
|
||||||
metadata, err := q.GetUserMetadataByKey(ctx, false, userID, string(smetadata.KeyProvisioningDomain), false)
|
|
||||||
|
// get the provisioningDomain and ignorePasswordOnCreate metadata keys associated with the service user
|
||||||
|
metadataKeys := []smetadata.Key{
|
||||||
|
smetadata.KeyProvisioningDomain,
|
||||||
|
smetadata.KeyIgnorePasswordOnCreate,
|
||||||
|
}
|
||||||
|
queries := sresources.BuildMetadataQueries(ctx, metadataKeys)
|
||||||
|
|
||||||
|
metadataList, err := q.SearchUserMetadata(ctx, false, userID, queries, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if zerrors.IsNotFound(err) {
|
if zerrors.IsNotFound(err) {
|
||||||
return ctx, nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata == nil {
|
if metadataList == nil || len(metadataList.Metadata) == 0 {
|
||||||
return ctx, nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data.ProvisioningDomain = string(metadata.Value)
|
for _, metadata := range metadataList.Metadata {
|
||||||
if data.ProvisioningDomain != "" {
|
switch metadata.Key {
|
||||||
data.ExternalIDScopedMetadataKey = smetadata.ScopeExternalIdKey(data.ProvisioningDomain)
|
case string(smetadata.KeyProvisioningDomain):
|
||||||
|
data.ProvisioningDomain = string(metadata.Value)
|
||||||
|
if data.ProvisioningDomain != "" {
|
||||||
|
data.ExternalIDScopedMetadataKey = smetadata.ScopeExternalIdKey(data.ProvisioningDomain)
|
||||||
|
}
|
||||||
|
case string(smetadata.KeyIgnorePasswordOnCreate):
|
||||||
|
ignorePasswordOnCreate, parseErr := strconv.ParseBool(strings.TrimSpace(string(metadata.Value)))
|
||||||
|
if parseErr != nil {
|
||||||
|
return ctx,
|
||||||
|
zerrors.ThrowInvalidArgumentf(nil, "SMCM-yvw2rt", "Invalid value for metadata key %s: %s", smetadata.KeyIgnorePasswordOnCreate, metadata.Value)
|
||||||
|
}
|
||||||
|
data.IgnorePasswordOnCreate = ignorePasswordOnCreate
|
||||||
|
default:
|
||||||
|
logging.WithFields("user_metadata_key", metadata.Key).Warn("unexpected metadata key")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return smetadata.SetScimContextData(ctx, data), nil
|
return smetadata.SetScimContextData(ctx, data), nil
|
||||||
}
|
}
|
||||||
|
@@ -45,7 +45,12 @@ func (h *UsersHandler) mapToAddHuman(ctx context.Context, scimUser *ScimUser) (*
|
|||||||
}
|
}
|
||||||
human.Metadata = md
|
human.Metadata = md
|
||||||
|
|
||||||
if scimUser.Password != nil {
|
// Okta sends a random password during SCIM provisioning
|
||||||
|
// irrespective of whether the Sync Password option is enabled or disabled on Okta.
|
||||||
|
// This password does not comply with Zitadel's password complexity, and
|
||||||
|
// the following workaround ignores the random password as it does not add any value.
|
||||||
|
ignorePasswordOnCreate := metadata.GetScimContextData(ctx).IgnorePasswordOnCreate
|
||||||
|
if scimUser.Password != nil && !ignorePasswordOnCreate {
|
||||||
human.Password = scimUser.Password.String()
|
human.Password = scimUser.Password.String()
|
||||||
scimUser.Password = nil
|
scimUser.Password = nil
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (h *UsersHandler) queryMetadataForUsers(ctx context.Context, userIds []string) (map[string]map[metadata.ScopedKey][]byte, error) {
|
func (h *UsersHandler) queryMetadataForUsers(ctx context.Context, userIds []string) (map[string]map[metadata.ScopedKey][]byte, error) {
|
||||||
queries := h.buildMetadataQueries(ctx)
|
queries := BuildMetadataQueries(ctx, metadata.ScimUserRelevantMetadataKeys)
|
||||||
|
|
||||||
md, err := h.query.SearchUserMetadataForUsers(ctx, false, userIds, queries)
|
md, err := h.query.SearchUserMetadataForUsers(ctx, false, userIds, queries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -43,7 +43,7 @@ func (h *UsersHandler) queryMetadataForUsers(ctx context.Context, userIds []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *UsersHandler) queryMetadataForUser(ctx context.Context, id string) (map[metadata.ScopedKey][]byte, error) {
|
func (h *UsersHandler) queryMetadataForUser(ctx context.Context, id string) (map[metadata.ScopedKey][]byte, error) {
|
||||||
queries := h.buildMetadataQueries(ctx)
|
queries := BuildMetadataQueries(ctx, metadata.ScimUserRelevantMetadataKeys)
|
||||||
|
|
||||||
md, err := h.query.SearchUserMetadata(ctx, false, id, queries, nil)
|
md, err := h.query.SearchUserMetadata(ctx, false, id, queries, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,9 +53,9 @@ func (h *UsersHandler) queryMetadataForUser(ctx context.Context, id string) (map
|
|||||||
return metadata.MapListToScopedKeyMap(md.Metadata), nil
|
return metadata.MapListToScopedKeyMap(md.Metadata), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UsersHandler) buildMetadataQueries(ctx context.Context) *query.UserMetadataSearchQueries {
|
func BuildMetadataQueries(ctx context.Context, metadataKeys []metadata.Key) *query.UserMetadataSearchQueries {
|
||||||
keyQueries := make([]query.SearchQuery, len(metadata.ScimUserRelevantMetadataKeys))
|
keyQueries := make([]query.SearchQuery, len(metadataKeys))
|
||||||
for i, key := range metadata.ScimUserRelevantMetadataKeys {
|
for i, key := range metadataKeys {
|
||||||
keyQueries[i] = buildMetadataKeyQuery(ctx, key)
|
keyQueries[i] = buildMetadataKeyQuery(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user