mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:47:22 +00:00
feat: User metadata (#2025)
* feat: user meta data events * feat: user meta data set tests * feat: user meta data tests * feat: user meta data in protos * feat: user meta data command api * feat: user meta data query side * feat: proto correct order, fix handlers * feat: proto correct order * feat: fixes of pr comments * feat: fixes of pr comments * feat: value as byte array * feat: metadata feature * Update internal/auth/repository/eventsourcing/handler/meta_data.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/command/user_meta_data.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update proto/zitadel/metadata.proto Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update proto/zitadel/metadata.proto Co-authored-by: Silvan <silvan.reusser@gmail.com> * fix: rename metadata files and table * fix: rename meta data to metadat in protos * Update internal/domain/metadata.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * fix: rename vars * fix: rebiuld docs * Update internal/iam/repository/view/metadata_view.go Co-authored-by: Silvan <silvan.reusser@gmail.com> Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
parent
ae50f57c2c
commit
7451ed58f2
@ -67,8 +67,10 @@ protoc \
|
||||
-I=/proto/include \
|
||||
--grpc-gateway_out ${GOPATH}/src \
|
||||
--grpc-gateway_opt logtostderr=true \
|
||||
--grpc-gateway_opt allow_delete_body=true \
|
||||
--openapiv2_out ${OPENAPI_PATH} \
|
||||
--openapiv2_opt logtostderr=true \
|
||||
--openapiv2_opt allow_delete_body=true \
|
||||
--authoption_out=${GRPC_PATH}/auth \
|
||||
--validate_out=lang=go:${GOPATH}/src \
|
||||
${PROTO_PATH}/auth.proto
|
||||
@ -114,6 +116,10 @@ protoc \
|
||||
-I=/proto/include \
|
||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,message.md \
|
||||
${PROTO_PATH}/message.proto
|
||||
protoc \
|
||||
-I=/proto/include \
|
||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,metadata.md \
|
||||
${PROTO_PATH}/metadata.proto
|
||||
protoc \
|
||||
-I=/proto/include \
|
||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,object.md \
|
||||
@ -134,9 +140,14 @@ protoc \
|
||||
-I=/proto/include \
|
||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,project.md \
|
||||
${PROTO_PATH}/project.proto
|
||||
protoc \
|
||||
-I=/proto/include \
|
||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,text.md \
|
||||
${PROTO_PATH}/text.proto
|
||||
protoc \
|
||||
-I=/proto/include \
|
||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,user.md \
|
||||
${PROTO_PATH}/user.proto
|
||||
|
||||
|
||||
echo "done generating grpc"
|
@ -67,6 +67,78 @@ Returns the user sessions of the authorized user of the current useragent
|
||||
POST: /users/me/sessions/_search
|
||||
|
||||
|
||||
### SetMyMetadata
|
||||
|
||||
> **rpc** SetMyMetadata([SetMyMetadataRequest](#setmymetadatarequest))
|
||||
[SetMyMetadataResponse](#setmymetadataresponse)
|
||||
|
||||
Sets a user metadata by key to the authorized user
|
||||
|
||||
|
||||
|
||||
POST: /users/me/metadata/{key}
|
||||
|
||||
|
||||
### BulkSetMyMetadata
|
||||
|
||||
> **rpc** BulkSetMyMetadata([BulkSetMyMetadataRequest](#bulksetmymetadatarequest))
|
||||
[BulkSetMyMetadataResponse](#bulksetmymetadataresponse)
|
||||
|
||||
Set a list of user metadata to the authorized user
|
||||
|
||||
|
||||
|
||||
POST: /users/me/metadata/_bulk
|
||||
|
||||
|
||||
### ListMyMetadata
|
||||
|
||||
> **rpc** ListMyMetadata([ListMyMetadataRequest](#listmymetadatarequest))
|
||||
[ListMyMetadataResponse](#listmymetadataresponse)
|
||||
|
||||
Returns the user metadata of the authorized user
|
||||
|
||||
|
||||
|
||||
POST: /users/me/metadata/_search
|
||||
|
||||
|
||||
### GetMyMetadata
|
||||
|
||||
> **rpc** GetMyMetadata([GetMyMetadataRequest](#getmymetadatarequest))
|
||||
[GetMyMetadataResponse](#getmymetadataresponse)
|
||||
|
||||
Returns the user metadata by key of the authorized user
|
||||
|
||||
|
||||
|
||||
GET: /users/me/metadata/{key}
|
||||
|
||||
|
||||
### RemoveMyMetadata
|
||||
|
||||
> **rpc** RemoveMyMetadata([RemoveMyMetadataRequest](#removemymetadatarequest))
|
||||
[RemoveMyMetadataResponse](#removemymetadataresponse)
|
||||
|
||||
Removes a user metadata by key to the authorized user
|
||||
|
||||
|
||||
|
||||
DELETE: /users/me/metadata/{key}
|
||||
|
||||
|
||||
### BulkRemoveMyMetadata
|
||||
|
||||
> **rpc** BulkRemoveMyMetadata([BulkRemoveMyMetadataRequest](#bulkremovemymetadatarequest))
|
||||
[BulkRemoveMyMetadataResponse](#bulkremovemymetadataresponse)
|
||||
|
||||
Set a list of user metadata to the authorized user
|
||||
|
||||
|
||||
|
||||
DELETE: /users/me/metadata/_bulk
|
||||
|
||||
|
||||
### ListMyRefreshTokens
|
||||
|
||||
> **rpc** ListMyRefreshTokens([ListMyRefreshTokensRequest](#listmyrefreshtokensrequest))
|
||||
@ -628,6 +700,62 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### BulkRemoveMyMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| keys | repeated string | - | repeated.items.string.min_len: 1<br /> repeated.items.string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkRemoveMyMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkSetMyMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| metadata | repeated BulkSetMyMetadataRequest.Metadata | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkSetMyMetadataRequest.Metadata
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| value | bytes | - | bytes.min_len: 1<br /> bytes.max_len: 500000<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkSetMyMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetMyEmailRequest
|
||||
This is an empty request
|
||||
|
||||
@ -646,6 +774,28 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetMyMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetMyMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| metadata | zitadel.metadata.v1.Metadata | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetMyPasswordComplexityPolicyRequest
|
||||
This is an empty request
|
||||
|
||||
@ -811,6 +961,30 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### ListMyMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| query | zitadel.v1.ListQuery | - | |
|
||||
| queries | repeated zitadel.metadata.v1.MetadataQuery | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListMyMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ListDetails | - | |
|
||||
| result | repeated zitadel.metadata.v1.Metadata | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListMyPasswordlessRequest
|
||||
This is an empty request
|
||||
|
||||
@ -1063,6 +1237,28 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### RemoveMyMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### RemoveMyMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### RemoveMyPasswordlessRequest
|
||||
|
||||
|
||||
@ -1209,6 +1405,29 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### SetMyMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| value | bytes | - | bytes.min_len: 1<br /> bytes.max_len: 500000<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### SetMyMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### SetMyPhoneRequest
|
||||
|
||||
|
||||
|
@ -236,6 +236,78 @@ Changes the username
|
||||
GET: /users/{user_id}/username
|
||||
|
||||
|
||||
### SetUserMetadata
|
||||
|
||||
> **rpc** SetUserMetadata([SetUserMetadataRequest](#setusermetadatarequest))
|
||||
[SetUserMetadataResponse](#setusermetadataresponse)
|
||||
|
||||
Sets a user metadata by key
|
||||
|
||||
|
||||
|
||||
POST: /users/{id}/metadata/{key}
|
||||
|
||||
|
||||
### BulkSetUserMetadata
|
||||
|
||||
> **rpc** BulkSetUserMetadata([BulkSetUserMetadataRequest](#bulksetusermetadatarequest))
|
||||
[BulkSetUserMetadataResponse](#bulksetusermetadataresponse)
|
||||
|
||||
Set a list of user metadata
|
||||
|
||||
|
||||
|
||||
POST: /users/{id}/metadata/_bulk
|
||||
|
||||
|
||||
### ListUserMetadata
|
||||
|
||||
> **rpc** ListUserMetadata([ListUserMetadataRequest](#listusermetadatarequest))
|
||||
[ListUserMetadataResponse](#listusermetadataresponse)
|
||||
|
||||
Returns the user metadata
|
||||
|
||||
|
||||
|
||||
POST: /users/{id}/metadata/_search
|
||||
|
||||
|
||||
### GetUserMetadata
|
||||
|
||||
> **rpc** GetUserMetadata([GetUserMetadataRequest](#getusermetadatarequest))
|
||||
[GetUserMetadataResponse](#getusermetadataresponse)
|
||||
|
||||
Returns the user metadata by key
|
||||
|
||||
|
||||
|
||||
GET: /users/{id}/metadata/{key}
|
||||
|
||||
|
||||
### RemoveUserMetadata
|
||||
|
||||
> **rpc** RemoveUserMetadata([RemoveUserMetadataRequest](#removeusermetadatarequest))
|
||||
[RemoveUserMetadataResponse](#removeusermetadataresponse)
|
||||
|
||||
Removes a user metadata by key
|
||||
|
||||
|
||||
|
||||
DELETE: /users/{id}/metadata/{key}
|
||||
|
||||
|
||||
### BulkRemoveUserMetadata
|
||||
|
||||
> **rpc** BulkRemoveUserMetadata([BulkRemoveUserMetadataRequest](#bulkremoveusermetadatarequest))
|
||||
[BulkRemoveUserMetadataResponse](#bulkremoveusermetadataresponse)
|
||||
|
||||
Set a list of user metadata
|
||||
|
||||
|
||||
|
||||
DELETE: /users/{id}/metadata/_bulk
|
||||
|
||||
|
||||
### GetHumanProfile
|
||||
|
||||
> **rpc** GetHumanProfile([GetHumanProfileRequest](#gethumanprofilerequest))
|
||||
@ -3347,6 +3419,64 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### BulkRemoveUserMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| keys | repeated string | - | repeated.items.string.min_len: 1<br /> repeated.items.string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkRemoveUserMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkSetUserMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| metadata | repeated BulkSetUserMetadataRequest.Metadata | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkSetUserMetadataRequest.Metadata
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| value | bytes | - | bytes.min_len: 1<br /> bytes.max_len: 500000<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### BulkSetUserMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### DeactivateAppRequest
|
||||
|
||||
|
||||
@ -4480,6 +4610,29 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetUserMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetUserMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| metadata | zitadel.metadata.v1.Metadata | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### HealthzRequest
|
||||
This is an empty request
|
||||
|
||||
@ -5264,6 +5417,31 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### ListUserMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| query | zitadel.v1.ListQuery | - | |
|
||||
| queries | repeated zitadel.metadata.v1.MetadataQuery | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListUserMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ListDetails | - | |
|
||||
| result | repeated zitadel.metadata.v1.Metadata | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ListUsersRequest
|
||||
|
||||
|
||||
@ -6068,6 +6246,29 @@ This is an empty response
|
||||
|
||||
|
||||
|
||||
### RemoveUserMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### RemoveUserMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### RemoveUserRequest
|
||||
|
||||
|
||||
@ -6756,6 +6957,31 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### SetUserMetadataRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| key | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| value | bytes | - | bytes.min_len: 1<br /> bytes.max_len: 500000<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### SetUserMetadataResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| id | string | - | |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### UnlockUserRequest
|
||||
|
||||
|
||||
|
49
docs/docs/apis/proto/metadata.md
Normal file
49
docs/docs/apis/proto/metadata.md
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: zitadel/metadata.proto
|
||||
---
|
||||
> This document reflects the state from API 1.0 (available from 20.04.2021)
|
||||
|
||||
|
||||
|
||||
|
||||
## Messages
|
||||
|
||||
|
||||
### Metadata
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| key | string | - | |
|
||||
| value | bytes | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### MetadataKeyQuery
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| key | string | - | string.max_len: 200<br /> |
|
||||
| method | zitadel.v1.TextQueryMethod | - | enum.defined_only: true<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### MetadataQuery
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.key_query | MetadataKeyQuery | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
601
docs/docs/apis/proto/text.md
Normal file
601
docs/docs/apis/proto/text.md
Normal file
@ -0,0 +1,601 @@
|
||||
---
|
||||
title: zitadel/text.proto
|
||||
---
|
||||
> This document reflects the state from API 1.0 (available from 20.04.2021)
|
||||
|
||||
|
||||
|
||||
|
||||
## Messages
|
||||
|
||||
|
||||
### EmailVerificationDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
| login_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### EmailVerificationScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| code_label | string | - | string.max_len: 200<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| resend_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### ExternalUserNotFoundScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| link_button_text | string | - | string.max_len: 100<br /> |
|
||||
| auto_register_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### FooterText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| tos | string | - | string.max_len: 200<br /> |
|
||||
| privacy_policy | string | - | string.max_len: 200<br /> |
|
||||
| help | string | - | string.max_len: 200<br /> |
|
||||
| help_link | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitMFADoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitMFAOTPScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| description_otp | string | - | string.max_len: 500<br /> |
|
||||
| secret_label | string | - | string.max_len: 200<br /> |
|
||||
| code_label | string | - | string.max_len: 200<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitMFAPromptScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| otp_option | string | - | string.max_len: 200<br /> |
|
||||
| u2f_option | string | - | string.max_len: 200<br /> |
|
||||
| skip_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitMFAU2FScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| token_name_label | string | - | string.max_len: 200<br /> |
|
||||
| not_supported | string | - | string.max_len: 500<br /> |
|
||||
| register_token_button_text | string | - | string.max_len: 100<br /> |
|
||||
| error_retry | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitPasswordDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitPasswordScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| code_label | string | - | string.max_len: 200<br /> |
|
||||
| new_password_label | string | - | string.max_len: 200<br /> |
|
||||
| new_password_confirm_label | string | - | string.max_len: 200<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| resend_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitializeUserDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### InitializeUserScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| code_label | string | - | string.max_len: 200<br /> |
|
||||
| new_password_label | string | - | string.max_len: 200<br /> |
|
||||
| new_password_confirm_label | string | - | string.max_len: 200<br /> |
|
||||
| resend_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### LinkingUserDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### LoginCustomText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| select_account_text | SelectAccountScreenText | - | |
|
||||
| login_text | LoginScreenText | - | |
|
||||
| password_text | PasswordScreenText | - | |
|
||||
| username_change_text | UsernameChangeScreenText | - | |
|
||||
| username_change_done_text | UsernameChangeDoneScreenText | - | |
|
||||
| init_password_text | InitPasswordScreenText | - | |
|
||||
| init_password_done_text | InitPasswordDoneScreenText | - | |
|
||||
| email_verification_text | EmailVerificationScreenText | - | |
|
||||
| email_verification_done_text | EmailVerificationDoneScreenText | - | |
|
||||
| initialize_user_text | InitializeUserScreenText | - | |
|
||||
| initialize_done_text | InitializeUserDoneScreenText | - | |
|
||||
| init_mfa_prompt_text | InitMFAPromptScreenText | - | |
|
||||
| init_mfa_otp_text | InitMFAOTPScreenText | - | |
|
||||
| init_mfa_u2f_text | InitMFAU2FScreenText | - | |
|
||||
| init_mfa_done_text | InitMFADoneScreenText | - | |
|
||||
| mfa_providers_text | MFAProvidersText | - | |
|
||||
| verify_mfa_otp_text | VerifyMFAOTPScreenText | - | |
|
||||
| verify_mfa_u2f_text | VerifyMFAU2FScreenText | - | |
|
||||
| passwordless_text | PasswordlessScreenText | - | |
|
||||
| password_change_text | PasswordChangeScreenText | - | |
|
||||
| password_change_done_text | PasswordChangeDoneScreenText | - | |
|
||||
| password_reset_done_text | PasswordResetDoneScreenText | - | |
|
||||
| registration_option_text | RegistrationOptionScreenText | - | |
|
||||
| registration_user_text | RegistrationUserScreenText | - | |
|
||||
| registration_org_text | RegistrationOrgScreenText | - | |
|
||||
| linking_user_done_text | LinkingUserDoneScreenText | - | |
|
||||
| external_user_not_found_text | ExternalUserNotFoundScreenText | - | |
|
||||
| success_login_text | SuccessLoginScreenText | - | |
|
||||
| logout_text | LogoutDoneScreenText | - | |
|
||||
| footer_text | FooterText | - | |
|
||||
| passwordless_prompt_text | PasswordlessPromptScreenText | - | |
|
||||
| passwordless_registration_text | PasswordlessRegistrationScreenText | - | |
|
||||
| passwordless_registration_done_text | PasswordlessRegistrationDoneScreenText | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### LoginScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| title_linking_process | string | - | string.max_len: 200<br /> |
|
||||
| description_linking_process | string | - | string.max_len: 500<br /> |
|
||||
| user_must_be_member_of_org | string | - | string.max_len: 500<br /> |
|
||||
| login_name_label | string | - | string.max_len: 200<br /> |
|
||||
| register_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| external_user_description | string | - | string.max_len: 500<br /> |
|
||||
| user_name_placeholder | string | - | string.max_len: 200<br /> |
|
||||
| login_name_placeholder | string | - | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### LogoutDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| login_button_text | string | - | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### MFAProvidersText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| choose_other | string | - | string.max_len: 500<br /> |
|
||||
| otp | string | - | string.max_len: 200<br /> |
|
||||
| u2f | string | - | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### MessageCustomText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| title | string | - | |
|
||||
| pre_header | string | - | |
|
||||
| subject | string | - | |
|
||||
| greeting | string | - | |
|
||||
| text | string | - | |
|
||||
| button_text | string | - | |
|
||||
| footer_text | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordChangeDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordChangeScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| old_password_label | string | - | string.max_len: 200<br /> |
|
||||
| new_password_label | string | - | string.max_len: 200<br /> |
|
||||
| new_password_confirm_label | string | - | string.max_len: 200<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordResetDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| password_label | string | - | string.max_len: 200<br /> |
|
||||
| reset_link_text | string | - | string.max_len: 100<br /> |
|
||||
| back_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| min_length | string | - | string.max_len: 100<br /> |
|
||||
| has_uppercase | string | - | string.max_len: 100<br /> |
|
||||
| has_lowercase | string | - | string.max_len: 100<br /> |
|
||||
| has_number | string | - | string.max_len: 100<br /> |
|
||||
| has_symbol | string | - | string.max_len: 100<br /> |
|
||||
| confirmation | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordlessPromptScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| description_init | string | - | string.max_len: 500<br /> |
|
||||
| passwordless_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
| skip_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordlessRegistrationDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordlessRegistrationScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| token_name_label | string | - | string.max_len: 200<br /> |
|
||||
| not_supported | string | - | string.max_len: 500<br /> |
|
||||
| register_token_button_text | string | - | string.max_len: 100<br /> |
|
||||
| error_retry | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### PasswordlessScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| login_with_pw_button_text | string | - | string.max_len: 100<br /> |
|
||||
| validate_token_button_text | string | - | string.max_len: 200<br /> |
|
||||
| not_supported | string | - | string.max_len: 500<br /> |
|
||||
| error_retry | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### RegistrationOptionScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| user_name_button_text | string | - | string.max_len: 200<br /> |
|
||||
| external_login_description | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### RegistrationOrgScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| orgname_label | string | - | string.max_len: 200<br /> |
|
||||
| firstname_label | string | - | string.max_len: 200<br /> |
|
||||
| lastname_label | string | - | string.max_len: 200<br /> |
|
||||
| username_label | string | - | string.max_len: 200<br /> |
|
||||
| email_label | string | - | string.max_len: 200<br /> |
|
||||
| password_label | string | - | string.max_len: 200<br /> |
|
||||
| password_confirm_label | string | - | string.max_len: 200<br /> |
|
||||
| tos_and_privacy_label | string | - | string.max_len: 200<br /> |
|
||||
| tos_confirm | string | - | string.max_len: 200<br /> |
|
||||
| tos_link_text | string | - | string.max_len: 200<br /> |
|
||||
| privacy_link_text | string | - | string.max_len: 200<br /> |
|
||||
| save_button_text | string | - | string.max_len: 200<br /> |
|
||||
| tos_confirm_and | string | - | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### RegistrationUserScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| description_org_register | string | - | string.max_len: 500<br /> |
|
||||
| firstname_label | string | - | string.max_len: 200<br /> |
|
||||
| lastname_label | string | - | string.max_len: 200<br /> |
|
||||
| email_label | string | - | string.max_len: 200<br /> |
|
||||
| username_label | string | - | string.max_len: 200<br /> |
|
||||
| language_label | string | - | string.max_len: 200<br /> |
|
||||
| gender_label | string | - | string.max_len: 200<br /> |
|
||||
| password_label | string | - | string.max_len: 200<br /> |
|
||||
| password_confirm_label | string | - | string.max_len: 200<br /> |
|
||||
| tos_and_privacy_label | string | - | string.max_len: 200<br /> |
|
||||
| tos_confirm | string | - | string.max_len: 200<br /> |
|
||||
| tos_link_text | string | - | string.max_len: 200<br /> |
|
||||
| privacy_link_text | string | - | string.max_len: 200<br /> |
|
||||
| next_button_text | string | - | string.max_len: 200<br /> |
|
||||
| back_button_text | string | - | string.max_len: 200<br /> |
|
||||
| tos_confirm_and | string | - | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### SelectAccountScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| title_linking_process | string | - | string.max_len: 200<br /> |
|
||||
| description_linking_process | string | - | string.max_len: 500<br /> |
|
||||
| other_user | string | - | string.max_len: 500<br /> |
|
||||
| session_state_active | string | - | string.max_len: 100<br /> |
|
||||
| session_state_inactive | string | - | string.max_len: 100<br /> |
|
||||
| user_must_be_member_of_org | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### SuccessLoginScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| auto_redirect_description | string | Text to describe that auto redirect should happen after successful login | string.max_len: 500<br /> |
|
||||
| redirected_description | string | Text to describe that the window can be closed after redirect | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### UsernameChangeDoneScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### UsernameChangeScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| username_label | string | - | string.max_len: 200<br /> |
|
||||
| cancel_button_text | string | - | string.max_len: 100<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### VerifyMFAOTPScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| code_label | string | - | string.max_len: 200<br /> |
|
||||
| next_button_text | string | - | string.max_len: 100<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### VerifyMFAU2FScreenText
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| title | string | - | string.max_len: 200<br /> |
|
||||
| description | string | - | string.max_len: 500<br /> |
|
||||
| validate_token_text | string | - | string.max_len: 500<br /> |
|
||||
| not_supported | string | - | string.max_len: 500<br /> |
|
||||
| error_retry | string | - | string.max_len: 500<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
29
internal/api/grpc/auth/metadata_converter.go
Normal file
29
internal/api/grpc/auth/metadata_converter.go
Normal file
@ -0,0 +1,29 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/pkg/grpc/auth"
|
||||
)
|
||||
|
||||
func BulkSetMetadataToDomain(req *auth.BulkSetMyMetadataRequest) []*domain.Metadata {
|
||||
metadata := make([]*domain.Metadata, len(req.Metadata))
|
||||
for i, data := range req.Metadata {
|
||||
metadata[i] = &domain.Metadata{
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
func ListUserMetadataToDomain(req *auth.ListMyMetadataRequest) *domain.MetadataSearchRequest {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &domain.MetadataSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
Queries: metadata.MetadataQueriesToModel(req.Queries),
|
||||
}
|
||||
}
|
@ -6,9 +6,12 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/grpc/change"
|
||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/api/grpc/org"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
|
||||
@ -37,6 +40,79 @@ func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserC
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListMyMetadata(ctx context.Context, req *auth_pb.ListMyMetadataRequest) (*auth_pb.ListMyMetadataResponse, error) {
|
||||
res, err := s.repo.SearchMyMetadata(ctx, ListUserMetadataToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.ListMyMetadataResponse{
|
||||
Result: metadata.MetadataListToPb(res.Result),
|
||||
Details: obj_grpc.ToListDetails(
|
||||
res.TotalResult,
|
||||
res.Sequence,
|
||||
res.Timestamp,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetMyMetadata(ctx context.Context, req *auth_pb.GetMyMetadataRequest) (*auth_pb.GetMyMetadataResponse, error) {
|
||||
data, err := s.repo.GetMyMetadataByKey(ctx, req.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.GetMyMetadataResponse{
|
||||
Metadata: metadata.DomainMetadataToPb(data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetMyMetadata(ctx context.Context, req *auth_pb.SetMyMetadataRequest) (*auth_pb.SetMyMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.SetUserMetadata(ctx, &domain.Metadata{Key: req.Key, Value: req.Value}, ctxData.UserID, ctxData.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.SetMyMetadataResponse{
|
||||
Details: obj_grpc.AddToDetailsPb(
|
||||
result.Sequence,
|
||||
result.ChangeDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) BulkSetMyMetadata(ctx context.Context, req *auth_pb.BulkSetMyMetadataRequest) (*auth_pb.BulkSetMyMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.BulkSetUserMetadata(ctx, ctxData.UserID, ctxData.ResourceOwner, BulkSetMetadataToDomain(req)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.BulkSetMyMetadataResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMyMetadata(ctx context.Context, req *auth_pb.RemoveMyMetadataRequest) (*auth_pb.RemoveMyMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.RemoveUserMetadata(ctx, req.Key, ctxData.UserID, ctxData.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.RemoveMyMetadataResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) BulkRemoveMyMetadata(ctx context.Context, req *auth_pb.BulkRemoveMyMetadataRequest) (*auth_pb.BulkRemoveMyMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.BulkRemoveUserMetadata(ctx, ctxData.UserID, ctxData.ResourceOwner, req.Keys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.BulkRemoveMyMetadataResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListMyUserSessions(ctx context.Context, req *auth_pb.ListMyUserSessionsRequest) (*auth_pb.ListMyUserSessionsResponse, error) {
|
||||
userSessions, err := s.repo.GetMyUserSessions(ctx)
|
||||
if err != nil {
|
||||
|
@ -9,10 +9,12 @@ import (
|
||||
"github.com/caos/zitadel/internal/api/grpc/authn"
|
||||
change_grpc "github.com/caos/zitadel/internal/api/grpc/change"
|
||||
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
|
||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/api/grpc/user"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
)
|
||||
@ -78,6 +80,79 @@ func (s *Server) IsUserUnique(ctx context.Context, req *mgmt_pb.IsUserUniqueRequ
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListUserMetadata(ctx context.Context, req *mgmt_pb.ListUserMetadataRequest) (*mgmt_pb.ListUserMetadataResponse, error) {
|
||||
res, err := s.user.SearchMetadata(ctx, req.Id, authz.GetCtxData(ctx).OrgID, ListUserMetadataToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ListUserMetadataResponse{
|
||||
Result: metadata.MetadataListToPb(res.Result),
|
||||
Details: obj_grpc.ToListDetails(
|
||||
res.TotalResult,
|
||||
res.Sequence,
|
||||
res.Timestamp,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetUserMetadata(ctx context.Context, req *mgmt_pb.GetUserMetadataRequest) (*mgmt_pb.GetUserMetadataResponse, error) {
|
||||
data, err := s.user.GetMetadataByKey(ctx, req.Id, authz.GetCtxData(ctx).OrgID, req.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetUserMetadataResponse{
|
||||
Metadata: metadata.DomainMetadataToPb(data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetUserMetadata(ctx context.Context, req *mgmt_pb.SetUserMetadataRequest) (*mgmt_pb.SetUserMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.SetUserMetadata(ctx, &domain.Metadata{Key: req.Key, Value: req.Value}, req.Id, ctxData.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.SetUserMetadataResponse{
|
||||
Details: obj_grpc.AddToDetailsPb(
|
||||
result.Sequence,
|
||||
result.ChangeDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) BulkSetUserMetadata(ctx context.Context, req *mgmt_pb.BulkSetUserMetadataRequest) (*mgmt_pb.BulkSetUserMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.BulkSetUserMetadata(ctx, req.Id, ctxData.ResourceOwner, BulkSetMetadataToDomain(req)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.BulkSetUserMetadataResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveUserMetadata(ctx context.Context, req *mgmt_pb.RemoveUserMetadataRequest) (*mgmt_pb.RemoveUserMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.RemoveUserMetadata(ctx, req.Key, req.Id, ctxData.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.RemoveUserMetadataResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRemoveUserMetadataRequest) (*mgmt_pb.BulkRemoveUserMetadataResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
result, err := s.command.BulkRemoveUserMetadata(ctx, req.Id, ctxData.ResourceOwner, req.Keys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.BulkRemoveUserMetadataResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(result),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) {
|
||||
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req))
|
||||
if err != nil {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/grpc/authn"
|
||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
@ -38,6 +39,27 @@ func ListUsersRequestToModel(ctx context.Context, req *mgmt_pb.ListUsersRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func BulkSetMetadataToDomain(req *mgmt_pb.BulkSetUserMetadataRequest) []*domain.Metadata {
|
||||
metadata := make([]*domain.Metadata, len(req.Metadata))
|
||||
for i, data := range req.Metadata {
|
||||
metadata[i] = &domain.Metadata{
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
func ListUserMetadataToDomain(req *mgmt_pb.ListUserMetadataRequest) *domain.MetadataSearchRequest {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &domain.MetadataSearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
Queries: metadata.MetadataQueriesToModel(req.Queries),
|
||||
}
|
||||
}
|
||||
|
||||
func AddHumanUserRequestToDomain(req *mgmt_pb.AddHumanUserRequest) *domain.Human {
|
||||
h := &domain.Human{
|
||||
Username: req.UserName,
|
||||
|
53
internal/api/grpc/metadata/metadata.go
Normal file
53
internal/api/grpc/metadata/metadata.go
Normal file
@ -0,0 +1,53 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
meta_pb "github.com/caos/zitadel/pkg/grpc/metadata"
|
||||
)
|
||||
|
||||
func MetadataListToPb(dataList []*domain.Metadata) []*meta_pb.Metadata {
|
||||
mds := make([]*meta_pb.Metadata, len(dataList))
|
||||
for i, data := range dataList {
|
||||
mds[i] = DomainMetadataToPb(data)
|
||||
}
|
||||
return mds
|
||||
}
|
||||
|
||||
func DomainMetadataToPb(data *domain.Metadata) *meta_pb.Metadata {
|
||||
return &meta_pb.Metadata{
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
Details: object.ToViewDetailsPb(
|
||||
data.Sequence,
|
||||
data.CreationDate,
|
||||
data.ChangeDate,
|
||||
data.ResourceOwner,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func MetadataQueriesToModel(queries []*meta_pb.MetadataQuery) []*domain.MetadataSearchQuery {
|
||||
q := make([]*domain.MetadataSearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i] = MetadataQueryToModel(query)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func MetadataQueryToModel(query *meta_pb.MetadataQuery) *domain.MetadataSearchQuery {
|
||||
switch q := query.Query.(type) {
|
||||
case *meta_pb.MetadataQuery_KeyQuery:
|
||||
return MetadataKeyQueryToModel(q.KeyQuery)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func MetadataKeyQueryToModel(q *meta_pb.MetadataKeyQuery) *domain.MetadataSearchQuery {
|
||||
return &domain.MetadataSearchQuery{
|
||||
Key: domain.MetadataSearchKeyKey,
|
||||
Method: object.TextMethodToModel(q.Method),
|
||||
Value: q.Key,
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
key_view_model "github.com/caos/zitadel/internal/key/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
@ -296,3 +297,39 @@ func (r *UserRepo) getUserEvents(ctx context.Context, userID string, sequence ui
|
||||
}
|
||||
return r.Eventstore.FilterEvents(ctx, query)
|
||||
}
|
||||
|
||||
func (repo *UserRepo) GetMyMetadataByKey(ctx context.Context, key string) (*domain.Metadata, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
data, err := repo.View.MetadataByKeyAndResourceOwner(ctxData.UserID, ctxData.ResourceOwner, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_model.MetadataViewToDomain(data), nil
|
||||
}
|
||||
|
||||
func (repo *UserRepo) SearchMyMetadata(ctx context.Context, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
err := req.EnsureLimit(repo.SearchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence, sequenceErr := repo.View.GetLatestUserSequence()
|
||||
logging.Log("EVENT-N9fsd").OnError(sequenceErr).Warn("could not read latest user sequence")
|
||||
req.AppendAggregateIDQuery(ctxData.UserID)
|
||||
req.AppendResourceOwnerQuery(ctxData.ResourceOwner)
|
||||
metadata, count, err := repo.View.SearchMetadata(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &domain.MetadataSearchResponse{
|
||||
Offset: req.Offset,
|
||||
Limit: req.Limit,
|
||||
TotalResult: count,
|
||||
Result: iam_model.MetadataViewsToDomain(metadata),
|
||||
}
|
||||
if sequenceErr == nil {
|
||||
result.Sequence = sequence.CurrentSequence
|
||||
result.Timestamp = sequence.LastSuccessfulSpoolerRun
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
newRefreshToken(handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}),
|
||||
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}),
|
||||
newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),
|
||||
}
|
||||
}
|
||||
|
||||
|
124
internal/auth/repository/eventsourcing/handler/metadata.go
Normal file
124
internal/auth/repository/eventsourcing/handler/metadata.go
Normal file
@ -0,0 +1,124 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newMetadata(handler handler) *Metadata {
|
||||
h := &Metadata{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (m *Metadata) subscribe() {
|
||||
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range m.subscription.Events {
|
||||
query.ReduceEvent(m, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
const (
|
||||
metadataTable = "auth.metadata"
|
||||
)
|
||||
|
||||
func (m *Metadata) ViewModel() string {
|
||||
return metadataTable
|
||||
}
|
||||
|
||||
func (m *Metadata) Subscription() *v1.Subscription {
|
||||
return m.subscription
|
||||
}
|
||||
|
||||
func (_ *Metadata) AggregateTypes() []es_models.AggregateType {
|
||||
return []es_models.AggregateType{usr_model.UserAggregate}
|
||||
}
|
||||
|
||||
func (p *Metadata) CurrentSequence() (uint64, error) {
|
||||
sequence, err := p.view.GetLatestMetadataSequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (m *Metadata) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := m.view.GetLatestMetadataSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(m.AggregateTypes()...).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (m *Metadata) Reduce(event *es_models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case usr_model.UserAggregate:
|
||||
err = m.processMetadata(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Metadata) processMetadata(event *es_models.Event) (err error) {
|
||||
metadata := new(iam_model.MetadataView)
|
||||
switch event.Type {
|
||||
case usr_model.UserMetadataSet:
|
||||
err = metadata.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata, err = m.view.MetadataByKey(event.AggregateID, metadata.Key)
|
||||
if err != nil && !caos_errs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if caos_errs.IsNotFound(err) {
|
||||
err = nil
|
||||
metadata = new(iam_model.MetadataView)
|
||||
metadata.CreationDate = event.CreationDate
|
||||
}
|
||||
err = metadata.AppendEvent(event)
|
||||
case usr_model.UserMetadataRemoved:
|
||||
data := new(iam_model.MetadataView)
|
||||
err = data.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
|
||||
case usr_model.UserRemoved:
|
||||
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
||||
default:
|
||||
return m.view.ProcessedMetadataSequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.view.PutMetadata(metadata, event)
|
||||
}
|
||||
|
||||
func (m *Metadata) OnError(event *es_models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-miJJs", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler")
|
||||
return spooler.HandleError(event, err, m.view.GetLatestMetadataFailedEvent, m.view.ProcessedMetadataFailedEvent, m.view.ProcessedMetadataSequence, m.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (m *Metadata) OnSuccess() error {
|
||||
return spooler.HandleSuccess(m.view.UpdateMetadataSpoolerRunTimestamp)
|
||||
}
|
73
internal/auth/repository/eventsourcing/view/metadata.go
Normal file
73
internal/auth/repository/eventsourcing/view/metadata.go
Normal file
@ -0,0 +1,73 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataTable = "auth.metadata"
|
||||
)
|
||||
|
||||
func (v *View) MetadataByKey(aggregateID, key string) (*model.MetadataView, error) {
|
||||
return view.MetadataByKey(v.Db, metadataTable, aggregateID, key)
|
||||
}
|
||||
|
||||
func (v *View) MetadataListByAggregateID(aggregateID string) ([]*model.MetadataView, error) {
|
||||
return view.GetMetadataList(v.Db, metadataTable, aggregateID)
|
||||
}
|
||||
|
||||
func (v *View) MetadataByKeyAndResourceOwner(aggregateID, resourceOwner, key string) (*model.MetadataView, error) {
|
||||
return view.MetadataByKeyAndResourceOwner(v.Db, metadataTable, aggregateID, resourceOwner, key)
|
||||
}
|
||||
|
||||
func (v *View) SearchMetadata(request *domain.MetadataSearchRequest) ([]*model.MetadataView, uint64, error) {
|
||||
return view.SearchMetadata(v.Db, metadataTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutMetadata(template *model.MetadataView, event *models.Event) error {
|
||||
err := view.PutMetadata(v.Db, metadataTable, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedMetadataSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteMetadata(aggregateID, key string, event *models.Event) error {
|
||||
err := view.DeleteMetadata(v.Db, metadataTable, aggregateID, key)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedMetadataSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteMetadataByAggregateID(aggregateID string, event *models.Event) error {
|
||||
err := view.DeleteMetadataByAggregateID(v.Db, metadataTable, aggregateID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedMetadataSequence(event)
|
||||
}
|
||||
func (v *View) GetLatestMetadataSequence() (*global_view.CurrentSequence, error) {
|
||||
return v.latestSequence(metadataTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedMetadataSequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(metadataTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdateMetadataSpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(metadataTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestMetadataFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(metadataTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedMetadataFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
@ -42,4 +43,7 @@ type myUserRepo interface {
|
||||
MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*model.UserChanges, error)
|
||||
|
||||
SearchMyUserMemberships(ctx context.Context, request *model.UserMembershipSearchRequest) (*model.UserMembershipSearchResponse, error)
|
||||
|
||||
GetMyMetadataByKey(ctx context.Context, key string) (*domain.Metadata, error)
|
||||
SearchMyMetadata(ctx context.Context, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error)
|
||||
}
|
||||
|
49
internal/command/metadata_model.go
Normal file
49
internal/command/metadata_model.go
Normal file
@ -0,0 +1,49 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/metadata"
|
||||
)
|
||||
|
||||
type MetadataWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Key string
|
||||
Value []byte
|
||||
State domain.MetadataState
|
||||
}
|
||||
|
||||
func (wm *MetadataWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *metadata.SetEvent:
|
||||
if wm.Key != e.Key {
|
||||
continue
|
||||
}
|
||||
wm.Value = e.Value
|
||||
wm.State = domain.MetadataStateActive
|
||||
case *metadata.RemovedEvent:
|
||||
wm.State = domain.MetadataStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
type MetadataListWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
metadataList map[string][]byte
|
||||
}
|
||||
|
||||
func (wm *MetadataListWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *metadata.SetEvent:
|
||||
wm.metadataList[e.Key] = e.Value
|
||||
case *metadata.RemovedEvent:
|
||||
delete(wm.metadataList, e.Key)
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
@ -153,3 +153,12 @@ func writeModelToPasswordlessInitCode(initCodeModel *HumanPasswordlessInitCodeWr
|
||||
State: initCodeModel.State,
|
||||
}
|
||||
}
|
||||
|
||||
func writeModelToUserMetadata(wm *UserMetadataWriteModel) *domain.Metadata {
|
||||
return &domain.Metadata{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
Key: wm.Key,
|
||||
Value: wm.Value,
|
||||
State: wm.State,
|
||||
}
|
||||
}
|
||||
|
177
internal/command/user_metadata.go
Normal file
177
internal/command/user_metadata.go
Normal file
@ -0,0 +1,177 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func (c *Commands) SetUserMetadata(ctx context.Context, metadata *domain.Metadata, userID, resourceOwner string) (_ *domain.Metadata, err error) {
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setMetadata := NewUserMetadataWriteModel(userID, resourceOwner, metadata.Key)
|
||||
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
|
||||
event, err := c.setUserMetadata(ctx, userAgg, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = AppendAndReduce(setMetadata, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToUserMetadata(setMetadata), nil
|
||||
}
|
||||
|
||||
func (c *Commands) BulkSetUserMetadata(ctx context.Context, userID, resourceOwner string, metadatas ...*domain.Metadata) (_ *domain.ObjectDetails, err error) {
|
||||
if len(metadatas) == 0 {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.EventPusher, len(metadatas))
|
||||
setMetadata := NewUserMetadataListWriteModel(userID, resourceOwner)
|
||||
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
|
||||
for i, data := range metadatas {
|
||||
event, err := c.setUserMetadata(ctx, userAgg, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events[i] = event
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = AppendAndReduce(setMetadata, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&setMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) setUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadata *domain.Metadata) (pusher eventstore.EventPusher, err error) {
|
||||
if !metadata.IsValid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "META-2m00f", "Errors.Metadata.Invalid")
|
||||
}
|
||||
return user.NewMetadataSetEvent(
|
||||
ctx,
|
||||
userAgg,
|
||||
metadata.Key,
|
||||
metadata.Value,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveUserMetadata(ctx context.Context, metadataKey, userID, resourceOwner string) (_ *domain.ObjectDetails, err error) {
|
||||
if metadataKey == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "META-2n0fs", "Errors.Metadata.Invalid")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
removeMetadata, err := c.getUserMetadataModelByID(ctx, userID, resourceOwner, metadataKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !removeMetadata.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "META-ncnw3", "Errors.Metadata.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&removeMetadata.WriteModel)
|
||||
event, err := c.removeUserMetadata(ctx, userAgg, metadataKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = AppendAndReduce(removeMetadata, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceOwner string, metadataKeys ...string) (_ *domain.ObjectDetails, err error) {
|
||||
if len(metadataKeys) == 0 {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.EventPusher, len(metadataKeys))
|
||||
removeMetadata, err := c.getUserMetadataListModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&removeMetadata.WriteModel)
|
||||
for i, key := range metadataKeys {
|
||||
if key == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-m29ds", "Errors.Metadata.Invalid")
|
||||
}
|
||||
if _, found := removeMetadata.metadataList[key]; !found {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "META-2nnds", "Errors.Metadata.KeyNotExisting")
|
||||
}
|
||||
event, err := c.removeUserMetadata(ctx, userAgg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events[i] = event
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = AppendAndReduce(removeMetadata, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadataKey string) (pusher eventstore.EventPusher, err error) {
|
||||
pusher = user.NewMetadataRemovedEvent(
|
||||
ctx,
|
||||
userAgg,
|
||||
metadataKey,
|
||||
)
|
||||
return pusher, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getUserMetadataModelByID(ctx context.Context, userID, resourceOwner, key string) (*UserMetadataWriteModel, error) {
|
||||
userMetadataWriteModel := NewUserMetadataWriteModel(userID, resourceOwner, key)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userMetadataWriteModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getUserMetadataListModelByID(ctx context.Context, userID, resourceOwner string) (*UserMetadataListWriteModel, error) {
|
||||
userMetadataWriteModel := NewUserMetadataListWriteModel(userID, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userMetadataWriteModel, nil
|
||||
}
|
92
internal/command/user_metadata_model.go
Normal file
92
internal/command/user_metadata_model.go
Normal file
@ -0,0 +1,92 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type UserMetadataWriteModel struct {
|
||||
MetadataWriteModel
|
||||
}
|
||||
|
||||
func NewUserMetadataWriteModel(userID, resourceOwner, key string) *UserMetadataWriteModel {
|
||||
return &UserMetadataWriteModel{
|
||||
MetadataWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
Key: key,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserMetadataWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *user.MetadataSetEvent:
|
||||
wm.MetadataWriteModel.AppendEvents(&e.SetEvent)
|
||||
case *user.MetadataRemovedEvent:
|
||||
wm.MetadataWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserMetadataWriteModel) Reduce() error {
|
||||
return wm.MetadataWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *UserMetadataWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateIDs(wm.MetadataWriteModel.AggregateID).
|
||||
AggregateTypes(user.AggregateType).
|
||||
EventTypes(
|
||||
user.MetadataSetType,
|
||||
user.MetadataRemovedType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
type UserMetadataListWriteModel struct {
|
||||
MetadataListWriteModel
|
||||
}
|
||||
|
||||
func NewUserMetadataListWriteModel(userID, resourceOwner string) *UserMetadataListWriteModel {
|
||||
return &UserMetadataListWriteModel{
|
||||
MetadataListWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
metadataList: make(map[string][]byte),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserMetadataListWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *user.MetadataSetEvent:
|
||||
wm.MetadataListWriteModel.AppendEvents(&e.SetEvent)
|
||||
case *user.MetadataRemovedEvent:
|
||||
wm.MetadataListWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserMetadataListWriteModel) Reduce() error {
|
||||
return wm.MetadataListWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *UserMetadataListWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateIDs(wm.MetadataListWriteModel.AggregateID).
|
||||
AggregateTypes(user.AggregateType).
|
||||
EventTypes(
|
||||
user.MetadataSetType,
|
||||
user.MetadataRemovedType).
|
||||
Builder()
|
||||
}
|
739
internal/command/user_metadata_test.go
Normal file
739
internal/command/user_metadata_test.go
Normal file
@ -0,0 +1,739 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestCommandSide_SetMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
metadata *domain.Metadata
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.Metadata
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Metadata{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
State: domain.MetadataStateActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.SetUserMetadata(tt.args.ctx, tt.args.metadata, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_BulkSetMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
metadataList []*domain.Metadata
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "empty meta data list, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []*domain.Metadata{
|
||||
{Key: "key", Value: []byte("value")},
|
||||
{Key: "key1", Value: []byte("value1")},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []*domain.Metadata{
|
||||
{Key: "key"},
|
||||
{Key: "key1"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key1",
|
||||
[]byte("value1"),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []*domain.Metadata{
|
||||
{Key: "key", Value: []byte("value")},
|
||||
{Key: "key1", Value: []byte("value1")},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.BulkSetUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
metadataKey string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataKey: "key",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataKey: "",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "meta data not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataKey: "key",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataRemovedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataKey: "key",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.RemoveUserMetadata(tt.args.ctx, tt.args.metadataKey, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_BulkRemoveMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
metadataList []string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "empty meta data list, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []string{"key", "key1"},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata keys not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []string{"key", "key1"},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key1",
|
||||
[]byte("value1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []string{""},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key1",
|
||||
[]byte("value1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataRemovedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataRemovedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key1",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []string{"key", "key1"},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.BulkRemoveUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
83
internal/domain/metadata.go
Normal file
83
internal/domain/metadata.go
Normal file
@ -0,0 +1,83 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
State MetadataState
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type MetadataState int32
|
||||
|
||||
const (
|
||||
MetadataStateUnspecified MetadataState = iota
|
||||
MetadataStateActive
|
||||
MetadataStateRemoved
|
||||
)
|
||||
|
||||
func (m *Metadata) IsValid() bool {
|
||||
return m.Key != "" && len(m.Value) > 0
|
||||
}
|
||||
|
||||
func (s MetadataState) Exists() bool {
|
||||
return s != MetadataStateUnspecified && s != MetadataStateRemoved
|
||||
}
|
||||
|
||||
type MetadataSearchRequest struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
SortingColumn MetadataSearchKey
|
||||
Asc bool
|
||||
Queries []*MetadataSearchQuery
|
||||
}
|
||||
|
||||
type MetadataSearchKey int32
|
||||
|
||||
const (
|
||||
MetadataSearchKeyUnspecified MetadataSearchKey = iota
|
||||
MetadataSearchKeyAggregateID
|
||||
MetadataSearchKeyResourceOwner
|
||||
MetadataSearchKeyKey
|
||||
MetadataSearchKeyValue
|
||||
)
|
||||
|
||||
type MetadataSearchQuery struct {
|
||||
Key MetadataSearchKey
|
||||
Method SearchMethod
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type MetadataSearchResponse struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
TotalResult uint64
|
||||
Result []*Metadata
|
||||
Sequence uint64
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func (r *MetadataSearchRequest) EnsureLimit(limit uint64) error {
|
||||
if r.Limit > limit {
|
||||
return caos_errors.ThrowInvalidArgument(nil, "SEARCH-0ds32", "Errors.Limit.ExceedsDefault")
|
||||
}
|
||||
if r.Limit == 0 {
|
||||
r.Limit = limit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MetadataSearchRequest) AppendAggregateIDQuery(aggregateID string) {
|
||||
r.Queries = append(r.Queries, &MetadataSearchQuery{Key: MetadataSearchKeyAggregateID, Method: SearchMethodEquals, Value: aggregateID})
|
||||
}
|
||||
|
||||
func (r *MetadataSearchRequest) AppendResourceOwnerQuery(resourceOwner string) {
|
||||
r.Queries = append(r.Queries, &MetadataSearchQuery{Key: MetadataSearchKeyResourceOwner, Method: SearchMethodEquals, Value: resourceOwner})
|
||||
}
|
80
internal/iam/repository/view/metadata_view.go
Normal file
80
internal/iam/repository/view/metadata_view.go
Normal file
@ -0,0 +1,80 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
func GetMetadataList(db *gorm.DB, table string, aggregateID string) ([]*model.MetadataView, error) {
|
||||
metadatas := make([]*model.MetadataView, 0)
|
||||
queries := []*domain.MetadataSearchQuery{
|
||||
{
|
||||
Key: domain.MetadataSearchKeyAggregateID,
|
||||
Value: aggregateID,
|
||||
Method: domain.SearchMethodEquals,
|
||||
},
|
||||
}
|
||||
query := repository.PrepareSearchQuery(table, model.MetadataSearchRequest{Queries: queries})
|
||||
_, err := query(db, &metadatas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metadatas, nil
|
||||
}
|
||||
|
||||
func MetadataByKey(db *gorm.DB, table, aggregateID, key string) (*model.MetadataView, error) {
|
||||
metadata := new(model.MetadataView)
|
||||
aggregateIDQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
|
||||
keyQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyKey, Value: key, Method: domain.SearchMethodEquals}
|
||||
query := repository.PrepareGetByQuery(table, aggregateIDQuery, keyQuery)
|
||||
err := query(db, metadata)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-29kkd", "Errors.Metadata.NotExisting")
|
||||
}
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
func MetadataByKeyAndResourceOwner(db *gorm.DB, table, aggregateID, resourceOwner, key string) (*model.MetadataView, error) {
|
||||
metadata := new(model.MetadataView)
|
||||
aggregateIDQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
|
||||
resourceOwnerQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyResourceOwner, Value: resourceOwner, Method: domain.SearchMethodEquals}
|
||||
keyQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyKey, Value: key, Method: domain.SearchMethodEquals}
|
||||
query := repository.PrepareGetByQuery(table, aggregateIDQuery, resourceOwnerQuery, keyQuery)
|
||||
err := query(db, metadata)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-29kkd", "Errors.Metadata.NotExisting")
|
||||
}
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
func SearchMetadata(db *gorm.DB, table string, req *domain.MetadataSearchRequest) ([]*model.MetadataView, uint64, error) {
|
||||
metadata := make([]*model.MetadataView, 0)
|
||||
query := repository.PrepareSearchQuery(table, model.MetadataSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
||||
count, err := query(db, &metadata)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return metadata, count, nil
|
||||
}
|
||||
|
||||
func PutMetadata(db *gorm.DB, table string, customText *model.MetadataView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, customText)
|
||||
}
|
||||
|
||||
func DeleteMetadata(db *gorm.DB, table, aggregateID, key string) error {
|
||||
aggregateIDQuery := repository.Key{Key: model.MetadataSearchKey(domain.MetadataSearchKeyAggregateID), Value: aggregateID}
|
||||
keyQuery := repository.Key{Key: model.MetadataSearchKey(domain.MetadataSearchKeyKey), Value: key}
|
||||
delete := repository.PrepareDeleteByKeys(table, aggregateIDQuery, keyQuery)
|
||||
return delete(db)
|
||||
}
|
||||
|
||||
func DeleteMetadataByAggregateID(db *gorm.DB, table, aggregateID string) error {
|
||||
aggregateIDQuery := repository.Key{Key: model.MetadataSearchKey(domain.MetadataSearchKeyAggregateID), Value: aggregateID}
|
||||
delete := repository.PrepareDeleteByKeys(table, aggregateIDQuery)
|
||||
return delete(db)
|
||||
}
|
@ -30,10 +30,10 @@ type CustomTextView struct {
|
||||
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||
|
||||
Template string `json:"Template" gorm:"column:template;primary_key"`
|
||||
Language string `json:"Language" gorm:"column:language;primary_key"`
|
||||
Key string `json:"Key" gorm:"column:key;primary_key"`
|
||||
Text string `json:"Text" gorm:"column:text"`
|
||||
Template string `json:"template" gorm:"column:template;primary_key"`
|
||||
Language string `json:"language" gorm:"column:language;primary_key"`
|
||||
Key string `json:"key" gorm:"column:key;primary_key"`
|
||||
Text string `json:"text" gorm:"column:text"`
|
||||
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
}
|
||||
|
79
internal/iam/repository/view/model/metadata.go
Normal file
79
internal/iam/repository/view/model/metadata.go
Normal file
@ -0,0 +1,79 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
)
|
||||
|
||||
const (
|
||||
MetadataKeyAggregateID = "aggregate_id"
|
||||
MetadataKeyResourceOwner = "resource_owner"
|
||||
MetadataKeyKey = "key"
|
||||
MetadataKeyValue = "value"
|
||||
)
|
||||
|
||||
type MetadataView struct {
|
||||
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
|
||||
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
|
||||
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||
|
||||
Key string `json:"key" gorm:"column:key;primary_key"`
|
||||
Value []byte `json:"value" gorm:"column:value"`
|
||||
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
}
|
||||
|
||||
func MetadataViewsToDomain(texts []*MetadataView) []*domain.Metadata {
|
||||
result := make([]*domain.Metadata, len(texts))
|
||||
for i, text := range texts {
|
||||
result[i] = MetadataViewToDomain(text)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MetadataViewToDomain(data *MetadataView) *domain.Metadata {
|
||||
return &domain.Metadata{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: data.AggregateID,
|
||||
Sequence: data.Sequence,
|
||||
CreationDate: data.CreationDate,
|
||||
ChangeDate: data.ChangeDate,
|
||||
},
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
}
|
||||
}
|
||||
|
||||
func (md *MetadataView) AppendEvent(event *models.Event) (err error) {
|
||||
md.Sequence = event.Sequence
|
||||
switch event.Type {
|
||||
case usr_model.UserMetadataSet:
|
||||
md.setRootData(event)
|
||||
err = md.SetData(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (md *MetadataView) setRootData(event *models.Event) {
|
||||
md.AggregateID = event.AggregateID
|
||||
md.ResourceOwner = event.ResourceOwner
|
||||
md.ChangeDate = event.CreationDate
|
||||
md.Sequence = event.Sequence
|
||||
}
|
||||
|
||||
func (md *MetadataView) SetData(event *models.Event) error {
|
||||
if err := json.Unmarshal(event.Data, md); err != nil {
|
||||
logging.Log("MODEL-3n9fs").WithError(err).Error("could not unmarshal event data")
|
||||
return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
|
||||
}
|
||||
return nil
|
||||
}
|
64
internal/iam/repository/view/model/metadata_query.go
Normal file
64
internal/iam/repository/view/model/metadata_query.go
Normal file
@ -0,0 +1,64 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
type MetadataSearchRequest domain.MetadataSearchRequest
|
||||
type MetadataSearchQuery domain.MetadataSearchQuery
|
||||
type MetadataSearchKey domain.MetadataSearchKey
|
||||
|
||||
func (req MetadataSearchRequest) GetLimit() uint64 {
|
||||
return req.Limit
|
||||
}
|
||||
|
||||
func (req MetadataSearchRequest) GetOffset() uint64 {
|
||||
return req.Offset
|
||||
}
|
||||
|
||||
func (req MetadataSearchRequest) GetSortingColumn() repository.ColumnKey {
|
||||
if req.SortingColumn == domain.MetadataSearchKeyUnspecified {
|
||||
return nil
|
||||
}
|
||||
return MetadataSearchKey(req.SortingColumn)
|
||||
}
|
||||
|
||||
func (req MetadataSearchRequest) GetAsc() bool {
|
||||
return req.Asc
|
||||
}
|
||||
|
||||
func (req MetadataSearchRequest) GetQueries() []repository.SearchQuery {
|
||||
result := make([]repository.SearchQuery, len(req.Queries))
|
||||
for i, q := range req.Queries {
|
||||
result[i] = MetadataSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (req MetadataSearchQuery) GetKey() repository.ColumnKey {
|
||||
return MetadataSearchKey(req.Key)
|
||||
}
|
||||
|
||||
func (req MetadataSearchQuery) GetMethod() domain.SearchMethod {
|
||||
return req.Method
|
||||
}
|
||||
|
||||
func (req MetadataSearchQuery) GetValue() interface{} {
|
||||
return req.Value
|
||||
}
|
||||
|
||||
func (key MetadataSearchKey) ToColumnName() string {
|
||||
switch domain.MetadataSearchKey(key) {
|
||||
case domain.MetadataSearchKeyAggregateID:
|
||||
return MetadataKeyAggregateID
|
||||
case domain.MetadataSearchKeyResourceOwner:
|
||||
return MetadataKeyResourceOwner
|
||||
case domain.MetadataSearchKeyKey:
|
||||
return MetadataKeyKey
|
||||
case domain.MetadataSearchKeyValue:
|
||||
return MetadataKeyValue
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
usr_view "github.com/caos/zitadel/internal/user/repository/view"
|
||||
|
||||
"github.com/caos/logging"
|
||||
@ -128,6 +129,40 @@ func (repo *UserRepo) IsUserUnique(ctx context.Context, userName, email string)
|
||||
return repo.View.IsUserUnique(userName, email)
|
||||
}
|
||||
|
||||
func (repo *UserRepo) GetMetadataByKey(ctx context.Context, userID, resourceOwner, key string) (*domain.Metadata, error) {
|
||||
data, err := repo.View.MetadataByKeyAndResourceOwner(userID, resourceOwner, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_model.MetadataViewToDomain(data), nil
|
||||
}
|
||||
|
||||
func (repo *UserRepo) SearchMetadata(ctx context.Context, userID, resourceOwner string, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) {
|
||||
err := req.EnsureLimit(repo.SearchLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence, sequenceErr := repo.View.GetLatestUserSequence()
|
||||
logging.Log("EVENT-m0ds3").OnError(sequenceErr).Warn("could not read latest user sequence")
|
||||
req.AppendAggregateIDQuery(userID)
|
||||
req.AppendResourceOwnerQuery(resourceOwner)
|
||||
metadata, count, err := repo.View.SearchMetadata(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &domain.MetadataSearchResponse{
|
||||
Offset: req.Offset,
|
||||
Limit: req.Limit,
|
||||
TotalResult: count,
|
||||
Result: iam_model.MetadataViewsToDomain(metadata),
|
||||
}
|
||||
if sequenceErr == nil {
|
||||
result.Sequence = sequence.CurrentSequence
|
||||
result.Timestamp = sequence.LastSuccessfulSpoolerRun
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *UserRepo) UserMFAs(ctx context.Context, userID string) ([]*usr_model.MultiFactor, error) {
|
||||
user, err := repo.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
|
@ -85,6 +85,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||
newCustomText(
|
||||
handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}),
|
||||
newMetadata(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),
|
||||
}
|
||||
}
|
||||
|
||||
|
124
internal/management/repository/eventsourcing/handler/metadata.go
Normal file
124
internal/management/repository/eventsourcing/handler/metadata.go
Normal file
@ -0,0 +1,124 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newMetadata(handler handler) *Metadata {
|
||||
h := &Metadata{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (m *Metadata) subscribe() {
|
||||
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range m.subscription.Events {
|
||||
query.ReduceEvent(m, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
const (
|
||||
metadataTable = "management.metadata"
|
||||
)
|
||||
|
||||
func (m *Metadata) ViewModel() string {
|
||||
return metadataTable
|
||||
}
|
||||
|
||||
func (m *Metadata) Subscription() *v1.Subscription {
|
||||
return m.subscription
|
||||
}
|
||||
|
||||
func (_ *Metadata) AggregateTypes() []es_models.AggregateType {
|
||||
return []es_models.AggregateType{usr_model.UserAggregate}
|
||||
}
|
||||
|
||||
func (p *Metadata) CurrentSequence() (uint64, error) {
|
||||
sequence, err := p.view.GetLatestMetadataSequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (m *Metadata) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := m.view.GetLatestMetadataSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(m.AggregateTypes()...).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (m *Metadata) Reduce(event *es_models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case usr_model.UserAggregate:
|
||||
err = m.processMetadata(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Metadata) processMetadata(event *es_models.Event) (err error) {
|
||||
metadata := new(iam_model.MetadataView)
|
||||
switch event.Type {
|
||||
case usr_model.UserMetadataSet:
|
||||
err = metadata.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata, err = m.view.MetadataByKey(event.AggregateID, metadata.Key)
|
||||
if err != nil && !caos_errs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if caos_errs.IsNotFound(err) {
|
||||
err = nil
|
||||
metadata = new(iam_model.MetadataView)
|
||||
metadata.CreationDate = event.CreationDate
|
||||
}
|
||||
err = metadata.AppendEvent(event)
|
||||
case usr_model.UserMetadataRemoved:
|
||||
data := new(iam_model.MetadataView)
|
||||
err = data.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
|
||||
case usr_model.UserRemoved:
|
||||
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
|
||||
default:
|
||||
return m.view.ProcessedMetadataSequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.view.PutMetadata(metadata, event)
|
||||
}
|
||||
|
||||
func (m *Metadata) OnError(event *es_models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-3m912", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler")
|
||||
return spooler.HandleError(event, err, m.view.GetLatestMetadataFailedEvent, m.view.ProcessedMetadataFailedEvent, m.view.ProcessedMetadataSequence, m.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (o *Metadata) OnSuccess() error {
|
||||
return spooler.HandleSuccess(o.view.UpdateMetadataSpoolerRunTimestamp)
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataTable = "management.metadata"
|
||||
)
|
||||
|
||||
func (v *View) MetadataByKey(aggregateID, key string) (*model.MetadataView, error) {
|
||||
return view.MetadataByKey(v.Db, metadataTable, aggregateID, key)
|
||||
}
|
||||
|
||||
func (v *View) MetadataByKeyAndResourceOwner(aggregateID, resourceOwner, key string) (*model.MetadataView, error) {
|
||||
return view.MetadataByKeyAndResourceOwner(v.Db, metadataTable, aggregateID, resourceOwner, key)
|
||||
}
|
||||
|
||||
func (v *View) MetadataListByAggregateID(aggregateID string) ([]*model.MetadataView, error) {
|
||||
return view.GetMetadataList(v.Db, metadataTable, aggregateID)
|
||||
}
|
||||
|
||||
func (v *View) SearchMetadata(request *domain.MetadataSearchRequest) ([]*model.MetadataView, uint64, error) {
|
||||
return view.SearchMetadata(v.Db, metadataTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutMetadata(template *model.MetadataView, event *models.Event) error {
|
||||
err := view.PutMetadata(v.Db, metadataTable, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedMetadataSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteMetadata(aggregateID, key string, event *models.Event) error {
|
||||
err := view.DeleteMetadata(v.Db, metadataTable, aggregateID, key)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedMetadataSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteMetadataByAggregateID(aggregateID string, event *models.Event) error {
|
||||
err := view.DeleteMetadataByAggregateID(v.Db, metadataTable, aggregateID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedMetadataSequence(event)
|
||||
}
|
||||
func (v *View) GetLatestMetadataSequence() (*global_view.CurrentSequence, error) {
|
||||
return v.latestSequence(metadataTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedMetadataSequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(metadataTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdateMetadataSpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(metadataTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestMetadataFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(metadataTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedMetadataFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
)
|
||||
@ -16,6 +17,9 @@ type UserRepository interface {
|
||||
GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error)
|
||||
IsUserUnique(ctx context.Context, userName, email string) (bool, error)
|
||||
|
||||
GetMetadataByKey(ctx context.Context, userID, resourceOwner, key string) (*domain.Metadata, error)
|
||||
SearchMetadata(ctx context.Context, userID, resourceOwner string, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error)
|
||||
|
||||
UserChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*model.UserChanges, error)
|
||||
|
||||
ProfileByID(ctx context.Context, userID string) (*model.Profile, error)
|
||||
|
92
internal/repository/metadata/metadata.go
Normal file
92
internal/repository/metadata/metadata.go
Normal file
@ -0,0 +1,92 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
SetEventType = "metadata.set"
|
||||
RemovedEventType = "metadata.removed"
|
||||
)
|
||||
|
||||
type SetEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Key string `json:"key"`
|
||||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
func (e *SetEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *SetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSetEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
key string,
|
||||
value []byte,
|
||||
) *SetEvent {
|
||||
return &SetEvent{
|
||||
BaseEvent: *base,
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func SetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &SetEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "META-3n9fs", "unable to unmarshal metadata set")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type RemovedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (e *RemovedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *RemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRemovedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
key string,
|
||||
) *RemovedEvent {
|
||||
|
||||
return &RemovedEvent{
|
||||
BaseEvent: *base,
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
func RemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &RemovedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "META-2m99f", "unable to unmarshal metadata removed")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
@ -45,6 +45,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(UserDomainClaimedType, DomainClaimedEventMapper).
|
||||
RegisterFilterEventMapper(UserDomainClaimedSentType, DomainClaimedSentEventMapper).
|
||||
RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper).
|
||||
RegisterFilterEventMapper(MetadataSetType, MetadataSetEventMapper).
|
||||
RegisterFilterEventMapper(MetadataRemovedType, MetadataRemovedEventMapper).
|
||||
RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper).
|
||||
RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper).
|
||||
RegisterFilterEventMapper(HumanInitialCodeAddedType, HumanInitialCodeAddedEventMapper).
|
||||
|
63
internal/repository/user/metadata.go
Normal file
63
internal/repository/user/metadata.go
Normal file
@ -0,0 +1,63 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/repository/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
MetadataSetType = userEventTypePrefix + metadata.SetEventType
|
||||
MetadataRemovedType = userEventTypePrefix + metadata.RemovedEventType
|
||||
)
|
||||
|
||||
type MetadataSetEvent struct {
|
||||
metadata.SetEvent
|
||||
}
|
||||
|
||||
func NewMetadataSetEvent(ctx context.Context, aggregate *eventstore.Aggregate, key string, value []byte) *MetadataSetEvent {
|
||||
return &MetadataSetEvent{
|
||||
SetEvent: *metadata.NewSetEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
MetadataSetType),
|
||||
key,
|
||||
value),
|
||||
}
|
||||
}
|
||||
|
||||
func MetadataSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := metadata.SetEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MetadataSetEvent{SetEvent: *e.(*metadata.SetEvent)}, nil
|
||||
}
|
||||
|
||||
type MetadataRemovedEvent struct {
|
||||
metadata.RemovedEvent
|
||||
}
|
||||
|
||||
func NewMetadataRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, key string) *MetadataRemovedEvent {
|
||||
return &MetadataRemovedEvent{
|
||||
RemovedEvent: *metadata.NewRemovedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
MetadataRemovedType),
|
||||
key),
|
||||
}
|
||||
}
|
||||
|
||||
func MetadataRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := metadata.RemovedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MetadataRemovedEvent{RemovedEvent: *e.(*metadata.RemovedEvent)}, nil
|
||||
}
|
@ -359,6 +359,11 @@ Errors:
|
||||
ReadError: Übersetzungsdatei konnte nicht gelesen werden
|
||||
MergeError: Übersetzungsdatei konnte nicht mit benutzerdefinierten Übersetzungen zusammengeführt werden
|
||||
NotFound: Übersetzungsdatei existiert nicht
|
||||
MetaData:
|
||||
NotFound: Meta Daten konnten nicht gefunden werden
|
||||
NoData: Meta Daten Liste ist leer
|
||||
Invalid: Meta Daten sind ungültig
|
||||
KeyNotExisting: Ein oder mehrere Keys existiert nicht
|
||||
EventTypes:
|
||||
user:
|
||||
added: Benutzer hinzugefügt
|
||||
|
@ -359,6 +359,11 @@ Errors:
|
||||
ReadError: Error in reading translation file
|
||||
MergeError: Translation file could not be merged with custom translations
|
||||
NotFound: Translation file doesn't exist
|
||||
MetaData:
|
||||
NotFound: Metadata not found
|
||||
NoData: Metadata list is empty
|
||||
Invalid: Metadata is invalid
|
||||
KeyNotExisting: One or more keys do not exist
|
||||
EventTypes:
|
||||
user:
|
||||
added: User added
|
||||
|
@ -69,6 +69,9 @@ const (
|
||||
|
||||
DomainClaimed models.EventType = "user.domain.claimed"
|
||||
DomainClaimedSent models.EventType = "user.domain.claimed.sent"
|
||||
|
||||
UserMetadataSet models.EventType = "user.metadata.set"
|
||||
UserMetadataRemoved models.EventType = "user.metadata.removed"
|
||||
)
|
||||
|
||||
// the following consts are for user(v2).human
|
||||
|
27
migrations/cockroach/V1.58__metadata.sql
Normal file
27
migrations/cockroach/V1.58__metadata.sql
Normal file
@ -0,0 +1,27 @@
|
||||
CREATE TABLE auth.metadata (
|
||||
aggregate_id TEXT,
|
||||
|
||||
key TEXT,
|
||||
value BYTES,
|
||||
|
||||
resource_owner TEXT,
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
sequence BIGINT,
|
||||
|
||||
PRIMARY KEY (aggregate_id, key)
|
||||
);
|
||||
|
||||
CREATE TABLE management.metadata (
|
||||
aggregate_id TEXT,
|
||||
|
||||
key TEXT,
|
||||
value BYTES,
|
||||
|
||||
resource_owner TEXT,
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
sequence BIGINT,
|
||||
|
||||
PRIMARY KEY (aggregate_id, key)
|
||||
);
|
@ -7,6 +7,7 @@ import "zitadel/object.proto";
|
||||
import "zitadel/options.proto";
|
||||
import "zitadel/policy.proto";
|
||||
import "zitadel/idp.proto";
|
||||
import "zitadel/metadata.proto";
|
||||
import "validate/validate.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
@ -76,6 +77,7 @@ service AuthService {
|
||||
rpc ListMyUserChanges(ListMyUserChangesRequest) returns (ListMyUserChangesResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/changes/_search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
@ -87,6 +89,77 @@ service AuthService {
|
||||
rpc ListMyUserSessions(ListMyUserSessionsRequest) returns (ListMyUserSessionsResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/sessions/_search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated"
|
||||
};
|
||||
}
|
||||
|
||||
// Sets a user metadata by key to the authorized user
|
||||
rpc SetMyMetadata(SetMyMetadataRequest) returns (SetMyMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/metadata/{key}"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated"
|
||||
};
|
||||
}
|
||||
|
||||
// Set a list of user metadata to the authorized user
|
||||
rpc BulkSetMyMetadata(BulkSetMyMetadataRequest) returns (BulkSetMyMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/metadata/_bulk"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the user metadata of the authorized user
|
||||
rpc ListMyMetadata(ListMyMetadataRequest) returns (ListMyMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/metadata/_search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the user metadata by key of the authorized user
|
||||
rpc GetMyMetadata(GetMyMetadataRequest) returns (GetMyMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/users/me/metadata/{key}"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated"
|
||||
};
|
||||
}
|
||||
|
||||
// Removes a user metadata by key to the authorized user
|
||||
rpc RemoveMyMetadata(RemoveMyMetadataRequest) returns (RemoveMyMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/users/me/metadata/{key}"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated"
|
||||
};
|
||||
}
|
||||
|
||||
// Set a list of user metadata to the authorized user
|
||||
rpc BulkRemoveMyMetadata(BulkRemoveMyMetadataRequest) returns (BulkRemoveMyMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/users/me/metadata/_bulk"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
@ -98,6 +171,7 @@ service AuthService {
|
||||
rpc ListMyRefreshTokens(ListMyRefreshTokensRequest) returns (ListMyRefreshTokensResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/tokens/refresh/_search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
@ -120,6 +194,7 @@ service AuthService {
|
||||
rpc RevokeAllMyRefreshTokens(RevokeAllMyRefreshTokensRequest) returns (RevokeAllMyRefreshTokensResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/me/tokens/refresh/_revoke_all"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
@ -592,6 +667,61 @@ message ListMyUserSessionsResponse {
|
||||
repeated zitadel.user.v1.Session result = 1;
|
||||
}
|
||||
|
||||
message ListMyMetadataRequest {
|
||||
zitadel.v1.ListQuery query = 1;
|
||||
repeated zitadel.metadata.v1.MetadataQuery queries = 2;
|
||||
}
|
||||
|
||||
message ListMyMetadataResponse {
|
||||
zitadel.v1.ListDetails details = 1;
|
||||
repeated zitadel.metadata.v1.Metadata result = 2;
|
||||
}
|
||||
|
||||
message GetMyMetadataRequest {
|
||||
string key = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetMyMetadataResponse {
|
||||
zitadel.metadata.v1.Metadata metadata = 1;
|
||||
}
|
||||
|
||||
message SetMyMetadataRequest {
|
||||
string key = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
bytes value = 2 [(validate.rules).bytes = {min_len: 1, max_len: 500000}];
|
||||
}
|
||||
|
||||
message SetMyMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message BulkSetMyMetadataRequest {
|
||||
message Metadata {
|
||||
string key = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
bytes value = 2 [(validate.rules).bytes = {min_len: 1, max_len: 500000}];
|
||||
}
|
||||
repeated Metadata metadata = 1;
|
||||
}
|
||||
|
||||
message BulkSetMyMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message RemoveMyMetadataRequest {
|
||||
string key = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message RemoveMyMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message BulkRemoveMyMetadataRequest {
|
||||
repeated string keys = 1 [(validate.rules).repeated.items.string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message BulkRemoveMyMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message ListMyRefreshTokensRequest {}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import "zitadel/message.proto";
|
||||
import "zitadel/change.proto";
|
||||
import "zitadel/auth_n_key.proto";
|
||||
import "zitadel/features.proto";
|
||||
import "zitadel/metadata.proto";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
@ -285,6 +286,82 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Sets a user metadata by key
|
||||
rpc SetUserMetadata(SetUserMetadataRequest) returns (SetUserMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/{id}/metadata/{key}"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
feature: "metadata.user"
|
||||
};
|
||||
}
|
||||
|
||||
// Set a list of user metadata
|
||||
rpc BulkSetUserMetadata(BulkSetUserMetadataRequest) returns (BulkSetUserMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/{id}/metadata/_bulk"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
feature: "metadata.user"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the user metadata
|
||||
rpc ListUserMetadata(ListUserMetadataRequest) returns (ListUserMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/users/{id}/metadata/_search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.read"
|
||||
feature: "metadata.user"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the user metadata by key
|
||||
rpc GetUserMetadata(GetUserMetadataRequest) returns (GetUserMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/users/{id}/metadata/{key}"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.read"
|
||||
feature: "metadata.user"
|
||||
};
|
||||
}
|
||||
|
||||
// Removes a user metadata by key
|
||||
rpc RemoveUserMetadata(RemoveUserMetadataRequest) returns (RemoveUserMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/users/{id}/metadata/{key}"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
feature: "metadata.user"
|
||||
};
|
||||
}
|
||||
|
||||
// Set a list of user metadata
|
||||
rpc BulkRemoveUserMetadata(BulkRemoveUserMetadataRequest) returns (BulkRemoveUserMetadataResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/users/{id}/metadata/_bulk"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
feature: "metadata.user"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the profile of the human
|
||||
rpc GetHumanProfile(GetHumanProfileRequest) returns (GetHumanProfileResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -2827,6 +2904,68 @@ message UpdateUserNameResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message ListUserMetadataRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
zitadel.v1.ListQuery query = 2;
|
||||
repeated zitadel.metadata.v1.MetadataQuery queries = 3;
|
||||
}
|
||||
|
||||
message ListUserMetadataResponse {
|
||||
zitadel.v1.ListDetails details = 1;
|
||||
repeated zitadel.metadata.v1.Metadata result = 2;
|
||||
}
|
||||
|
||||
message GetUserMetadataRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string key = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetUserMetadataResponse {
|
||||
zitadel.metadata.v1.Metadata metadata = 1;
|
||||
}
|
||||
|
||||
message SetUserMetadataRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string key = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
bytes value = 3 [(validate.rules).bytes = {min_len: 1, max_len: 500000}];
|
||||
}
|
||||
|
||||
message SetUserMetadataResponse {
|
||||
string id = 1;
|
||||
zitadel.v1.ObjectDetails details = 2;
|
||||
}
|
||||
|
||||
message BulkSetUserMetadataRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
message Metadata {
|
||||
string key = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
bytes value = 2 [(validate.rules).bytes = {min_len: 1, max_len: 500000}];
|
||||
}
|
||||
repeated Metadata metadata = 2;
|
||||
}
|
||||
|
||||
message BulkSetUserMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message RemoveUserMetadataRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string key = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message RemoveUserMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message BulkRemoveUserMetadataRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
repeated string keys = 2 [(validate.rules).repeated.items.string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message BulkRemoveUserMetadataResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message GetHumanProfileRequest {
|
||||
string user_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
45
proto/zitadel/metadata.proto
Normal file
45
proto/zitadel/metadata.proto
Normal file
@ -0,0 +1,45 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "zitadel/object.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
package zitadel.metadata.v1;
|
||||
|
||||
option go_package ="github.com/caos/zitadel/pkg/grpc/metadata";
|
||||
|
||||
message Metadata {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
string key = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "metadata key"
|
||||
}
|
||||
];
|
||||
bytes value = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "metadata value"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message MetadataQuery {
|
||||
oneof query {
|
||||
option (validate.required) = true;
|
||||
MetadataKeyQuery key_query = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message MetadataKeyQuery {
|
||||
string key = 1 [
|
||||
(validate.rules).string = {max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"key\""
|
||||
}
|
||||
];
|
||||
zitadel.v1.TextQueryMethod method = 2 [
|
||||
(validate.rules).enum.defined_only = true,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "defines which text equality method is used";
|
||||
}
|
||||
];
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user