zitadel/internal/api/scim/resources/user_metadata.go
Lars e621224ab2
feat: create user scim v2 endpoint (#9132)
# Which Problems Are Solved
- Adds infrastructure code (basic implementation, error handling,
middlewares, ...) to implement the SCIM v2 interface
- Adds support for the user create SCIM v2 endpoint

# How the Problems Are Solved
- Adds support for the user create SCIM v2 endpoint under `POST
/scim/v2/{orgID}/Users`

# Additional Context

Part of #8140
2025-01-09 12:46:36 +01:00

151 lines
3.5 KiB
Go

package resources
import (
"context"
"encoding/json"
"time"
"github.com/zitadel/logging"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/scim/metadata"
"github.com/zitadel/zitadel/internal/api/scim/schemas"
"github.com/zitadel/zitadel/internal/api/scim/serrors"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/zerrors"
)
func (h *UsersHandler) mapMetadataToCommands(ctx context.Context, user *ScimUser) ([]*command.AddMetadataEntry, error) {
md := make([]*command.AddMetadataEntry, 0, len(metadata.ScimUserRelevantMetadataKeys))
for _, key := range metadata.ScimUserRelevantMetadataKeys {
value, err := getValueForMetadataKey(user, key)
if err != nil {
return nil, err
}
if len(value) > 0 {
md = append(md, &command.AddMetadataEntry{
Key: string(metadata.ScopeKey(ctx, key)),
Value: value,
})
}
}
return md, nil
}
func getValueForMetadataKey(user *ScimUser, key metadata.Key) ([]byte, error) {
value := getRawValueForMetadataKey(user, key)
if value == nil {
return nil, nil
}
switch key {
// json values
case metadata.KeyEntitlements:
fallthrough
case metadata.KeyIms:
fallthrough
case metadata.KeyPhotos:
fallthrough
case metadata.KeyAddresses:
fallthrough
case metadata.KeyRoles:
return json.Marshal(value)
// http url values
case metadata.KeyProfileUrl:
return []byte(value.(*schemas.HttpURL).String()), nil
// raw values
case metadata.KeyProvisioningDomain:
fallthrough
case metadata.KeyExternalId:
fallthrough
case metadata.KeyMiddleName:
fallthrough
case metadata.KeyHonorificSuffix:
fallthrough
case metadata.KeyHonorificPrefix:
fallthrough
case metadata.KeyTitle:
fallthrough
case metadata.KeyLocale:
fallthrough
case metadata.KeyTimezone:
valueStr := value.(string)
if valueStr == "" {
return nil, nil
}
return []byte(valueStr), validateValueForMetadataKey(valueStr, key)
}
logging.Panicf("Unknown metadata key %s", key)
return nil, nil
}
func validateValueForMetadataKey(v string, key metadata.Key) error {
//nolint:exhaustive
switch key {
case metadata.KeyLocale:
if _, err := language.Parse(v); err != nil {
return serrors.ThrowInvalidValue(zerrors.ThrowInvalidArgument(err, "SCIM-MD11", "Could not parse locale"))
}
return nil
case metadata.KeyTimezone:
if _, err := time.LoadLocation(v); err != nil {
return serrors.ThrowInvalidValue(zerrors.ThrowInvalidArgument(err, "SCIM-MD12", "Could not parse timezone"))
}
return nil
}
return nil
}
func getRawValueForMetadataKey(user *ScimUser, key metadata.Key) interface{} {
switch key {
case metadata.KeyIms:
return user.Ims
case metadata.KeyPhotos:
return user.Photos
case metadata.KeyAddresses:
return user.Addresses
case metadata.KeyEntitlements:
return user.Entitlements
case metadata.KeyRoles:
return user.Roles
case metadata.KeyMiddleName:
if user.Name == nil {
return ""
}
return user.Name.MiddleName
case metadata.KeyHonorificPrefix:
if user.Name == nil {
return ""
}
return user.Name.HonorificPrefix
case metadata.KeyHonorificSuffix:
if user.Name == nil {
return ""
}
return user.Name.HonorificSuffix
case metadata.KeyExternalId:
return user.ExternalID
case metadata.KeyProfileUrl:
return user.ProfileUrl
case metadata.KeyTitle:
return user.Title
case metadata.KeyLocale:
return user.Locale
case metadata.KeyTimezone:
return user.Timezone
case metadata.KeyProvisioningDomain:
break
}
logging.Panicf("Unknown or unsupported metadata key %s", key)
return nil
}