mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-28 09:23:41 +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 \
|
-I=/proto/include \
|
||||||
--grpc-gateway_out ${GOPATH}/src \
|
--grpc-gateway_out ${GOPATH}/src \
|
||||||
--grpc-gateway_opt logtostderr=true \
|
--grpc-gateway_opt logtostderr=true \
|
||||||
|
--grpc-gateway_opt allow_delete_body=true \
|
||||||
--openapiv2_out ${OPENAPI_PATH} \
|
--openapiv2_out ${OPENAPI_PATH} \
|
||||||
--openapiv2_opt logtostderr=true \
|
--openapiv2_opt logtostderr=true \
|
||||||
|
--openapiv2_opt allow_delete_body=true \
|
||||||
--authoption_out=${GRPC_PATH}/auth \
|
--authoption_out=${GRPC_PATH}/auth \
|
||||||
--validate_out=lang=go:${GOPATH}/src \
|
--validate_out=lang=go:${GOPATH}/src \
|
||||||
${PROTO_PATH}/auth.proto
|
${PROTO_PATH}/auth.proto
|
||||||
@ -114,6 +116,10 @@ protoc \
|
|||||||
-I=/proto/include \
|
-I=/proto/include \
|
||||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,message.md \
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,message.md \
|
||||||
${PROTO_PATH}/message.proto
|
${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 \
|
protoc \
|
||||||
-I=/proto/include \
|
-I=/proto/include \
|
||||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,object.md \
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,object.md \
|
||||||
@ -134,9 +140,14 @@ protoc \
|
|||||||
-I=/proto/include \
|
-I=/proto/include \
|
||||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,project.md \
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,project.md \
|
||||||
${PROTO_PATH}/project.proto
|
${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 \
|
protoc \
|
||||||
-I=/proto/include \
|
-I=/proto/include \
|
||||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,user.md \
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,user.md \
|
||||||
${PROTO_PATH}/user.proto
|
${PROTO_PATH}/user.proto
|
||||||
|
|
||||||
|
|
||||||
echo "done generating grpc"
|
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
|
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
|
### ListMyRefreshTokens
|
||||||
|
|
||||||
> **rpc** ListMyRefreshTokens([ListMyRefreshTokensRequest](#listmyrefreshtokensrequest))
|
> **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
|
### GetMyEmailRequest
|
||||||
This is an empty request
|
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
|
### GetMyPasswordComplexityPolicyRequest
|
||||||
This is an empty request
|
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
|
### ListMyPasswordlessRequest
|
||||||
This is an empty request
|
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
|
### 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
|
### SetMyPhoneRequest
|
||||||
|
|
||||||
|
|
||||||
|
@ -236,6 +236,78 @@ Changes the username
|
|||||||
GET: /users/{user_id}/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
|
### GetHumanProfile
|
||||||
|
|
||||||
> **rpc** GetHumanProfile([GetHumanProfileRequest](#gethumanprofilerequest))
|
> **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
|
### 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
|
### HealthzRequest
|
||||||
This is an empty request
|
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
|
### 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
|
### 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
|
### 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/authz"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/change"
|
"github.com/caos/zitadel/internal/api/grpc/change"
|
||||||
|
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
"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"
|
"github.com/caos/zitadel/internal/api/grpc/org"
|
||||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
|
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
|
}, 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) {
|
func (s *Server) ListMyUserSessions(ctx context.Context, req *auth_pb.ListMyUserSessionsRequest) (*auth_pb.ListMyUserSessionsResponse, error) {
|
||||||
userSessions, err := s.repo.GetMyUserSessions(ctx)
|
userSessions, err := s.repo.GetMyUserSessions(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,10 +9,12 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/api/grpc/authn"
|
"github.com/caos/zitadel/internal/api/grpc/authn"
|
||||||
change_grpc "github.com/caos/zitadel/internal/api/grpc/change"
|
change_grpc "github.com/caos/zitadel/internal/api/grpc/change"
|
||||||
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
|
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"
|
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||||
obj_grpc "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"
|
"github.com/caos/zitadel/internal/api/grpc/user"
|
||||||
user_grpc "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"
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
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
|
}, 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) {
|
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))
|
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/authn"
|
"github.com/caos/zitadel/internal/api/grpc/authn"
|
||||||
|
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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 {
|
func AddHumanUserRequestToDomain(req *mgmt_pb.AddHumanUserRequest) *domain.Human {
|
||||||
h := &domain.Human{
|
h := &domain.Human{
|
||||||
Username: req.UserName,
|
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/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"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_model "github.com/caos/zitadel/internal/key/model"
|
||||||
key_view_model "github.com/caos/zitadel/internal/key/repository/view/model"
|
key_view_model "github.com/caos/zitadel/internal/key/repository/view/model"
|
||||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
"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)
|
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}),
|
newRefreshToken(handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}),
|
||||||
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||||
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), 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"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
key_model "github.com/caos/zitadel/internal/key/model"
|
key_model "github.com/caos/zitadel/internal/key/model"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/user/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)
|
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)
|
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,
|
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"`
|
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||||
|
|
||||||
Template string `json:"Template" gorm:"column:template;primary_key"`
|
Template string `json:"template" gorm:"column:template;primary_key"`
|
||||||
Language string `json:"Language" gorm:"column:language;primary_key"`
|
Language string `json:"language" gorm:"column:language;primary_key"`
|
||||||
Key string `json:"Key" gorm:"column:key;primary_key"`
|
Key string `json:"key" gorm:"column:key;primary_key"`
|
||||||
Text string `json:"Text" gorm:"column:text"`
|
Text string `json:"text" gorm:"column:text"`
|
||||||
|
|
||||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
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"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"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"
|
usr_view "github.com/caos/zitadel/internal/user/repository/view"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
@ -128,6 +129,40 @@ func (repo *UserRepo) IsUserUnique(ctx context.Context, userName, email string)
|
|||||||
return repo.View.IsUserUnique(userName, email)
|
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) {
|
func (repo *UserRepo) UserMFAs(ctx context.Context, userID string) ([]*usr_model.MultiFactor, error) {
|
||||||
user, err := repo.UserByID(ctx, userID)
|
user, err := repo.UserByID(ctx, userID)
|
||||||
if err != nil {
|
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}),
|
handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||||
newCustomText(
|
newCustomText(
|
||||||
handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}),
|
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"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
key_model "github.com/caos/zitadel/internal/key/model"
|
key_model "github.com/caos/zitadel/internal/key/model"
|
||||||
"github.com/caos/zitadel/internal/user/model"
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
)
|
)
|
||||||
@ -16,6 +17,9 @@ type UserRepository interface {
|
|||||||
GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error)
|
GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error)
|
||||||
IsUserUnique(ctx context.Context, userName, email string) (bool, 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)
|
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)
|
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(UserDomainClaimedType, DomainClaimedEventMapper).
|
||||||
RegisterFilterEventMapper(UserDomainClaimedSentType, DomainClaimedSentEventMapper).
|
RegisterFilterEventMapper(UserDomainClaimedSentType, DomainClaimedSentEventMapper).
|
||||||
RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper).
|
RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper).
|
||||||
|
RegisterFilterEventMapper(MetadataSetType, MetadataSetEventMapper).
|
||||||
|
RegisterFilterEventMapper(MetadataRemovedType, MetadataRemovedEventMapper).
|
||||||
RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper).
|
RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper).
|
||||||
RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper).
|
RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper).
|
||||||
RegisterFilterEventMapper(HumanInitialCodeAddedType, HumanInitialCodeAddedEventMapper).
|
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
|
ReadError: Übersetzungsdatei konnte nicht gelesen werden
|
||||||
MergeError: Übersetzungsdatei konnte nicht mit benutzerdefinierten Übersetzungen zusammengeführt werden
|
MergeError: Übersetzungsdatei konnte nicht mit benutzerdefinierten Übersetzungen zusammengeführt werden
|
||||||
NotFound: Übersetzungsdatei existiert nicht
|
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:
|
EventTypes:
|
||||||
user:
|
user:
|
||||||
added: Benutzer hinzugefügt
|
added: Benutzer hinzugefügt
|
||||||
|
@ -359,6 +359,11 @@ Errors:
|
|||||||
ReadError: Error in reading translation file
|
ReadError: Error in reading translation file
|
||||||
MergeError: Translation file could not be merged with custom translations
|
MergeError: Translation file could not be merged with custom translations
|
||||||
NotFound: Translation file doesn't exist
|
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:
|
EventTypes:
|
||||||
user:
|
user:
|
||||||
added: User added
|
added: User added
|
||||||
|
@ -69,6 +69,9 @@ const (
|
|||||||
|
|
||||||
DomainClaimed models.EventType = "user.domain.claimed"
|
DomainClaimed models.EventType = "user.domain.claimed"
|
||||||
DomainClaimedSent models.EventType = "user.domain.claimed.sent"
|
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
|
// 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/options.proto";
|
||||||
import "zitadel/policy.proto";
|
import "zitadel/policy.proto";
|
||||||
import "zitadel/idp.proto";
|
import "zitadel/idp.proto";
|
||||||
|
import "zitadel/metadata.proto";
|
||||||
import "validate/validate.proto";
|
import "validate/validate.proto";
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/duration.proto";
|
import "google/protobuf/duration.proto";
|
||||||
@ -76,6 +77,7 @@ service AuthService {
|
|||||||
rpc ListMyUserChanges(ListMyUserChangesRequest) returns (ListMyUserChangesResponse) {
|
rpc ListMyUserChanges(ListMyUserChangesRequest) returns (ListMyUserChangesResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/users/me/changes/_search"
|
post: "/users/me/changes/_search"
|
||||||
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
@ -87,6 +89,77 @@ service AuthService {
|
|||||||
rpc ListMyUserSessions(ListMyUserSessionsRequest) returns (ListMyUserSessionsResponse) {
|
rpc ListMyUserSessions(ListMyUserSessionsRequest) returns (ListMyUserSessionsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/users/me/sessions/_search"
|
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) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
@ -98,6 +171,7 @@ service AuthService {
|
|||||||
rpc ListMyRefreshTokens(ListMyRefreshTokensRequest) returns (ListMyRefreshTokensResponse) {
|
rpc ListMyRefreshTokens(ListMyRefreshTokensRequest) returns (ListMyRefreshTokensResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/users/me/tokens/refresh/_search"
|
post: "/users/me/tokens/refresh/_search"
|
||||||
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
@ -120,6 +194,7 @@ service AuthService {
|
|||||||
rpc RevokeAllMyRefreshTokens(RevokeAllMyRefreshTokensRequest) returns (RevokeAllMyRefreshTokensResponse) {
|
rpc RevokeAllMyRefreshTokens(RevokeAllMyRefreshTokensRequest) returns (RevokeAllMyRefreshTokensResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/users/me/tokens/refresh/_revoke_all"
|
post: "/users/me/tokens/refresh/_revoke_all"
|
||||||
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
@ -592,6 +667,61 @@ message ListMyUserSessionsResponse {
|
|||||||
repeated zitadel.user.v1.Session result = 1;
|
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
|
//This is an empty request
|
||||||
message ListMyRefreshTokensRequest {}
|
message ListMyRefreshTokensRequest {}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import "zitadel/message.proto";
|
|||||||
import "zitadel/change.proto";
|
import "zitadel/change.proto";
|
||||||
import "zitadel/auth_n_key.proto";
|
import "zitadel/auth_n_key.proto";
|
||||||
import "zitadel/features.proto";
|
import "zitadel/features.proto";
|
||||||
|
import "zitadel/metadata.proto";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.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
|
// Returns the profile of the human
|
||||||
rpc GetHumanProfile(GetHumanProfileRequest) returns (GetHumanProfileResponse) {
|
rpc GetHumanProfile(GetHumanProfileRequest) returns (GetHumanProfileResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
@ -2827,6 +2904,68 @@ message UpdateUserNameResponse {
|
|||||||
zitadel.v1.ObjectDetails details = 1;
|
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 {
|
message GetHumanProfileRequest {
|
||||||
string user_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
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