mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:27:32 +00:00
feat: actions (#2377)
* feat(actions): begin api * feat(actions): begin api * api and projections * fix: handle multiple statements for a single event in projections * export func type * fix test * update to new reduce interface * flows in login * feat: jwt idp * feat: command side * feat: add tests * actions and flows * fill idp views with jwt idps and return apis * add jwtEndpoint to jwt idp * begin jwt request handling * add feature * merge * merge * handle jwt idp * cleanup * bug fixes * autoregister * get token from specific header name * fix: proto * fixes * i18n * begin tests * fix and log http proxy * remove docker cache * fixes * usergrants in actions api * tests adn cleanup * cleanup * fix add user grant * set login context * i18n Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
@@ -92,6 +92,10 @@ protoc \
|
|||||||
-I=/proto/include \
|
-I=/proto/include \
|
||||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,admin.md \
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,admin.md \
|
||||||
${PROTO_PATH}/admin.proto
|
${PROTO_PATH}/admin.proto
|
||||||
|
protoc \
|
||||||
|
-I=/proto/include \
|
||||||
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,action.md \
|
||||||
|
${PROTO_PATH}/action.proto
|
||||||
protoc \
|
protoc \
|
||||||
-I=/proto/include \
|
-I=/proto/include \
|
||||||
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,app.md \
|
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,app.md \
|
||||||
|
@@ -15,6 +15,12 @@ InternalAuthZ:
|
|||||||
- "iam.idp.read"
|
- "iam.idp.read"
|
||||||
- "iam.idp.write"
|
- "iam.idp.write"
|
||||||
- "iam.idp.delete"
|
- "iam.idp.delete"
|
||||||
|
- "iam.action.read"
|
||||||
|
- "iam.action.write"
|
||||||
|
- "iam.action.delete"
|
||||||
|
- "iam.flow.read"
|
||||||
|
- "iam.flow.write"
|
||||||
|
- "iam.flow.delete"
|
||||||
- "org.read"
|
- "org.read"
|
||||||
- "org.global.read"
|
- "org.global.read"
|
||||||
- "org.create"
|
- "org.create"
|
||||||
@@ -25,6 +31,12 @@ InternalAuthZ:
|
|||||||
- "org.idp.read"
|
- "org.idp.read"
|
||||||
- "org.idp.write"
|
- "org.idp.write"
|
||||||
- "org.idp.delete"
|
- "org.idp.delete"
|
||||||
|
- "org.action.read"
|
||||||
|
- "org.action.write"
|
||||||
|
- "org.action.delete"
|
||||||
|
- "org.flow.read"
|
||||||
|
- "org.flow.write"
|
||||||
|
- "org.flow.delete"
|
||||||
- "user.read"
|
- "user.read"
|
||||||
- "user.global.read"
|
- "user.global.read"
|
||||||
- "user.write"
|
- "user.write"
|
||||||
@@ -63,9 +75,13 @@ InternalAuthZ:
|
|||||||
- "iam.policy.read"
|
- "iam.policy.read"
|
||||||
- "iam.member.read"
|
- "iam.member.read"
|
||||||
- "iam.idp.read"
|
- "iam.idp.read"
|
||||||
|
- "iam.action.read"
|
||||||
|
- "iam.flow.read"
|
||||||
- "org.read"
|
- "org.read"
|
||||||
- "org.member.read"
|
- "org.member.read"
|
||||||
- "org.idp.read"
|
- "org.idp.read"
|
||||||
|
- "org.action.read"
|
||||||
|
- "org.flow.read"
|
||||||
- "user.read"
|
- "user.read"
|
||||||
- "user.global.read"
|
- "user.global.read"
|
||||||
- "user.grant.read"
|
- "user.grant.read"
|
||||||
@@ -90,6 +106,12 @@ InternalAuthZ:
|
|||||||
- "org.idp.read"
|
- "org.idp.read"
|
||||||
- "org.idp.write"
|
- "org.idp.write"
|
||||||
- "org.idp.delete"
|
- "org.idp.delete"
|
||||||
|
- "org.action.read"
|
||||||
|
- "org.action.write"
|
||||||
|
- "org.action.delete"
|
||||||
|
- "org.flow.read"
|
||||||
|
- "org.flow.write"
|
||||||
|
- "org.flow.delete"
|
||||||
- "user.read"
|
- "user.read"
|
||||||
- "user.global.read"
|
- "user.global.read"
|
||||||
- "user.write"
|
- "user.write"
|
||||||
@@ -125,6 +147,8 @@ InternalAuthZ:
|
|||||||
- "org.read"
|
- "org.read"
|
||||||
- "org.member.read"
|
- "org.member.read"
|
||||||
- "org.idp.read"
|
- "org.idp.read"
|
||||||
|
- "org.action.read"
|
||||||
|
- "org.flow.read"
|
||||||
- "user.read"
|
- "user.read"
|
||||||
- "user.global.read"
|
- "user.global.read"
|
||||||
- "user.grant.read"
|
- "user.grant.read"
|
||||||
|
@@ -145,6 +145,11 @@ func startZitadel(configPaths []string) {
|
|||||||
err := config.Read(conf, configPaths...)
|
err := config.Read(conf, configPaths...)
|
||||||
logging.Log("ZITAD-EDz31").OnError(err).Fatal("cannot read config")
|
logging.Log("ZITAD-EDz31").OnError(err).Fatal("cannot read config")
|
||||||
|
|
||||||
|
logging.LogWithFields("MAIN-dsfg2",
|
||||||
|
"HTTP_PROXY", os.Getenv("HTTP_PROXY") != "",
|
||||||
|
"HTTPS_PROXY", os.Getenv("HTTPS_PROXY") != "",
|
||||||
|
"NO_PROXY", os.Getenv("NO_PROXY")).Info("http proxy settings")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
esQueries, err := eventstore.StartWithUser(conf.EventstoreBase, conf.Queries.Eventstore)
|
esQueries, err := eventstore.StartWithUser(conf.EventstoreBase, conf.Queries.Eventstore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
188
docs/docs/apis/proto/action.md
Normal file
188
docs/docs/apis/proto/action.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
title: zitadel/action.proto
|
||||||
|
---
|
||||||
|
> This document reflects the state from API 1.0 (available from 20.04.2021)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Messages
|
||||||
|
|
||||||
|
|
||||||
|
### Action
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
| state | ActionState | - | |
|
||||||
|
| name | string | - | |
|
||||||
|
| script | string | - | |
|
||||||
|
| timeout | google.protobuf.Duration | - | |
|
||||||
|
| allowed_to_fail | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ActionIDQuery
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | string.max_len: 200<br /> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ActionNameQuery
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| name | string | - | string.max_len: 200<br /> |
|
||||||
|
| method | zitadel.v1.TextQueryMethod | - | enum.defined_only: true<br /> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ActionStateQuery
|
||||||
|
ActionStateQuery is always equals
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| state | ActionState | - | enum.defined_only: true<br /> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Flow
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| type | FlowType | - | |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
| state | FlowState | - | |
|
||||||
|
| trigger_actions | repeated TriggerAction | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### FlowStateQuery
|
||||||
|
FlowStateQuery is always equals
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| state | FlowState | - | enum.defined_only: true<br /> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### FlowTypeQuery
|
||||||
|
FlowTypeQuery is always equals
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| state | FlowType | - | enum.defined_only: true<br /> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### TriggerAction
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| trigger_type | TriggerType | - | |
|
||||||
|
| actions | repeated Action | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Enums
|
||||||
|
|
||||||
|
|
||||||
|
### ActionFieldName {#actionfieldname}
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Number | Description |
|
||||||
|
| ---- | ------ | ----------- |
|
||||||
|
| ACTION_FIELD_NAME_UNSPECIFIED | 0 | - |
|
||||||
|
| ACTION_FIELD_NAME_NAME | 1 | - |
|
||||||
|
| ACTION_FIELD_NAME_ID | 2 | - |
|
||||||
|
| ACTION_FIELD_NAME_STATE | 3 | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ActionState {#actionstate}
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Number | Description |
|
||||||
|
| ---- | ------ | ----------- |
|
||||||
|
| ACTION_STATE_UNSPECIFIED | 0 | - |
|
||||||
|
| ACTION_STATE_INACTIVE | 1 | - |
|
||||||
|
| ACTION_STATE_ACTIVE | 2 | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### FlowFieldName {#flowfieldname}
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Number | Description |
|
||||||
|
| ---- | ------ | ----------- |
|
||||||
|
| FLOW_FIELD_NAME_UNSPECIFIED | 0 | - |
|
||||||
|
| FLOW_FIELD_NAME_TYPE | 1 | - |
|
||||||
|
| FLOW_FIELD_NAME_STATE | 2 | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### FlowState {#flowstate}
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Number | Description |
|
||||||
|
| ---- | ------ | ----------- |
|
||||||
|
| FLOW_STATE_UNSPECIFIED | 0 | - |
|
||||||
|
| FLOW_STATE_INACTIVE | 1 | - |
|
||||||
|
| FLOW_STATE_ACTIVE | 2 | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### FlowType {#flowtype}
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Number | Description |
|
||||||
|
| ---- | ------ | ----------- |
|
||||||
|
| FLOW_TYPE_UNSPECIFIED | 0 | - |
|
||||||
|
| FLOW_TYPE_EXTERNAL_AUTHENTICATION | 1 | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### TriggerType {#triggertype}
|
||||||
|
|
||||||
|
|
||||||
|
| Name | Number | Description |
|
||||||
|
| ---- | ------ | ----------- |
|
||||||
|
| TRIGGER_TYPE_UNSPECIFIED | 0 | - |
|
||||||
|
| TRIGGER_TYPE_POST_AUTHENTICATION | 1 | - |
|
||||||
|
| TRIGGER_TYPE_PRE_CREATION | 2 | - |
|
||||||
|
| TRIGGER_TYPE_POST_CREATION | 3 | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -2563,6 +2563,7 @@ This is an empty request
|
|||||||
| custom_text_message | bool | - | |
|
| custom_text_message | bool | - | |
|
||||||
| custom_text_login | bool | - | |
|
| custom_text_login | bool | - | |
|
||||||
| lockout_policy | bool | - | |
|
| lockout_policy | bool | - | |
|
||||||
|
| actions | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2752,6 +2753,7 @@ This is an empty request
|
|||||||
| custom_text_message | bool | - | |
|
| custom_text_message | bool | - | |
|
||||||
| custom_text_login | bool | - | |
|
| custom_text_login | bool | - | |
|
||||||
| lockout_policy | bool | - | |
|
| lockout_policy | bool | - | |
|
||||||
|
| actions | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2683,6 +2683,102 @@ Change JWT identity provider configuration of the organisation
|
|||||||
PUT: /idps/{idp_id}/jwt_config
|
PUT: /idps/{idp_id}/jwt_config
|
||||||
|
|
||||||
|
|
||||||
|
### ListActions
|
||||||
|
|
||||||
|
> **rpc** ListActions([ListActionsRequest](#listactionsrequest))
|
||||||
|
[ListActionsResponse](#listactionsresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /actions/_search
|
||||||
|
|
||||||
|
|
||||||
|
### GetAction
|
||||||
|
|
||||||
|
> **rpc** GetAction([GetActionRequest](#getactionrequest))
|
||||||
|
[GetActionResponse](#getactionresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GET: /actions/{id}
|
||||||
|
|
||||||
|
|
||||||
|
### CreateAction
|
||||||
|
|
||||||
|
> **rpc** CreateAction([CreateActionRequest](#createactionrequest))
|
||||||
|
[CreateActionResponse](#createactionresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /actions
|
||||||
|
|
||||||
|
|
||||||
|
### UpdateAction
|
||||||
|
|
||||||
|
> **rpc** UpdateAction([UpdateActionRequest](#updateactionrequest))
|
||||||
|
[UpdateActionResponse](#updateactionresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PUT: /actions/{id}
|
||||||
|
|
||||||
|
|
||||||
|
### DeleteAction
|
||||||
|
|
||||||
|
> **rpc** DeleteAction([DeleteActionRequest](#deleteactionrequest))
|
||||||
|
[DeleteActionResponse](#deleteactionresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DELETE: /actions/{id}
|
||||||
|
|
||||||
|
|
||||||
|
### GetFlow
|
||||||
|
|
||||||
|
> **rpc** GetFlow([GetFlowRequest](#getflowrequest))
|
||||||
|
[GetFlowResponse](#getflowresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GET: /flows/{type}
|
||||||
|
|
||||||
|
|
||||||
|
### ClearFlow
|
||||||
|
|
||||||
|
> **rpc** ClearFlow([ClearFlowRequest](#clearflowrequest))
|
||||||
|
[ClearFlowResponse](#clearflowresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /flows/{type}/_clear
|
||||||
|
|
||||||
|
|
||||||
|
### SetTriggerActions
|
||||||
|
|
||||||
|
> **rpc** SetTriggerActions([SetTriggerActionsRequest](#settriggeractionsrequest))
|
||||||
|
[SetTriggerActionsResponse](#settriggeractionsresponse)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /flows/{flow_type}/trigger/{trigger_type}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2691,6 +2787,19 @@ Change JWT identity provider configuration of the organisation
|
|||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
|
|
||||||
|
### ActionQuery
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.action_id_query | zitadel.action.v1.ActionIDQuery | - | |
|
||||||
|
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.action_name_query | zitadel.action.v1.ActionNameQuery | - | |
|
||||||
|
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.action_state_query | zitadel.action.v1.ActionStateQuery | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ActivateCustomLabelPolicyRequest
|
### ActivateCustomLabelPolicyRequest
|
||||||
This is an empty request
|
This is an empty request
|
||||||
|
|
||||||
@@ -3532,6 +3641,76 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ClearFlowRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| type | zitadel.action.v1.FlowType | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ClearFlowResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### CreateActionRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||||
|
| script | string | - | string.min_len: 1<br /> string.max_len: 2000<br /> |
|
||||||
|
| timeout | google.protobuf.Duration | - | duration.lte.seconds: 20<br /> duration.lte.nanos: 0<br /> duration.gte.seconds: 0<br /> duration.gte.nanos: 0<br /> |
|
||||||
|
| allowed_to_fail | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### CreateActionResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
| id | string | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### DeactivateActionRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### DeactivateActionResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### DeactivateAppRequest
|
### DeactivateAppRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -3684,6 +3863,23 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### DeleteActionRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### DeleteActionResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### GenerateOrgDomainValidationRequest
|
### GenerateOrgDomainValidationRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -3708,6 +3904,28 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### GetActionRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### GetActionResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| action | zitadel.action.v1.Action | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### GetAppByIDRequest
|
### GetAppByIDRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -4182,6 +4400,28 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### GetFlowRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| type | zitadel.action.v1.FlowType | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### GetFlowResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| flow | zitadel.action.v1.Flow | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### GetGrantedProjectByIDRequest
|
### GetGrantedProjectByIDRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -4818,6 +5058,32 @@ This is an empty response
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ListActionsRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| query | zitadel.v1.ListQuery | list limitations and ordering | |
|
||||||
|
| sorting_column | zitadel.action.v1.ActionFieldName | the field the result is sorted | |
|
||||||
|
| queries | repeated ActionQuery | criteria the client is looking for | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ListActionsResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ListDetails | - | |
|
||||||
|
| sorting_column | zitadel.action.v1.ActionFieldName | - | |
|
||||||
|
| result | repeated zitadel.action.v1.Action | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ListAppChangesRequest
|
### ListAppChangesRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -5545,6 +5811,28 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ReactivateActionRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ReactivateActionResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ReactivateAppRequest
|
### ReactivateAppRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -7013,6 +7301,30 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### SetTriggerActionsRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| flow_type | zitadel.action.v1.FlowType | - | |
|
||||||
|
| trigger_type | zitadel.action.v1.TriggerType | - | |
|
||||||
|
| action_ids | repeated string | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### SetTriggerActionsResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### SetUserMetadataRequest
|
### SetUserMetadataRequest
|
||||||
|
|
||||||
|
|
||||||
@@ -7084,6 +7396,32 @@ This is an empty request
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### UpdateActionRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||||
|
| name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||||
|
| script | string | - | string.min_len: 1<br /> string.max_len: 2000<br /> |
|
||||||
|
| timeout | google.protobuf.Duration | - | duration.lte.seconds: 20<br /> duration.lte.nanos: 0<br /> duration.gte.seconds: 0<br /> duration.gte.nanos: 0<br /> |
|
||||||
|
| allowed_to_fail | bool | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### UpdateActionResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Description | Validation |
|
||||||
|
| ----- | ---- | ----------- | ----------- |
|
||||||
|
| details | zitadel.v1.ObjectDetails | - | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### UpdateAppRequest
|
### UpdateAppRequest
|
||||||
|
|
||||||
|
|
||||||
|
3
go.mod
3
go.mod
@@ -11,6 +11,7 @@ require (
|
|||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||||
|
github.com/Masterminds/squirrel v1.5.0
|
||||||
github.com/VictoriaMetrics/fastcache v1.7.0
|
github.com/VictoriaMetrics/fastcache v1.7.0
|
||||||
github.com/ajstarks/svgo v0.0.0-20210406150507-75cfd577ce75
|
github.com/ajstarks/svgo v0.0.0-20210406150507-75cfd577ce75
|
||||||
github.com/allegro/bigcache v1.2.1
|
github.com/allegro/bigcache v1.2.1
|
||||||
@@ -19,6 +20,8 @@ require (
|
|||||||
github.com/caos/oidc v0.15.10
|
github.com/caos/oidc v0.15.10
|
||||||
github.com/caos/orbos v1.5.14-0.20210803090517-905668247c09
|
github.com/caos/orbos v1.5.14-0.20210803090517-905668247c09
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.1.1
|
github.com/cockroachdb/cockroach-go/v2 v2.1.1
|
||||||
|
github.com/dop251/goja v0.0.0-20210817151038-07a7fd9355b4
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7
|
||||||
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc
|
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.6.1
|
github.com/envoyproxy/protoc-gen-validate v0.6.1
|
||||||
github.com/getsentry/sentry-go v0.11.0
|
github.com/getsentry/sentry-go v0.11.0
|
||||||
|
20
go.sum
20
go.sum
@@ -94,6 +94,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q
|
|||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
|
github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
|
||||||
|
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||||
@@ -231,12 +233,18 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
|||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
|
||||||
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
|
github.com/dop251/goja v0.0.0-20210817151038-07a7fd9355b4 h1:c6+6EmiSboC79bkEtZGQhpeEWP1lzHuHpr1tPdISdYo=
|
||||||
|
github.com/dop251/goja v0.0.0-20210817151038-07a7fd9355b4/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 h1:tYwu/z8Y0NkkzGEh3z21mSWggMg4LwLRFucLS7TjARg=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||||
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc h1:mLNknBMRNrYNf16wFFUyhSAe1tISZN7oAfal4CZ2OxY=
|
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc h1:mLNknBMRNrYNf16wFFUyhSAe1tISZN7oAfal4CZ2OxY=
|
||||||
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc/go.mod h1:/X2OJiJxjQ7alqWZqX9EtBTmZc+4qQ0LvZ1k5wP67RM=
|
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc/go.mod h1:/X2OJiJxjQ7alqWZqX9EtBTmZc+4qQ0LvZ1k5wP67RM=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
@@ -379,6 +387,8 @@ github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85n
|
|||||||
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
@@ -690,6 +700,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||||
@@ -701,6 +713,10 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/landoop/tableprinter v0.0.0-20200805134727-ea32388e35c1/go.mod h1:f0X1c0za3TbET/rl5ThtCSel0+G3/yZ8iuU9BxnyVK0=
|
github.com/landoop/tableprinter v0.0.0-20200805134727-ea32388e35c1/go.mod h1:f0X1c0za3TbET/rl5ThtCSel0+G3/yZ8iuU9BxnyVK0=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
@@ -816,7 +832,6 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
|
|||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
|
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
|
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
@@ -1621,8 +1636,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
112
internal/actions/actions.go
Normal file
112
internal/actions/actions.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/dop251/goja_nodejs/console"
|
||||||
|
"github.com/dop251/goja_nodejs/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrHalt = errors.New("interrupt")
|
||||||
|
|
||||||
|
type jsAction func(*Context, *API) error
|
||||||
|
|
||||||
|
func Run(ctx *Context, api *API, script, name string, timeout time.Duration, allowedToFail bool) error {
|
||||||
|
if timeout <= 0 || timeout > 20 {
|
||||||
|
timeout = 20 * time.Second
|
||||||
|
}
|
||||||
|
prepareTimeout := timeout
|
||||||
|
if prepareTimeout > 5 {
|
||||||
|
prepareTimeout = 5 * time.Second
|
||||||
|
}
|
||||||
|
vm, err := prepareRun(script, prepareTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var fn jsAction
|
||||||
|
jsFn := vm.Get(name)
|
||||||
|
if jsFn == nil {
|
||||||
|
return errors.New("function not found")
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(jsFn, &fn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t := setInterrupt(vm, timeout)
|
||||||
|
defer func() {
|
||||||
|
t.Stop()
|
||||||
|
}()
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
if r != nil && !allowedToFail {
|
||||||
|
err, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
e, ok := r.(string)
|
||||||
|
if ok {
|
||||||
|
err = errors.New(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
err = fn(ctx, api)
|
||||||
|
if err != nil && !allowedToFail {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errCh <- nil
|
||||||
|
}()
|
||||||
|
return <-errCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuntime() *goja.Runtime {
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
printer := console.PrinterFunc(func(s string) {
|
||||||
|
logging.Log("ACTIONS-dfgg2").Debug(s)
|
||||||
|
})
|
||||||
|
registry := new(require.Registry)
|
||||||
|
registry.Enable(vm)
|
||||||
|
registry.RegisterNativeModule("console", console.RequireWithPrinter(printer))
|
||||||
|
console.Enable(vm)
|
||||||
|
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareRun(script string, timeout time.Duration) (*goja.Runtime, error) {
|
||||||
|
vm := newRuntime()
|
||||||
|
t := setInterrupt(vm, timeout)
|
||||||
|
defer func() {
|
||||||
|
t.Stop()
|
||||||
|
}()
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
if r != nil {
|
||||||
|
errCh <- r.(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err := vm.RunString(script)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errCh <- nil
|
||||||
|
}()
|
||||||
|
return vm, <-errCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInterrupt(vm *goja.Runtime, timeout time.Duration) *time.Timer {
|
||||||
|
vm.ClearInterrupt()
|
||||||
|
return time.AfterFunc(timeout, func() {
|
||||||
|
vm.Interrupt(ErrHalt)
|
||||||
|
})
|
||||||
|
}
|
105
internal/actions/api.go
Normal file
105
internal/actions/api.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API map[string]interface{}
|
||||||
|
|
||||||
|
func (a API) set(name string, value interface{}) {
|
||||||
|
map[string]interface{}(a)[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) SetHuman(human *domain.Human) *API {
|
||||||
|
a.set("setFirstName", func(firstName string) {
|
||||||
|
human.FirstName = firstName
|
||||||
|
})
|
||||||
|
a.set("setLastName", func(lastName string) {
|
||||||
|
human.LastName = lastName
|
||||||
|
})
|
||||||
|
a.set("setNickName", func(nickName string) {
|
||||||
|
human.NickName = nickName
|
||||||
|
})
|
||||||
|
a.set("setDisplayName", func(displayName string) {
|
||||||
|
human.DisplayName = displayName
|
||||||
|
})
|
||||||
|
a.set("setPreferredLanguage", func(preferredLanguage string) {
|
||||||
|
human.PreferredLanguage = language.Make(preferredLanguage)
|
||||||
|
})
|
||||||
|
a.set("setGender", func(gender domain.Gender) {
|
||||||
|
human.Gender = gender
|
||||||
|
})
|
||||||
|
a.set("setUsername", func(username string) {
|
||||||
|
human.Username = username
|
||||||
|
})
|
||||||
|
a.set("setEmail", func(email string) {
|
||||||
|
if human.Email == nil {
|
||||||
|
human.Email = &domain.Email{}
|
||||||
|
}
|
||||||
|
human.Email.EmailAddress = email
|
||||||
|
})
|
||||||
|
a.set("setEmailVerified", func(verified bool) {
|
||||||
|
if human.Email == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
human.Email.IsEmailVerified = verified
|
||||||
|
})
|
||||||
|
a.set("setPhone", func(email string) {
|
||||||
|
if human.Phone == nil {
|
||||||
|
human.Phone = &domain.Phone{}
|
||||||
|
}
|
||||||
|
human.Phone.PhoneNumber = email
|
||||||
|
})
|
||||||
|
a.set("setPhoneVerified", func(verified bool) {
|
||||||
|
if human.Phone == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
human.Phone.IsPhoneVerified = verified
|
||||||
|
})
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) SetExternalUser(user *domain.ExternalUser) *API {
|
||||||
|
a.set("setFirstName", func(firstName string) {
|
||||||
|
user.FirstName = firstName
|
||||||
|
})
|
||||||
|
a.set("setLastName", func(lastName string) {
|
||||||
|
user.LastName = lastName
|
||||||
|
})
|
||||||
|
a.set("setNickName", func(nickName string) {
|
||||||
|
user.NickName = nickName
|
||||||
|
})
|
||||||
|
a.set("setDisplayName", func(displayName string) {
|
||||||
|
user.DisplayName = displayName
|
||||||
|
})
|
||||||
|
a.set("setPreferredLanguage", func(preferredLanguage string) {
|
||||||
|
user.PreferredLanguage = language.Make(preferredLanguage)
|
||||||
|
})
|
||||||
|
a.set("setPreferredUsername", func(username string) {
|
||||||
|
user.PreferredUsername = username
|
||||||
|
})
|
||||||
|
a.set("setEmail", func(email string) {
|
||||||
|
user.Email = email
|
||||||
|
})
|
||||||
|
a.set("setEmailVerified", func(verified bool) {
|
||||||
|
user.IsEmailVerified = verified
|
||||||
|
})
|
||||||
|
a.set("setPhone", func(phone string) {
|
||||||
|
user.Phone = phone
|
||||||
|
})
|
||||||
|
a.set("setPhoneVerified", func(verified bool) {
|
||||||
|
user.IsPhoneVerified = verified
|
||||||
|
})
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) SetMetadata(metadata *[]*domain.Metadata) *API {
|
||||||
|
a.set("metadata", metadata)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) SetUserGrants(usergrants *[]UserGrant) *API {
|
||||||
|
a.set("userGrants", usergrants)
|
||||||
|
return a
|
||||||
|
}
|
33
internal/actions/context.go
Normal file
33
internal/actions/context.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context map[string]interface{}
|
||||||
|
|
||||||
|
func (c Context) set(name string, value interface{}) {
|
||||||
|
map[string]interface{}(c)[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetToken(t *oidc.Tokens) *Context {
|
||||||
|
if t.Token != nil && t.Token.AccessToken != "" {
|
||||||
|
c.set("accessToken", t.AccessToken)
|
||||||
|
}
|
||||||
|
if t.IDToken != "" {
|
||||||
|
c.set("idToken", t.IDToken)
|
||||||
|
}
|
||||||
|
if t.IDTokenClaims != nil {
|
||||||
|
c.set("getClaim", func(claim string) interface{} { return t.IDTokenClaims.GetClaim(claim) })
|
||||||
|
c.set("claimsJSON", func() (string, error) {
|
||||||
|
c, err := json.Marshal(t.IDTokenClaims)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(c), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
55
internal/actions/provided.go
Normal file
55
internal/actions/provided.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrant struct {
|
||||||
|
ProjectID string
|
||||||
|
ProjectGrantID string
|
||||||
|
Roles []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUserGrant(list *[]UserGrant) func(goja.FunctionCall) goja.Value {
|
||||||
|
return func(call goja.FunctionCall) goja.Value {
|
||||||
|
userGrantMap := call.Argument(0).Export()
|
||||||
|
userGrant, _ := userGrantFromMap(userGrantMap)
|
||||||
|
*list = append(*list, userGrant)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userGrantFromMap(grantMap interface{}) (UserGrant, error) {
|
||||||
|
m, ok := grantMap.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return UserGrant{}, errors.New("invalid")
|
||||||
|
}
|
||||||
|
projectID, ok := m["projectID"].(string)
|
||||||
|
if !ok {
|
||||||
|
return UserGrant{}, errors.New("invalid")
|
||||||
|
}
|
||||||
|
var projectGrantID string
|
||||||
|
if id, ok := m["projectGrantID"]; ok {
|
||||||
|
projectGrantID, ok = id.(string)
|
||||||
|
if !ok {
|
||||||
|
return UserGrant{}, errors.New("invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var roles []string
|
||||||
|
if r := m["roles"]; r != nil {
|
||||||
|
rs, ok := r.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return UserGrant{}, errors.New("invalid")
|
||||||
|
}
|
||||||
|
for _, role := range rs {
|
||||||
|
roles = append(roles, role.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UserGrant{
|
||||||
|
ProjectID: projectID,
|
||||||
|
ProjectGrantID: projectGrantID,
|
||||||
|
Roles: roles,
|
||||||
|
}, nil
|
||||||
|
}
|
126
internal/api/grpc/action/action.go
Normal file
126
internal/api/grpc/action/action.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
object_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/query"
|
||||||
|
action_pb "github.com/caos/zitadel/pkg/grpc/action"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FlowTypeToDomain(flowType action_pb.FlowType) domain.FlowType {
|
||||||
|
switch flowType {
|
||||||
|
case action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION:
|
||||||
|
return domain.FlowTypeExternalAuthentication
|
||||||
|
default:
|
||||||
|
return domain.FlowTypeUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerTypeToDomain(triggerType action_pb.TriggerType) domain.TriggerType {
|
||||||
|
switch triggerType {
|
||||||
|
case action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION:
|
||||||
|
return domain.TriggerTypePostAuthentication
|
||||||
|
case action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION:
|
||||||
|
return domain.TriggerTypePreCreation
|
||||||
|
case action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION:
|
||||||
|
return domain.TriggerTypePostCreation
|
||||||
|
default:
|
||||||
|
return domain.TriggerTypeUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlowToPb(flow *query.Flow) *action_pb.Flow {
|
||||||
|
return &action_pb.Flow{
|
||||||
|
Type: FlowTypeToPb(flow.Type),
|
||||||
|
Details: object_grpc.ChangeToDetailsPb(flow.Sequence, flow.ChangeDate, flow.ResourceOwner),
|
||||||
|
State: action_pb.FlowState_FLOW_STATE_ACTIVE, //TODO: state in next release
|
||||||
|
TriggerActions: TriggerActionsToPb(flow.TriggerActions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerActionToPb(trigger domain.TriggerType, actions []*query.Action) *action_pb.TriggerAction {
|
||||||
|
return &action_pb.TriggerAction{
|
||||||
|
TriggerType: TriggerTypeToPb(trigger),
|
||||||
|
Actions: ActionsToPb(actions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlowTypeToPb(flowType domain.FlowType) action_pb.FlowType {
|
||||||
|
switch flowType {
|
||||||
|
case domain.FlowTypeExternalAuthentication:
|
||||||
|
return action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION
|
||||||
|
default:
|
||||||
|
return action_pb.FlowType_FLOW_TYPE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerTypeToPb(triggerType domain.TriggerType) action_pb.TriggerType {
|
||||||
|
switch triggerType {
|
||||||
|
case domain.TriggerTypePostAuthentication:
|
||||||
|
return action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION
|
||||||
|
case domain.TriggerTypePreCreation:
|
||||||
|
return action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION
|
||||||
|
case domain.TriggerTypePostCreation:
|
||||||
|
return action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION
|
||||||
|
default:
|
||||||
|
return action_pb.TriggerType_TRIGGER_TYPE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerActionsToPb(triggers map[domain.TriggerType][]*query.Action) []*action_pb.TriggerAction {
|
||||||
|
list := make([]*action_pb.TriggerAction, 0)
|
||||||
|
for trigger, actions := range triggers {
|
||||||
|
list = append(list, TriggerActionToPb(trigger, actions))
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionsToPb(actions []*query.Action) []*action_pb.Action {
|
||||||
|
list := make([]*action_pb.Action, len(actions))
|
||||||
|
for i, action := range actions {
|
||||||
|
list[i] = ActionToPb(action)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionToPb(action *query.Action) *action_pb.Action {
|
||||||
|
return &action_pb.Action{
|
||||||
|
Id: action.ID,
|
||||||
|
Details: object_grpc.ChangeToDetailsPb(action.Sequence, action.ChangeDate, action.ResourceOwner),
|
||||||
|
State: ActionStateToPb(action.State),
|
||||||
|
Name: action.Name,
|
||||||
|
Script: action.Script,
|
||||||
|
Timeout: durationpb.New(action.Timeout),
|
||||||
|
AllowedToFail: action.AllowedToFail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionStateToPb(state domain.ActionState) action_pb.ActionState {
|
||||||
|
switch state {
|
||||||
|
case domain.ActionStateActive:
|
||||||
|
return action_pb.ActionState_ACTION_STATE_ACTIVE
|
||||||
|
case domain.ActionStateInactive:
|
||||||
|
return action_pb.ActionState_ACTION_STATE_INACTIVE
|
||||||
|
default:
|
||||||
|
return action_pb.ActionState_ACTION_STATE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionNameQuery(q *action_pb.ActionNameQuery) (query.SearchQuery, error) {
|
||||||
|
return query.NewActionNameSearchQuery(object_grpc.TextMethodToQuery(q.Method), q.Name)
|
||||||
|
}
|
||||||
|
func ActionStateQuery(q *action_pb.ActionStateQuery) (query.SearchQuery, error) {
|
||||||
|
return query.NewActionStateSearchQuery(ActionStateToDomain(q.State))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionStateToDomain(state action_pb.ActionState) domain.ActionState {
|
||||||
|
switch state {
|
||||||
|
case action_pb.ActionState_ACTION_STATE_ACTIVE:
|
||||||
|
return domain.ActionStateActive
|
||||||
|
case action_pb.ActionState_ACTION_STATE_INACTIVE:
|
||||||
|
return domain.ActionStateInactive
|
||||||
|
default:
|
||||||
|
return domain.ActionStateUnspecified
|
||||||
|
}
|
||||||
|
}
|
@@ -79,6 +79,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
|
|||||||
CustomTextLogin: req.CustomTextLogin || req.CustomText,
|
CustomTextLogin: req.CustomTextLogin || req.CustomText,
|
||||||
CustomTextMessage: req.CustomTextMessage,
|
CustomTextMessage: req.CustomTextMessage,
|
||||||
LockoutPolicy: req.LockoutPolicy,
|
LockoutPolicy: req.LockoutPolicy,
|
||||||
|
Actions: req.Actions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,5 +105,6 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
|
|||||||
CustomTextLogin: req.CustomTextLogin || req.CustomText,
|
CustomTextLogin: req.CustomTextLogin || req.CustomText,
|
||||||
CustomTextMessage: req.CustomTextMessage,
|
CustomTextMessage: req.CustomTextMessage,
|
||||||
LockoutPolicy: req.LockoutPolicy,
|
LockoutPolicy: req.LockoutPolicy,
|
||||||
|
Actions: req.Actions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,7 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
|
|||||||
CustomTextLogin: features.CustomTextLogin,
|
CustomTextLogin: features.CustomTextLogin,
|
||||||
MetadataUser: features.MetadataUser,
|
MetadataUser: features.MetadataUser,
|
||||||
LockoutPolicy: features.LockoutPolicy,
|
LockoutPolicy: features.LockoutPolicy,
|
||||||
|
Actions: features.Actions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
94
internal/api/grpc/management/actions.go
Normal file
94
internal/api/grpc/management/actions.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package management
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
action_grpc "github.com/caos/zitadel/internal/api/grpc/action"
|
||||||
|
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||||
|
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) ListActions(ctx context.Context, req *mgmt_pb.ListActionsRequest) (*mgmt_pb.ListActionsResponse, error) {
|
||||||
|
query, _ := listActionsToQuery(authz.GetCtxData(ctx).OrgID, req)
|
||||||
|
actions, err := s.query.SearchActions(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.ListActionsResponse{
|
||||||
|
Result: action_grpc.ActionsToPb(actions),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetAction(ctx context.Context, req *mgmt_pb.GetActionRequest) (*mgmt_pb.GetActionResponse, error) {
|
||||||
|
action, err := s.query.GetAction(ctx, req.Id, authz.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.GetActionResponse{
|
||||||
|
Action: action_grpc.ActionToPb(action),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) CreateAction(ctx context.Context, req *mgmt_pb.CreateActionRequest) (*mgmt_pb.CreateActionResponse, error) {
|
||||||
|
id, details, err := s.command.AddAction(ctx, createActionRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.CreateActionResponse{
|
||||||
|
Id: id,
|
||||||
|
Details: obj_grpc.AddToDetailsPb(
|
||||||
|
details.Sequence,
|
||||||
|
details.EventDate,
|
||||||
|
details.ResourceOwner,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateAction(ctx context.Context, req *mgmt_pb.UpdateActionRequest) (*mgmt_pb.UpdateActionResponse, error) {
|
||||||
|
details, err := s.command.ChangeAction(ctx, updateActionRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.UpdateActionResponse{
|
||||||
|
Details: obj_grpc.AddToDetailsPb(
|
||||||
|
details.Sequence,
|
||||||
|
details.EventDate,
|
||||||
|
details.ResourceOwner,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeactivateAction(ctx context.Context, req *mgmt_pb.DeactivateActionRequest) (*mgmt_pb.DeactivateActionResponse, error) {
|
||||||
|
details, err := s.command.DeactivateAction(ctx, req.Id, authz.GetCtxData(ctx).OrgID)
|
||||||
|
return &mgmt_pb.DeactivateActionResponse{
|
||||||
|
Details: obj_grpc.AddToDetailsPb(
|
||||||
|
details.Sequence,
|
||||||
|
details.EventDate,
|
||||||
|
details.ResourceOwner,
|
||||||
|
),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ReactivateAction(ctx context.Context, req *mgmt_pb.ReactivateActionRequest) (*mgmt_pb.ReactivateActionResponse, error) {
|
||||||
|
details, err := s.command.ReactivateAction(ctx, req.Id, authz.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.ReactivateActionResponse{
|
||||||
|
Details: obj_grpc.AddToDetailsPb(
|
||||||
|
details.Sequence,
|
||||||
|
details.EventDate,
|
||||||
|
details.ResourceOwner,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeleteAction(ctx context.Context, req *mgmt_pb.DeleteActionRequest) (*mgmt_pb.DeleteActionResponse, error) {
|
||||||
|
flowTypes, err := s.query.GetFlowTypesOfActionID(ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = s.command.DeleteAction(ctx, req.Id, authz.GetCtxData(ctx).OrgID, flowTypes...)
|
||||||
|
return &mgmt_pb.DeleteActionResponse{}, err
|
||||||
|
}
|
64
internal/api/grpc/management/actions_converter.go
Normal file
64
internal/api/grpc/management/actions_converter.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package management
|
||||||
|
|
||||||
|
import (
|
||||||
|
action_grpc "github.com/caos/zitadel/internal/api/grpc/action"
|
||||||
|
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/caos/zitadel/internal/query"
|
||||||
|
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createActionRequestToDomain(req *mgmt_pb.CreateActionRequest) *domain.Action {
|
||||||
|
return &domain.Action{
|
||||||
|
Name: req.Name,
|
||||||
|
Script: req.Script,
|
||||||
|
Timeout: req.Timeout.AsDuration(),
|
||||||
|
AllowedToFail: req.AllowedToFail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateActionRequestToDomain(req *mgmt_pb.UpdateActionRequest) *domain.Action {
|
||||||
|
return &domain.Action{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: req.Id,
|
||||||
|
},
|
||||||
|
Name: req.Name,
|
||||||
|
Script: req.Script,
|
||||||
|
Timeout: req.Timeout.AsDuration(),
|
||||||
|
AllowedToFail: req.AllowedToFail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listActionsToQuery(id string, req *mgmt_pb.ListActionsRequest) (_ *query.ActionSearchQueries, err error) {
|
||||||
|
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||||
|
queries := make([]query.SearchQuery, len(req.Queries)+1)
|
||||||
|
queries[0], err = query.NewActionResourceOwnerQuery(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, actionQuery := range req.Queries {
|
||||||
|
queries[i+1], err = ActionQueryToQuery(actionQuery.Query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &query.ActionSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
},
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionQueryToQuery(query interface{}) (query.SearchQuery, error) {
|
||||||
|
switch q := query.(type) {
|
||||||
|
case *mgmt_pb.ActionQuery_ActionNameQuery:
|
||||||
|
return action_grpc.ActionNameQuery(q.ActionNameQuery)
|
||||||
|
case *mgmt_pb.ActionQuery_ActionStateQuery:
|
||||||
|
return action_grpc.ActionStateQuery(q.ActionStateQuery)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
32
internal/api/grpc/management/auth_checks.go
Normal file
32
internal/api/grpc/management/auth_checks.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package management
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkExplicitProjectPermission(ctx context.Context, grantID, projectID string) error {
|
||||||
|
permissions := authz.GetRequestPermissionsFromCtx(ctx)
|
||||||
|
if authz.HasGlobalPermission(permissions) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ids := authz.GetAllPermissionCtxIDs(permissions)
|
||||||
|
if grantID != "" && listContainsID(ids, grantID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if listContainsID(ids, projectID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return caos_errors.ThrowPermissionDenied(nil, "EVENT-Shu7e", "Errors.UserGrant.NoPermissionForProject")
|
||||||
|
}
|
||||||
|
|
||||||
|
func listContainsID(ids []string, id string) bool {
|
||||||
|
for _, i := range ids {
|
||||||
|
if i == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
50
internal/api/grpc/management/flow.go
Normal file
50
internal/api/grpc/management/flow.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package management
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
action_grpc "github.com/caos/zitadel/internal/api/grpc/action"
|
||||||
|
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||||
|
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) GetFlow(ctx context.Context, req *mgmt_pb.GetFlowRequest) (*mgmt_pb.GetFlowResponse, error) {
|
||||||
|
flow, err := s.query.GetFlow(ctx, action_grpc.FlowTypeToDomain(req.Type))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.GetFlowResponse{
|
||||||
|
Flow: action_grpc.FlowToPb(flow),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ClearFlow(ctx context.Context, req *mgmt_pb.ClearFlowRequest) (*mgmt_pb.ClearFlowResponse, error) {
|
||||||
|
details, err := s.command.ClearFlow(ctx, action_grpc.FlowTypeToDomain(req.Type), authz.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.ClearFlowResponse{
|
||||||
|
Details: obj_grpc.DomainToChangeDetailsPb(details),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SetTriggerActions(ctx context.Context, req *mgmt_pb.SetTriggerActionsRequest) (*mgmt_pb.SetTriggerActionsResponse, error) {
|
||||||
|
details, err := s.command.SetTriggerActions(
|
||||||
|
ctx,
|
||||||
|
action_grpc.FlowTypeToDomain(req.FlowType),
|
||||||
|
action_grpc.TriggerTypeToDomain(req.TriggerType),
|
||||||
|
req.ActionIds,
|
||||||
|
authz.GetCtxData(ctx).OrgID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mgmt_pb.SetTriggerActionsResponse{
|
||||||
|
Details: obj_grpc.AddToDetailsPb(
|
||||||
|
details.Sequence,
|
||||||
|
details.EventDate,
|
||||||
|
details.ResourceOwner,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package management
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
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"
|
||||||
@@ -37,7 +38,11 @@ func (s *Server) ListUserGrants(ctx context.Context, req *mgmt_pb.ListUserGrantR
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AddUserGrant(ctx context.Context, req *mgmt_pb.AddUserGrantRequest) (*mgmt_pb.AddUserGrantResponse, error) {
|
func (s *Server) AddUserGrant(ctx context.Context, req *mgmt_pb.AddUserGrantRequest) (*mgmt_pb.AddUserGrantResponse, error) {
|
||||||
grant, err := s.command.AddUserGrant(ctx, AddUserGrantRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
grant := AddUserGrantRequestToDomain(req)
|
||||||
|
if err := checkExplicitProjectPermission(ctx, grant.ProjectGrantID, grant.ProjectID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
grant, err := s.command.AddUserGrant(ctx, grant, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/query"
|
||||||
"github.com/caos/zitadel/pkg/grpc/object"
|
"github.com/caos/zitadel/pkg/grpc/object"
|
||||||
object_pb "github.com/caos/zitadel/pkg/grpc/object"
|
object_pb "github.com/caos/zitadel/pkg/grpc/object"
|
||||||
)
|
)
|
||||||
@@ -105,3 +106,26 @@ func ListQueryToModel(query *object_pb.ListQuery) (offset, limit uint64, asc boo
|
|||||||
}
|
}
|
||||||
return query.Offset, uint64(query.Limit), query.Asc
|
return query.Offset, uint64(query.Limit), query.Asc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TextMethodToQuery(method object_pb.TextQueryMethod) query.TextComparison {
|
||||||
|
switch method {
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS:
|
||||||
|
return query.TextEquals
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE:
|
||||||
|
return query.TextEqualsIgnore
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH:
|
||||||
|
return query.TextStartsWith
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE:
|
||||||
|
return query.TextStartsWithIgnore
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS:
|
||||||
|
return query.TextContains
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE:
|
||||||
|
return query.TextContainsIgnore
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH:
|
||||||
|
return query.TextEndsWith
|
||||||
|
case object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE:
|
||||||
|
return query.TextEndsWithIgnore
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -106,7 +106,7 @@ func (g *GatewayHandler) Serve(ctx context.Context) {
|
|||||||
func createGateway(ctx context.Context, g Gateway, port string, customHeaders ...string) http.Handler {
|
func createGateway(ctx context.Context, g Gateway, port string, customHeaders ...string) http.Handler {
|
||||||
mux := createMux(g, customHeaders...)
|
mux := createMux(g, customHeaders...)
|
||||||
opts := createDialOptions(g)
|
opts := createDialOptions(g)
|
||||||
err := g.RegisterGateway()(ctx, mux, http_util.Endpoint(port), opts)
|
err := g.RegisterGateway()(ctx, mux, "localhost"+http_util.Endpoint(port), opts)
|
||||||
logging.Log("SERVE-7B7G0E").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("failed to register grpc gateway")
|
logging.Log("SERVE-7B7G0E").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("failed to register grpc gateway")
|
||||||
return addInterceptors(mux, g)
|
return addInterceptors(mux, g)
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,6 +32,6 @@ type AuthRequestRepository interface {
|
|||||||
VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
|
VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
|
||||||
|
|
||||||
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error
|
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error
|
||||||
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, info *domain.BrowserInfo) error
|
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error
|
||||||
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
|
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
@@ -404,7 +403,7 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u
|
|||||||
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, info *domain.BrowserInfo) (err error) {
|
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) (err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
|
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
|
||||||
@@ -423,6 +422,12 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(metadatas) > 0 {
|
||||||
|
_, err = repo.Command.BulkSetUserMetadata(ctx, request.UserID, request.UserOrgID, metadatas...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -181,6 +181,12 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if requiredFeature == domain.FeatureActions {
|
||||||
|
if !features.Actions {
|
||||||
|
return MissingFeatureErr(requiredFeature)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
return MissingFeatureErr(requiredFeature)
|
return MissingFeatureErr(requiredFeature)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/config/types"
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/http"
|
"github.com/caos/zitadel/internal/api/http"
|
||||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
@@ -78,6 +79,7 @@ func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults
|
|||||||
usr_grant_repo.RegisterEventMappers(repo.eventstore)
|
usr_grant_repo.RegisterEventMappers(repo.eventstore)
|
||||||
proj_repo.RegisterEventMappers(repo.eventstore)
|
proj_repo.RegisterEventMappers(repo.eventstore)
|
||||||
keypair.RegisterEventMappers(repo.eventstore)
|
keypair.RegisterEventMappers(repo.eventstore)
|
||||||
|
action.RegisterEventMappers(repo.eventstore)
|
||||||
|
|
||||||
repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey)
|
repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -31,6 +31,7 @@ type FeaturesWriteModel struct {
|
|||||||
CustomTextMessage bool
|
CustomTextMessage bool
|
||||||
CustomTextLogin bool
|
CustomTextLogin bool
|
||||||
LockoutPolicy bool
|
LockoutPolicy bool
|
||||||
|
Actions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *FeaturesWriteModel) Reduce() error {
|
func (wm *FeaturesWriteModel) Reduce() error {
|
||||||
@@ -98,6 +99,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
|
|||||||
if e.LockoutPolicy != nil {
|
if e.LockoutPolicy != nil {
|
||||||
wm.LockoutPolicy = *e.LockoutPolicy
|
wm.LockoutPolicy = *e.LockoutPolicy
|
||||||
}
|
}
|
||||||
|
if e.Actions != nil {
|
||||||
|
wm.Actions = *e.Actions
|
||||||
|
}
|
||||||
case *features.FeaturesRemovedEvent:
|
case *features.FeaturesRemovedEvent:
|
||||||
wm.State = domain.FeaturesStateRemoved
|
wm.State = domain.FeaturesStateRemoved
|
||||||
}
|
}
|
||||||
|
52
internal/command/flow_model.go
Normal file
52
internal/command/flow_model.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/repository/flow"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlowWriteModel struct {
|
||||||
|
eventstore.WriteModel
|
||||||
|
|
||||||
|
FlowType domain.FlowType
|
||||||
|
State domain.FlowState
|
||||||
|
Triggers map[domain.TriggerType][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlowWriteModel(flowType domain.FlowType, resourceOwner string) *FlowWriteModel {
|
||||||
|
return &FlowWriteModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
AggregateID: resourceOwner,
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
},
|
||||||
|
FlowType: flowType,
|
||||||
|
Triggers: make(map[domain.TriggerType][]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *FlowWriteModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *flow.TriggerActionsSetEvent:
|
||||||
|
if wm.Triggers == nil {
|
||||||
|
wm.Triggers = make(map[domain.TriggerType][]string)
|
||||||
|
}
|
||||||
|
wm.Triggers[e.TriggerType] = e.ActionIDs
|
||||||
|
case *flow.TriggerActionsCascadeRemovedEvent:
|
||||||
|
remove(wm.Triggers[e.TriggerType], e.ActionID)
|
||||||
|
case *flow.FlowClearedEvent:
|
||||||
|
wm.Triggers = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.WriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(ids []string, id string) {
|
||||||
|
for i := 0; i < len(ids); i++ {
|
||||||
|
if ids[i] == id {
|
||||||
|
ids = append(ids[:i], ids[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -54,6 +54,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
|
|||||||
features.CustomTextMessage,
|
features.CustomTextMessage,
|
||||||
features.CustomTextLogin,
|
features.CustomTextLogin,
|
||||||
features.LockoutPolicy,
|
features.LockoutPolicy,
|
||||||
|
features.Actions,
|
||||||
)
|
)
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||||
|
@@ -71,7 +71,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
|||||||
metadataUser,
|
metadataUser,
|
||||||
customTextMessage,
|
customTextMessage,
|
||||||
customTextLogin,
|
customTextLogin,
|
||||||
lockoutPolicy bool,
|
lockoutPolicy,
|
||||||
|
actions bool,
|
||||||
) (*iam.FeaturesSetEvent, bool) {
|
) (*iam.FeaturesSetEvent, bool) {
|
||||||
|
|
||||||
changes := make([]features.FeaturesChanges, 0)
|
changes := make([]features.FeaturesChanges, 0)
|
||||||
@@ -133,6 +134,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
|||||||
if wm.LockoutPolicy != lockoutPolicy {
|
if wm.LockoutPolicy != lockoutPolicy {
|
||||||
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
|
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
|
||||||
}
|
}
|
||||||
|
if wm.Actions != actions {
|
||||||
|
changes = append(changes, features.ChangeActions(actions))
|
||||||
|
}
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository/mock"
|
"github.com/caos/zitadel/internal/eventstore/repository/mock"
|
||||||
|
action_repo "github.com/caos/zitadel/internal/repository/action"
|
||||||
iam_repo "github.com/caos/zitadel/internal/repository/iam"
|
iam_repo "github.com/caos/zitadel/internal/repository/iam"
|
||||||
key_repo "github.com/caos/zitadel/internal/repository/keypair"
|
key_repo "github.com/caos/zitadel/internal/repository/keypair"
|
||||||
"github.com/caos/zitadel/internal/repository/org"
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
@@ -35,6 +36,7 @@ func eventstoreExpect(t *testing.T, expects ...expect) *eventstore.Eventstore {
|
|||||||
proj_repo.RegisterEventMappers(es)
|
proj_repo.RegisterEventMappers(es)
|
||||||
usergrant.RegisterEventMappers(es)
|
usergrant.RegisterEventMappers(es)
|
||||||
key_repo.RegisterEventMappers(es)
|
key_repo.RegisterEventMappers(es)
|
||||||
|
action_repo.RegisterEventMappers(es)
|
||||||
return es
|
return es
|
||||||
}
|
}
|
||||||
|
|
||||||
|
200
internal/command/org_action.go
Normal file
200
internal/command/org_action.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
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/action"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Commands) AddAction(ctx context.Context, addAction *domain.Action, resourceOwner string) (_ string, _ *domain.ObjectDetails, err error) {
|
||||||
|
if !addAction.IsValid() {
|
||||||
|
return "", nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-eg2gf", "Errors.Action.Invalid")
|
||||||
|
}
|
||||||
|
addAction.AggregateID, err = c.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
actionModel := NewActionWriteModel(addAction.AggregateID, resourceOwner)
|
||||||
|
actionAgg := ActionAggregateFromWriteModel(&actionModel.WriteModel)
|
||||||
|
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, action.NewAddedEvent(
|
||||||
|
ctx,
|
||||||
|
actionAgg,
|
||||||
|
addAction.Name,
|
||||||
|
addAction.Script,
|
||||||
|
addAction.Timeout,
|
||||||
|
addAction.AllowedToFail,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(actionModel, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return actionModel.AggregateID, writeModelToObjectDetails(&actionModel.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) ChangeAction(ctx context.Context, actionChange *domain.Action, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if !actionChange.IsValid() || actionChange.AggregateID == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Df2f3", "Errors.Action.Invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAction, err := c.getActionWriteModelByID(ctx, actionChange.AggregateID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existingAction.State.Exists() {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Sfg2t", "Errors.Action.NotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||||
|
changedEvent, err := existingAction.NewChangedEvent(
|
||||||
|
ctx,
|
||||||
|
actionAgg,
|
||||||
|
actionChange.Name,
|
||||||
|
actionChange.Script,
|
||||||
|
actionChange.Timeout,
|
||||||
|
actionChange.AllowedToFail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) DeactivateAction(ctx context.Context, actionID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if actionID == "" || resourceOwner == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-DAhk5", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existingAction.State.Exists() {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-NRmhu", "Errors.Action.NotFound")
|
||||||
|
}
|
||||||
|
if existingAction.State != domain.ActionStateActive {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Dgj92", "Errors.Action.NotActive")
|
||||||
|
}
|
||||||
|
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||||
|
events := []eventstore.EventPusher{
|
||||||
|
action.NewDeactivatedEvent(ctx, actionAgg),
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) ReactivateAction(ctx context.Context, actionID string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if actionID == "" || resourceOwner == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-BNm56", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existingAction.State.Exists() {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Aa22g", "Errors.Action.NotFound")
|
||||||
|
}
|
||||||
|
if existingAction.State != domain.ActionStateInactive {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J53zh", "Errors.Action.NotInactive")
|
||||||
|
}
|
||||||
|
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||||
|
events := []eventstore.EventPusher{
|
||||||
|
action.NewReactivatedEvent(ctx, actionAgg),
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) DeleteAction(ctx context.Context, actionID, resourceOwner string, flowTypes ...domain.FlowType) (*domain.ObjectDetails, error) {
|
||||||
|
if actionID == "" || resourceOwner == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Gfg3g", "Errors.IDMissing")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existingAction.State.Exists() {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Dgh4h", "Errors.Action.NotFound")
|
||||||
|
}
|
||||||
|
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
|
||||||
|
events := []eventstore.EventPusher{
|
||||||
|
action.NewRemovedEvent(ctx, actionAgg, existingAction.Name),
|
||||||
|
}
|
||||||
|
orgAgg := org.NewAggregate(resourceOwner, resourceOwner).Aggregate
|
||||||
|
for _, flowType := range flowTypes {
|
||||||
|
events = append(events, org.NewTriggerActionsCascadeRemovedEvent(ctx, &orgAgg, flowType, actionID))
|
||||||
|
}
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existingAction, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existingAction.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) removeActionsFromOrg(ctx context.Context, resourceOwner string) ([]eventstore.EventPusher, error) {
|
||||||
|
existingActions, err := c.getActionsByOrgWriteModelByID(ctx, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(existingActions.Actions) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
events := make([]eventstore.EventPusher, 0, len(existingActions.Actions))
|
||||||
|
for id, name := range existingActions.Actions {
|
||||||
|
actionAgg := NewActionAggregate(id, resourceOwner)
|
||||||
|
events = append(events, action.NewRemovedEvent(ctx, actionAgg, name))
|
||||||
|
}
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) getActionWriteModelByID(ctx context.Context, actionID string, resourceOwner string) (*ActionWriteModel, error) {
|
||||||
|
actionWriteModel := NewActionWriteModel(actionID, resourceOwner)
|
||||||
|
err := c.eventstore.FilterToQueryReducer(ctx, actionWriteModel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actionWriteModel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) getActionsByOrgWriteModelByID(ctx context.Context, resourceOwner string) (*ActionsListByOrgModel, error) {
|
||||||
|
actionWriteModel := NewActionsListByOrgModel(resourceOwner)
|
||||||
|
err := c.eventstore.FilterToQueryReducer(ctx, actionWriteModel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actionWriteModel, nil
|
||||||
|
}
|
194
internal/command/org_action_model.go
Normal file
194
internal/command/org_action_model.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionWriteModel struct {
|
||||||
|
eventstore.WriteModel
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Script string
|
||||||
|
Timeout time.Duration
|
||||||
|
AllowedToFail bool
|
||||||
|
State domain.ActionState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionWriteModel(actionID string, resourceOwner string) *ActionWriteModel {
|
||||||
|
return &ActionWriteModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
AggregateID: actionID,
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionWriteModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *action.AddedEvent:
|
||||||
|
wm.Name = e.Name
|
||||||
|
wm.Script = e.Script
|
||||||
|
wm.Timeout = e.Timeout
|
||||||
|
wm.AllowedToFail = e.AllowedToFail
|
||||||
|
wm.State = domain.ActionStateActive
|
||||||
|
case *action.ChangedEvent:
|
||||||
|
if e.Name != nil {
|
||||||
|
wm.Name = *e.Name
|
||||||
|
}
|
||||||
|
if e.Script != nil {
|
||||||
|
wm.Script = *e.Script
|
||||||
|
}
|
||||||
|
if e.Timeout != nil {
|
||||||
|
wm.Timeout = *e.Timeout
|
||||||
|
}
|
||||||
|
if e.AllowedToFail != nil {
|
||||||
|
wm.AllowedToFail = *e.AllowedToFail
|
||||||
|
}
|
||||||
|
case *action.DeactivatedEvent:
|
||||||
|
wm.State = domain.ActionStateInactive
|
||||||
|
case *action.ReactivatedEvent:
|
||||||
|
wm.State = domain.ActionStateActive
|
||||||
|
case *action.RemovedEvent:
|
||||||
|
wm.State = domain.ActionStateRemoved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.WriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
ResourceOwner(wm.ResourceOwner).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(action.AggregateType).
|
||||||
|
AggregateIDs(wm.AggregateID).
|
||||||
|
EventTypes(action.AddedEventType,
|
||||||
|
action.ChangedEventType,
|
||||||
|
action.DeactivatedEventType,
|
||||||
|
action.ReactivatedEventType,
|
||||||
|
action.RemovedEventType).
|
||||||
|
Builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionWriteModel) NewChangedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
agg *eventstore.Aggregate,
|
||||||
|
name string,
|
||||||
|
script string,
|
||||||
|
timeout time.Duration,
|
||||||
|
allowedToFail bool,
|
||||||
|
) (*action.ChangedEvent, error) {
|
||||||
|
changes := make([]action.ActionChanges, 0)
|
||||||
|
if wm.Name != name {
|
||||||
|
changes = append(changes, action.ChangeName(name, wm.Name))
|
||||||
|
}
|
||||||
|
if wm.Script != script {
|
||||||
|
changes = append(changes, action.ChangeScript(script))
|
||||||
|
}
|
||||||
|
if wm.Timeout != timeout {
|
||||||
|
changes = append(changes, action.ChangeTimeout(timeout))
|
||||||
|
}
|
||||||
|
if wm.AllowedToFail != allowedToFail {
|
||||||
|
changes = append(changes, action.ChangeAllowedToFail(allowedToFail))
|
||||||
|
}
|
||||||
|
return action.NewChangedEvent(ctx, agg, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||||
|
return eventstore.AggregateFromWriteModel(wm, action.AggregateType, action.AggregateVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionAggregate(id, resourceOwner string) *eventstore.Aggregate {
|
||||||
|
return ActionAggregateFromWriteModel(&eventstore.WriteModel{
|
||||||
|
AggregateID: id,
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionExistsModel struct {
|
||||||
|
eventstore.WriteModel
|
||||||
|
|
||||||
|
actionIDs []string
|
||||||
|
checkedIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionsExistModel(actionIDs []string, resourceOwner string) *ActionExistsModel {
|
||||||
|
return &ActionExistsModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
},
|
||||||
|
actionIDs: actionIDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionExistsModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *action.AddedEvent:
|
||||||
|
wm.checkedIDs = append(wm.checkedIDs, e.Aggregate().ID)
|
||||||
|
case *action.RemovedEvent:
|
||||||
|
for i := len(wm.checkedIDs) - 1; i >= 0; i-- {
|
||||||
|
if wm.checkedIDs[i] == e.Aggregate().ID {
|
||||||
|
wm.checkedIDs[i] = wm.checkedIDs[len(wm.checkedIDs)-1]
|
||||||
|
wm.checkedIDs[len(wm.checkedIDs)-1] = ""
|
||||||
|
wm.checkedIDs = wm.checkedIDs[:len(wm.checkedIDs)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.WriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionExistsModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
ResourceOwner(wm.ResourceOwner).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(action.AggregateType).
|
||||||
|
AggregateIDs(wm.actionIDs...).
|
||||||
|
EventTypes(action.AddedEventType,
|
||||||
|
action.RemovedEventType).
|
||||||
|
Builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionsListByOrgModel struct {
|
||||||
|
eventstore.WriteModel
|
||||||
|
|
||||||
|
Actions map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionsListByOrgModel(resourceOwner string) *ActionsListByOrgModel {
|
||||||
|
return &ActionsListByOrgModel{
|
||||||
|
WriteModel: eventstore.WriteModel{
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
},
|
||||||
|
Actions: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionsListByOrgModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *action.AddedEvent:
|
||||||
|
wm.Actions[e.Aggregate().ID] = e.Name
|
||||||
|
case *action.RemovedEvent:
|
||||||
|
delete(wm.Actions, e.Aggregate().ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.WriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *ActionsListByOrgModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
ResourceOwner(wm.ResourceOwner).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(action.AggregateType).
|
||||||
|
EventTypes(action.AddedEventType,
|
||||||
|
action.RemovedEventType).
|
||||||
|
Builder()
|
||||||
|
}
|
791
internal/command/org_action_test.go
Normal file
791
internal/command/org_action_test.go
Normal file
@@ -0,0 +1,791 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"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/id"
|
||||||
|
"github.com/caos/zitadel/internal/id/mock"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommands_AddAction(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
idGenerator id.Generator
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
addAction *domain.Action
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
id string
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no name, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
addAction: &domain.Action{
|
||||||
|
Script: "test()",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unique constraint failed, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectPushFailed(
|
||||||
|
errors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name", "org1")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
addAction: &domain.Action{
|
||||||
|
Name: "name",
|
||||||
|
Script: "name() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name", "org1")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.ExpectID(t, "id1"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
addAction: &domain.Action{
|
||||||
|
Name: "name",
|
||||||
|
Script: "name() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
id: "id1",
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
idGenerator: tt.fields.idGenerator,
|
||||||
|
}
|
||||||
|
id, details, err := c.AddAction(tt.args.ctx, tt.args.addAction, tt.args.resourceOwner)
|
||||||
|
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.id, id)
|
||||||
|
assert.Equal(t, tt.res.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_ChangeAction(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
changeAction *domain.Action
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"id missing, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
changeAction: &domain.Action{
|
||||||
|
Name: "name",
|
||||||
|
Script: "name() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not found, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
changeAction: &domain.Action{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Script: "name() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no changes, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
changeAction: &domain.Action{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Script: "name() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unique constraint failed, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPushFailed(
|
||||||
|
errors.ThrowPreconditionFailed(nil, "id", "name already exists"),
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
func() *action.ChangedEvent {
|
||||||
|
event, _ := action.NewChangedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
[]action.ActionChanges{
|
||||||
|
action.ChangeName("name2", "name"),
|
||||||
|
action.ChangeScript("name2() {};"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return event
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name2", "org1")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
changeAction: &domain.Action{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: "name2",
|
||||||
|
Script: "name2() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"push ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
func() *action.ChangedEvent {
|
||||||
|
event, _ := action.NewChangedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
[]action.ActionChanges{
|
||||||
|
action.ChangeName("name2", "name"),
|
||||||
|
action.ChangeScript("name2() {};"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return event
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name2", "org1")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
changeAction: &domain.Action{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "id1",
|
||||||
|
},
|
||||||
|
Name: "name2",
|
||||||
|
Script: "name2() {};",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.ChangeAction(tt.args.ctx, tt.args.changeAction, tt.args.resourceOwner)
|
||||||
|
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.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_DeactivateAction(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
actionID string
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"id missing, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not found, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not active, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewDeactivatedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"deactivate ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewDeactivatedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.DeactivateAction(tt.args.ctx, tt.args.actionID, tt.args.resourceOwner)
|
||||||
|
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.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_ReactivateAction(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
actionID string
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"id missing, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not found, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not inactive, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reactivate ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewDeactivatedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewReactivatedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
actionID: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.ReactivateAction(tt.args.ctx, tt.args.actionID, tt.args.resourceOwner)
|
||||||
|
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.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_DeleteAction(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
id string
|
||||||
|
resourceOwner string
|
||||||
|
flowTypes []domain.FlowType
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"id or resourceOwner emtpy, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "",
|
||||||
|
resourceOwner: "",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action not found, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
err: errors.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"remove ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewRemovedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"remove with used action ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
"name() {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
[]*repository.Event{
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewRemovedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("id1", "org1").Aggregate,
|
||||||
|
"name",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewTriggerActionsCascadeRemovedEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
|
domain.FlowTypeExternalAuthentication,
|
||||||
|
"id1",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
id: "id1",
|
||||||
|
resourceOwner: "org1",
|
||||||
|
flowTypes: []domain.FlowType{
|
||||||
|
domain.FlowTypeExternalAuthentication,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.DeleteAction(tt.args.ctx, tt.args.id, tt.args.resourceOwner, tt.args.flowTypes...)
|
||||||
|
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.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -45,6 +45,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
|
|||||||
features.CustomTextMessage,
|
features.CustomTextMessage,
|
||||||
features.CustomTextLogin,
|
features.CustomTextLogin,
|
||||||
features.LockoutPolicy,
|
features.LockoutPolicy,
|
||||||
|
features.Actions,
|
||||||
)
|
)
|
||||||
if !hasChanged {
|
if !hasChanged {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||||
@@ -176,6 +177,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
|
|||||||
events = append(events, removeOrgUserMetadatas...)
|
events = append(events, removeOrgUserMetadatas...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !features.Actions {
|
||||||
|
removeOrgActions, err := c.removeActionsFromOrg(ctx, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(removeOrgActions) > 0 {
|
||||||
|
events = append(events, removeOrgActions...)
|
||||||
|
}
|
||||||
|
}
|
||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -78,7 +78,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
|||||||
metadataUser,
|
metadataUser,
|
||||||
customTextMessage,
|
customTextMessage,
|
||||||
customTextLogin,
|
customTextLogin,
|
||||||
lockoutPolicy bool,
|
lockoutPolicy,
|
||||||
|
actions bool,
|
||||||
) (*org.FeaturesSetEvent, bool) {
|
) (*org.FeaturesSetEvent, bool) {
|
||||||
|
|
||||||
changes := make([]features.FeaturesChanges, 0)
|
changes := make([]features.FeaturesChanges, 0)
|
||||||
@@ -143,6 +144,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
|||||||
if wm.LockoutPolicy != lockoutPolicy {
|
if wm.LockoutPolicy != lockoutPolicy {
|
||||||
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
|
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
|
||||||
}
|
}
|
||||||
|
if wm.Actions != actions {
|
||||||
|
changes = append(changes, features.ChangeActions(actions))
|
||||||
|
}
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@@ -275,6 +275,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@@ -307,6 +308,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
MetadataUser: false,
|
MetadataUser: false,
|
||||||
LockoutPolicy: false,
|
LockoutPolicy: false,
|
||||||
|
Actions: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -472,6 +474,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@@ -509,6 +512,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
MetadataUser: false,
|
MetadataUser: false,
|
||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
LockoutPolicy: false,
|
LockoutPolicy: false,
|
||||||
|
Actions: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -681,6 +685,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@@ -721,6 +726,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
MetadataUser: false,
|
MetadataUser: false,
|
||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
LockoutPolicy: false,
|
LockoutPolicy: false,
|
||||||
|
Actions: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -900,6 +906,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@@ -943,6 +950,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
MetadataUser: false,
|
MetadataUser: false,
|
||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
LockoutPolicy: false,
|
LockoutPolicy: false,
|
||||||
|
Actions: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -1174,6 +1182,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@@ -1234,6 +1243,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
MetadataUser: false,
|
MetadataUser: false,
|
||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
LockoutPolicy: false,
|
LockoutPolicy: false,
|
||||||
|
Actions: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -1387,6 +1397,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@@ -1422,6 +1433,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
|||||||
PrivacyPolicy: false,
|
PrivacyPolicy: false,
|
||||||
MetadataUser: false,
|
MetadataUser: false,
|
||||||
LockoutPolicy: false,
|
LockoutPolicy: false,
|
||||||
|
Actions: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
@@ -1635,6 +1647,7 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
|
83
internal/command/org_flow.go
Normal file
83
internal/command/org_flow.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Commands) ClearFlow(ctx context.Context, flowType domain.FlowType, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if !flowType.Valid() || resourceOwner == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfw2h", "Errors.Flow.FlowTypeMissing")
|
||||||
|
}
|
||||||
|
existingFlow, err := c.getOrgFlowWriteModelByType(ctx, flowType, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(existingFlow.Triggers) == 0 {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-DgGh3", "Errors.Flow.Empty")
|
||||||
|
}
|
||||||
|
orgAgg := OrgAggregateFromWriteModel(&existingFlow.WriteModel)
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewFlowClearedEvent(ctx, orgAgg, flowType))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existingFlow, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existingFlow.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) SetTriggerActions(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType, actionIDs []string, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
if !flowType.Valid() || !triggerType.Valid() || resourceOwner == "" {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfhj5", "Errors.Flow.FlowTypeMissing")
|
||||||
|
}
|
||||||
|
if !flowType.HasTrigger(triggerType) {
|
||||||
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfgh6", "Errors.Flow.WrongTriggerType")
|
||||||
|
}
|
||||||
|
existingFlow, err := c.getOrgFlowWriteModelByType(ctx, flowType, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(existingFlow.Triggers[triggerType], actionIDs) {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Nfh52", "Errors.Flow.NoChanges")
|
||||||
|
}
|
||||||
|
if len(actionIDs) > 0 {
|
||||||
|
exists, err := c.actionsIDsExist(ctx, actionIDs, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-dg422", "Errors.Flow.ActionIDsNotExist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orgAgg := OrgAggregateFromWriteModel(&existingFlow.WriteModel)
|
||||||
|
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewTriggerActionsSetEvent(ctx, orgAgg, flowType, triggerType, actionIDs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = AppendAndReduce(existingFlow, pushedEvents...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModelToObjectDetails(&existingFlow.WriteModel), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) getOrgFlowWriteModelByType(ctx context.Context, flowType domain.FlowType, resourceOwner string) (*OrgFlowWriteModel, error) {
|
||||||
|
flowWriteModel := NewOrgFlowWriteModel(flowType, resourceOwner)
|
||||||
|
err := c.eventstore.FilterToQueryReducer(ctx, flowWriteModel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return flowWriteModel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) actionsIDsExist(ctx context.Context, ids []string, resourceOwner string) (bool, error) {
|
||||||
|
actionIDsModel := NewActionsExistModel(ids, resourceOwner)
|
||||||
|
err := c.eventstore.FilterToQueryReducer(ctx, actionIDsModel)
|
||||||
|
return len(actionIDsModel.actionIDs) == len(actionIDsModel.checkedIDs), err
|
||||||
|
}
|
54
internal/command/org_flow_model.go
Normal file
54
internal/command/org_flow_model.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrgFlowWriteModel struct {
|
||||||
|
FlowWriteModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrgFlowWriteModel(flowType domain.FlowType, resourceOwner string) *OrgFlowWriteModel {
|
||||||
|
return &OrgFlowWriteModel{
|
||||||
|
FlowWriteModel: *NewFlowWriteModel(flowType, resourceOwner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *OrgFlowWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *org.TriggerActionsSetEvent:
|
||||||
|
if e.FlowType != wm.FlowType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wm.FlowWriteModel.AppendEvents(&e.TriggerActionsSetEvent)
|
||||||
|
case *org.TriggerActionsCascadeRemovedEvent:
|
||||||
|
if e.FlowType != wm.FlowType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wm.FlowWriteModel.AppendEvents(&e.TriggerActionsCascadeRemovedEvent)
|
||||||
|
case *org.FlowClearedEvent:
|
||||||
|
if e.FlowType != wm.FlowType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wm.FlowWriteModel.AppendEvents(&e.FlowClearedEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *OrgFlowWriteModel) Reduce() error {
|
||||||
|
return wm.FlowWriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *OrgFlowWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
ResourceOwner(wm.ResourceOwner).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(org.AggregateType).
|
||||||
|
EventTypes(org.TriggerActionsSetEventType,
|
||||||
|
org.TriggerActionsCascadeRemovedEventType,
|
||||||
|
org.FlowClearedEventType).
|
||||||
|
Builder()
|
||||||
|
}
|
286
internal/command/org_flow_test.go
Normal file
286
internal/command/org_flow_test.go
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommands_ClearFlow(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
flowType domain.FlowType
|
||||||
|
resourceOwner string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"invalid flow type, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeUnspecified,
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: nil,
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"already empty, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeExternalAuthentication,
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: nil,
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clear ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewTriggerActionsSetEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
|
domain.FlowTypeExternalAuthentication,
|
||||||
|
domain.TriggerTypePostAuthentication,
|
||||||
|
[]string{"actionID1"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
eventPusherToEvents(
|
||||||
|
org.NewFlowClearedEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
|
domain.FlowTypeExternalAuthentication,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeExternalAuthentication,
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.ClearFlow(tt.args.ctx, tt.args.flowType, tt.args.resourceOwner)
|
||||||
|
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.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_SetTriggerActions(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
flowType domain.FlowType
|
||||||
|
resourceOwner string
|
||||||
|
triggerType domain.TriggerType
|
||||||
|
actionIDs []string
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
details *domain.ObjectDetails
|
||||||
|
err func(error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"invalid flow type, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeUnspecified,
|
||||||
|
triggerType: domain.TriggerTypePostAuthentication,
|
||||||
|
actionIDs: []string{"actionID1"},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: nil,
|
||||||
|
err: errors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//TODO: combination not possible at the moment, add when more flow types available
|
||||||
|
//{
|
||||||
|
// "impossible flow / trigger type, error",
|
||||||
|
// fields{
|
||||||
|
// eventstore: eventstoreExpect(t,),
|
||||||
|
// },
|
||||||
|
// args{
|
||||||
|
// ctx: context.Background(),
|
||||||
|
// flowType: domain.FlowTypeUnspecified,
|
||||||
|
// triggerType: domain.TriggerTypePostAuthentication,
|
||||||
|
// actionIDs: []string{"actionID1"},
|
||||||
|
// resourceOwner: "org1",
|
||||||
|
// },
|
||||||
|
// res{
|
||||||
|
// details: nil,
|
||||||
|
// err: errors.IsErrorInvalidArgument,
|
||||||
|
// },
|
||||||
|
//},
|
||||||
|
{
|
||||||
|
"no changes, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
org.NewTriggerActionsSetEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
|
domain.FlowTypeExternalAuthentication,
|
||||||
|
domain.TriggerTypePostAuthentication,
|
||||||
|
[]string{"actionID1"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeExternalAuthentication,
|
||||||
|
triggerType: domain.TriggerTypePostAuthentication,
|
||||||
|
actionIDs: []string{"actionID1"},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: nil,
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"actionID not exists, error",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeExternalAuthentication,
|
||||||
|
triggerType: domain.TriggerTypePostAuthentication,
|
||||||
|
actionIDs: []string{"actionID1"},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: nil,
|
||||||
|
err: errors.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"set ok",
|
||||||
|
fields{
|
||||||
|
eventstore: eventstoreExpect(t,
|
||||||
|
expectFilter(),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
action.NewAddedEvent(context.Background(),
|
||||||
|
&action.NewAggregate("action1", "org1").Aggregate,
|
||||||
|
"actionID1",
|
||||||
|
"function(ctx, api) action {};",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
eventPusherToEvents(
|
||||||
|
org.NewTriggerActionsSetEvent(context.Background(),
|
||||||
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
|
domain.FlowTypeExternalAuthentication,
|
||||||
|
domain.TriggerTypePostAuthentication,
|
||||||
|
[]string{"actionID1"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
flowType: domain.FlowTypeExternalAuthentication,
|
||||||
|
triggerType: domain.TriggerTypePostAuthentication,
|
||||||
|
actionIDs: []string{"actionID1"},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
}
|
||||||
|
details, err := c.SetTriggerActions(tt.args.ctx, tt.args.flowType, tt.args.triggerType, tt.args.actionIDs, tt.args.resourceOwner)
|
||||||
|
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.details, details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -2,9 +2,10 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/repository/usergrant"
|
"github.com/caos/zitadel/internal/repository/usergrant"
|
||||||
@@ -29,10 +30,6 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (pusher eventstore.EventPusher, _ *UserGrantWriteModel, err error) {
|
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (pusher eventstore.EventPusher, _ *UserGrantWriteModel, err error) {
|
||||||
err = checkExplicitProjectPermission(ctx, userGrant.ProjectGrantID, userGrant.ProjectID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !userGrant.IsValid() {
|
if !userGrant.IsValid() {
|
||||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
|
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
@@ -15,7 +17,6 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/usergrant"
|
"github.com/caos/zitadel/internal/repository/usergrant"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCommandSide_AddUserGrant(t *testing.T) {
|
func TestCommandSide_AddUserGrant(t *testing.T) {
|
||||||
@@ -38,24 +39,6 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
args args
|
args args
|
||||||
res res
|
res res
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "invalid permissions, error",
|
|
||||||
fields: fields{
|
|
||||||
eventstore: eventstoreExpect(
|
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
userGrant: &domain.UserGrant{
|
|
||||||
UserID: "user1",
|
|
||||||
},
|
|
||||||
resourceOwner: "org1",
|
|
||||||
},
|
|
||||||
res: res{
|
|
||||||
err: caos_errs.IsPermissionDenied,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "invalid usergrant, error",
|
name: "invalid usergrant, error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
|
39
internal/domain/action.go
Normal file
39
internal/domain/action.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
models.ObjectRoot
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Script string
|
||||||
|
Timeout time.Duration
|
||||||
|
AllowedToFail bool
|
||||||
|
State ActionState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Action) IsValid() bool {
|
||||||
|
return a.Name != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionStateUnspecified ActionState = iota
|
||||||
|
ActionStateActive
|
||||||
|
ActionStateInactive
|
||||||
|
ActionStateRemoved
|
||||||
|
actionStateCount
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s ActionState) Valid() bool {
|
||||||
|
return s >= 0 && s < actionStateCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ActionState) Exists() bool {
|
||||||
|
return s != ActionStateUnspecified && s != ActionStateRemoved
|
||||||
|
}
|
@@ -68,6 +68,7 @@ type ExternalUser struct {
|
|||||||
PreferredLanguage language.Tag
|
PreferredLanguage language.Tag
|
||||||
Phone string
|
Phone string
|
||||||
IsPhoneVerified bool
|
IsPhoneVerified bool
|
||||||
|
Metadatas []*Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
type Prompt int32
|
type Prompt int32
|
||||||
|
@@ -26,6 +26,7 @@ const (
|
|||||||
FeatureCustomTextMessage = FeatureCustomText + ".message"
|
FeatureCustomTextMessage = FeatureCustomText + ".message"
|
||||||
FeatureCustomTextLogin = FeatureCustomText + ".login"
|
FeatureCustomTextLogin = FeatureCustomText + ".login"
|
||||||
FeatureMetadataUser = FeatureMetadata + ".user"
|
FeatureMetadataUser = FeatureMetadata + ".user"
|
||||||
|
FeatureActions = "actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Features struct {
|
type Features struct {
|
||||||
@@ -53,6 +54,7 @@ type Features struct {
|
|||||||
PrivacyPolicy bool
|
PrivacyPolicy bool
|
||||||
MetadataUser bool
|
MetadataUser bool
|
||||||
LockoutPolicy bool
|
LockoutPolicy bool
|
||||||
|
Actions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeaturesState int32
|
type FeaturesState int32
|
||||||
|
52
internal/domain/flow.go
Normal file
52
internal/domain/flow.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type FlowState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlowStateActive FlowState = iota
|
||||||
|
FlowStateInactive
|
||||||
|
flowStateCount
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s FlowState) Valid() bool {
|
||||||
|
return s >= 0 && s < flowStateCount
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlowType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlowTypeUnspecified FlowType = iota
|
||||||
|
FlowTypeExternalAuthentication
|
||||||
|
flowTypeCount
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s FlowType) Valid() bool {
|
||||||
|
return s > 0 && s < flowTypeCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FlowType) HasTrigger(triggerType TriggerType) bool {
|
||||||
|
switch triggerType {
|
||||||
|
case TriggerTypePostAuthentication:
|
||||||
|
return s == FlowTypeExternalAuthentication
|
||||||
|
case TriggerTypePreCreation:
|
||||||
|
return s == FlowTypeExternalAuthentication
|
||||||
|
case TriggerTypePostCreation:
|
||||||
|
return s == FlowTypeExternalAuthentication
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
TriggerTypeUnspecified TriggerType = iota
|
||||||
|
TriggerTypePostAuthentication
|
||||||
|
TriggerTypePreCreation
|
||||||
|
TriggerTypePostCreation
|
||||||
|
triggerTypeCount
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s TriggerType) Valid() bool {
|
||||||
|
return s >= 0 && s < triggerTypeCount
|
||||||
|
}
|
@@ -33,6 +33,7 @@ type FeaturesView struct {
|
|||||||
CustomTextMessage bool
|
CustomTextMessage bool
|
||||||
CustomTextLogin bool
|
CustomTextLogin bool
|
||||||
LockoutPolicy bool
|
LockoutPolicy bool
|
||||||
|
Actions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeaturesView) FeatureList() []string {
|
func (f *FeaturesView) FeatureList() []string {
|
||||||
@@ -82,6 +83,9 @@ func (f *FeaturesView) FeatureList() []string {
|
|||||||
if f.LockoutPolicy {
|
if f.LockoutPolicy {
|
||||||
list = append(list, domain.FeatureLockoutPolicy)
|
list = append(list, domain.FeatureLockoutPolicy)
|
||||||
}
|
}
|
||||||
|
if f.Actions {
|
||||||
|
list = append(list, domain.FeatureActions)
|
||||||
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,6 +47,7 @@ type FeaturesView struct {
|
|||||||
CustomTextMessage bool `json:"customTextMessage" gorm:"column:custom_text_message"`
|
CustomTextMessage bool `json:"customTextMessage" gorm:"column:custom_text_message"`
|
||||||
CustomTextLogin bool `json:"customTextLogin" gorm:"column:custom_text_login"`
|
CustomTextLogin bool `json:"customTextLogin" gorm:"column:custom_text_login"`
|
||||||
LockoutPolicy bool `json:"lockoutPolicy" gorm:"column:lockout_policy"`
|
LockoutPolicy bool `json:"lockoutPolicy" gorm:"column:lockout_policy"`
|
||||||
|
Actions bool `json:"actions" gorm:"column:actions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
||||||
@@ -76,6 +77,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
|||||||
CustomTextMessage: features.CustomTextMessage,
|
CustomTextMessage: features.CustomTextMessage,
|
||||||
CustomTextLogin: features.CustomTextLogin,
|
CustomTextLogin: features.CustomTextLogin,
|
||||||
LockoutPolicy: features.LockoutPolicy,
|
LockoutPolicy: features.LockoutPolicy,
|
||||||
|
Actions: features.Actions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
104
internal/query/action.go
Normal file
104
internal/query/action.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var actionsQuery = squirrel.StatementBuilder.Select("creation_date", "change_date", "resource_owner", "sequence", "id", "action_state", "name", "script", "timeout", "allowed_to_fail").
|
||||||
|
From("zitadel.projections.actions").PlaceholderFormat(squirrel.Dollar)
|
||||||
|
|
||||||
|
func (q *Queries) GetAction(ctx context.Context, id string, orgID string) (*Action, error) {
|
||||||
|
idQuery, _ := newActionIDSearchQuery(id)
|
||||||
|
actions, err := q.SearchActions(ctx, &ActionSearchQueries{Queries: []SearchQuery{idQuery}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(actions) != 1 {
|
||||||
|
return nil, errors.ThrowNotFound(nil, "QUERY-dft2g", "Errors.Action.NotFound")
|
||||||
|
}
|
||||||
|
return actions[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SearchActions(ctx context.Context, query *ActionSearchQueries) ([]*Action, error) {
|
||||||
|
stmt, args, err := query.ToQuery(actionsQuery).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
actions := []*Action{}
|
||||||
|
for rows.Next() {
|
||||||
|
org := new(Action)
|
||||||
|
rows.Scan(
|
||||||
|
&org.CreationDate,
|
||||||
|
&org.ChangeDate,
|
||||||
|
&org.ResourceOwner,
|
||||||
|
&org.Sequence,
|
||||||
|
&org.ID,
|
||||||
|
&org.State,
|
||||||
|
&org.Name,
|
||||||
|
&org.Script,
|
||||||
|
&org.Timeout,
|
||||||
|
&org.AllowedToFail,
|
||||||
|
)
|
||||||
|
actions = append(actions, org)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
ID string `col:"id"`
|
||||||
|
CreationDate time.Time `col:"creation_date"`
|
||||||
|
ChangeDate time.Time `col:"change_date"`
|
||||||
|
ResourceOwner string `col:"resource_owner"`
|
||||||
|
State domain.ActionState `col:"action_state"`
|
||||||
|
Sequence uint64 `col:"sequence"`
|
||||||
|
|
||||||
|
Name string `col:"name"`
|
||||||
|
Script string `col:"script"`
|
||||||
|
Timeout time.Duration `col:"-"`
|
||||||
|
AllowedToFail bool `col:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionSearchQueries struct {
|
||||||
|
SearchRequest
|
||||||
|
Queries []SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *ActionSearchQueries) ToQuery(query squirrel.SelectBuilder) squirrel.SelectBuilder {
|
||||||
|
query = q.SearchRequest.ToQuery(query)
|
||||||
|
for _, q := range q.Queries {
|
||||||
|
query = q.ToQuery(query)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionResourceOwnerQuery(id string) (SearchQuery, error) {
|
||||||
|
return NewTextQuery("resource_owner", id, TextEquals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||||
|
return NewTextQuery("name", value, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionStateSearchQuery(value domain.ActionState) (SearchQuery, error) {
|
||||||
|
return NewIntQuery("state", int(value), IntEquals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActionIDSearchQuery(id string) (SearchQuery, error) {
|
||||||
|
return NewTextQuery("id", id, TextEquals)
|
||||||
|
}
|
173
internal/query/action_flow.go
Normal file
173
internal/query/action_flow.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (q *Queries) GetActionsByFlowAndTriggerType(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType) ([]*Action, error) {
|
||||||
|
flowTypeQuery, _ := NewTriggerActionFlowTypeSearchQuery(flowType)
|
||||||
|
triggerTypeQuery, _ := NewTriggerActionTriggerTypeSearchQuery(triggerType)
|
||||||
|
return q.SearchActionsFromFlow(ctx, &TriggerActionSearchQueries{Queries: []SearchQuery{flowTypeQuery, triggerTypeQuery}})
|
||||||
|
}
|
||||||
|
|
||||||
|
var triggerActionsQuery = squirrel.StatementBuilder.Select("creation_date", "change_date", "resource_owner", "sequence", "action_id", "name", "script", "trigger_type", "trigger_sequence").
|
||||||
|
From("zitadel.projections.flows_actions_triggers").PlaceholderFormat(squirrel.Dollar)
|
||||||
|
|
||||||
|
func (q *Queries) SearchActionsFromFlow(ctx context.Context, query *TriggerActionSearchQueries) ([]*Action, error) {
|
||||||
|
stmt, args, err := query.ToQuery(triggerActionsQuery).OrderBy("flow_type", "trigger_type", "trigger_sequence").ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
actions := []*Action{}
|
||||||
|
for rows.Next() {
|
||||||
|
action := new(Action)
|
||||||
|
var triggerType domain.TriggerType
|
||||||
|
var triggerSequence int
|
||||||
|
rows.Scan(
|
||||||
|
&action.CreationDate,
|
||||||
|
&action.ChangeDate,
|
||||||
|
&action.ResourceOwner,
|
||||||
|
&action.Sequence,
|
||||||
|
//&action.State, //TODO: state in next release
|
||||||
|
&action.ID,
|
||||||
|
&action.Name,
|
||||||
|
&action.Script,
|
||||||
|
&triggerType,
|
||||||
|
&triggerSequence,
|
||||||
|
)
|
||||||
|
actions = append(actions, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetFlow(ctx context.Context, flowType domain.FlowType) (*Flow, error) {
|
||||||
|
flowTypeQuery, _ := NewTriggerActionFlowTypeSearchQuery(flowType)
|
||||||
|
return q.SearchFlow(ctx, &TriggerActionSearchQueries{Queries: []SearchQuery{flowTypeQuery}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SearchFlow(ctx context.Context, query *TriggerActionSearchQueries) (*Flow, error) {
|
||||||
|
stmt, args, err := query.ToQuery(triggerActionsQuery.OrderBy("flow_type", "trigger_type", "trigger_sequence")).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
flow := &Flow{
|
||||||
|
TriggerActions: make(map[domain.TriggerType][]*Action),
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
action := new(Action)
|
||||||
|
var triggerType domain.TriggerType
|
||||||
|
var triggerSequence int
|
||||||
|
rows.Scan(
|
||||||
|
&action.CreationDate,
|
||||||
|
&action.ChangeDate,
|
||||||
|
&action.ResourceOwner,
|
||||||
|
&action.Sequence,
|
||||||
|
//&action.State, //TODO: state in next release
|
||||||
|
&action.ID,
|
||||||
|
&action.Name,
|
||||||
|
&action.Script,
|
||||||
|
&triggerType,
|
||||||
|
&triggerSequence,
|
||||||
|
)
|
||||||
|
|
||||||
|
flow.TriggerActions[triggerType] = append(flow.TriggerActions[triggerType], action)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetFlowTypesOfActionID(ctx context.Context, actionID string) ([]domain.FlowType, error) {
|
||||||
|
actionIDQuery, _ := NewTriggerActionActionIDSearchQuery(actionID)
|
||||||
|
query := &TriggerActionSearchQueries{Queries: []SearchQuery{actionIDQuery}}
|
||||||
|
stmt, args, err := query.ToQuery(
|
||||||
|
squirrel.StatementBuilder.
|
||||||
|
Select("flow_type").
|
||||||
|
From("zitadel.projections.flows_actions_triggers").
|
||||||
|
PlaceholderFormat(squirrel.Dollar)).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal")
|
||||||
|
}
|
||||||
|
flowTypes := make([]domain.FlowType, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var flow_type domain.FlowType
|
||||||
|
rows.Scan(
|
||||||
|
&flow_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
flowTypes = append(flowTypes, flow_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
return flowTypes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flow struct {
|
||||||
|
ID string `col:"id"`
|
||||||
|
CreationDate time.Time `col:"creation_date"`
|
||||||
|
ChangeDate time.Time `col:"change_date"`
|
||||||
|
ResourceOwner string `col:"resource_owner"`
|
||||||
|
Sequence uint64 `col:"sequence"`
|
||||||
|
Type domain.FlowType `col:"flow_type"`
|
||||||
|
|
||||||
|
TriggerActions map[domain.TriggerType][]*Action
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerActionSearchQueries struct {
|
||||||
|
SearchRequest
|
||||||
|
Queries []SearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TriggerActionSearchQueries) ToQuery(query squirrel.SelectBuilder) squirrel.SelectBuilder {
|
||||||
|
query = q.SearchRequest.ToQuery(query)
|
||||||
|
for _, q := range q.Queries {
|
||||||
|
query = q.ToQuery(query)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionTriggerTypeSearchQuery(value domain.TriggerType) (SearchQuery, error) {
|
||||||
|
return NewIntQuery("trigger_type", int(value), IntEquals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionFlowTypeSearchQuery(value domain.FlowType) (SearchQuery, error) {
|
||||||
|
return NewIntQuery("flow_type", int(value), IntEquals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionActionIDSearchQuery(actionID string) (SearchQuery, error) {
|
||||||
|
return NewTextQuery("action_id", actionID, TextEquals)
|
||||||
|
}
|
174
internal/query/projection/action.go
Normal file
174
internal/query/projection/action.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package projection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/handler"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionProjection struct {
|
||||||
|
crdb.StatementHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ActionProjection {
|
||||||
|
p := &ActionProjection{}
|
||||||
|
config.ProjectionName = "projections.actions"
|
||||||
|
config.Reducers = p.reducers()
|
||||||
|
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActionProjection) reducers() []handler.AggregateReducer {
|
||||||
|
return []handler.AggregateReducer{
|
||||||
|
{
|
||||||
|
Aggregate: action.AggregateType,
|
||||||
|
EventRedusers: []handler.EventReducer{
|
||||||
|
{
|
||||||
|
Event: action.AddedEventType,
|
||||||
|
Reduce: p.reduceActionAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: action.ChangedEventType,
|
||||||
|
Reduce: p.reduceActionChanged,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: action.DeactivatedEventType,
|
||||||
|
Reduce: p.reduceActionDeactivated,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: action.ReactivatedEventType,
|
||||||
|
Reduce: p.reduceActionReactivated,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: action.RemovedEventType,
|
||||||
|
Reduce: p.reduceActionRemoved,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
actionIDCol = "id"
|
||||||
|
actionCreationDateCol = "creation_date"
|
||||||
|
actionChangeDateCol = "change_date"
|
||||||
|
actionResourceOwnerCol = "resource_owner"
|
||||||
|
actionStateCol = "action_state"
|
||||||
|
actionSequenceCol = "sequence"
|
||||||
|
actionNameCol = "name"
|
||||||
|
actionScriptCol = "script"
|
||||||
|
actionTimeoutCol = "timeout"
|
||||||
|
actionAllowedToFailCol = "allowed_to_fail"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *ActionProjection) reduceActionAdded(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.AddedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", action.AddedEventType).Error("was not an event")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewCreateStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(actionIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCol(actionCreationDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionChangeDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionResourceOwnerCol, e.Aggregate().ResourceOwner),
|
||||||
|
handler.NewCol(actionSequenceCol, e.Sequence()),
|
||||||
|
handler.NewCol(actionNameCol, e.Name),
|
||||||
|
handler.NewCol(actionScriptCol, e.Script),
|
||||||
|
handler.NewCol(actionTimeoutCol, e.Timeout),
|
||||||
|
handler.NewCol(actionAllowedToFailCol, e.AllowedToFail),
|
||||||
|
handler.NewCol(actionStateCol, domain.ActionStateActive),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActionProjection) reduceActionChanged(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.ChangedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence, "expected", action.ChangedEventType).Error("wrong event type")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-Bg8oM", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
values := []handler.Column{
|
||||||
|
handler.NewCol(actionChangeDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionSequenceCol, e.Sequence()),
|
||||||
|
}
|
||||||
|
if e.Name != nil {
|
||||||
|
values = append(values, handler.NewCol(actionNameCol, *e.Name))
|
||||||
|
}
|
||||||
|
if e.Script != nil {
|
||||||
|
values = append(values, handler.NewCol(actionScriptCol, *e.Script))
|
||||||
|
}
|
||||||
|
if e.Timeout != nil {
|
||||||
|
values = append(values, handler.NewCol(actionTimeoutCol, *e.Timeout))
|
||||||
|
}
|
||||||
|
if e.AllowedToFail != nil {
|
||||||
|
values = append(values, handler.NewCol(actionAllowedToFailCol, *e.AllowedToFail))
|
||||||
|
}
|
||||||
|
return crdb.NewUpdateStatement(
|
||||||
|
e,
|
||||||
|
values,
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(actionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActionProjection) reduceActionDeactivated(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.DeactivatedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-1gwdc", "seq", event.Sequence, "expectedType", action.DeactivatedEventType).Error("wrong event type")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-BApK4", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewUpdateStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(actionChangeDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionSequenceCol, e.Sequence()),
|
||||||
|
handler.NewCol(actionStateCol, domain.ActionStateInactive),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(actionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActionProjection) reduceActionReactivated(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.ReactivatedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-Vjwiy", "seq", event.Sequence, "expectedType", action.ReactivatedEventType).Error("wrong event type")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-o37De", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewUpdateStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(actionChangeDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionSequenceCol, e.Sequence()),
|
||||||
|
handler.NewCol(actionStateCol, domain.ActionStateActive),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(actionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ActionProjection) reduceActionRemoved(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.RemovedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence, "expectedType", action.RemovedEventType).Error("wrong event type")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-4TbKT", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewDeleteStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(actionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
184
internal/query/projection/flow/flow.go
Normal file
184
internal/query/projection/flow/flow.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package flow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/handler"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlowProjection struct {
|
||||||
|
crdb.StatementHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlowProjection(ctx context.Context, config crdb.StatementHandlerConfig) *FlowProjection {
|
||||||
|
p := &FlowProjection{}
|
||||||
|
config.ProjectionName = "projections.flows"
|
||||||
|
config.Reducers = p.reducers()
|
||||||
|
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FlowProjection) reducers() []handler.AggregateReducer {
|
||||||
|
return []handler.AggregateReducer{
|
||||||
|
{
|
||||||
|
Aggregate: org.AggregateType,
|
||||||
|
EventRedusers: []handler.EventReducer{
|
||||||
|
{
|
||||||
|
Event: org.TriggerActionsSetEventType,
|
||||||
|
Reduce: p.reduceTriggerActionsSetEventType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: org.FlowClearedEventType,
|
||||||
|
Reduce: p.reduceFlowClearedEventType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Aggregate: action.AggregateType,
|
||||||
|
EventRedusers: []handler.EventReducer{
|
||||||
|
{
|
||||||
|
Event: action.AddedEventType,
|
||||||
|
Reduce: p.reduceFlowActionAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: action.ChangedEventType,
|
||||||
|
Reduce: p.reduceFlowActionChanged,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: action.RemovedEventType,
|
||||||
|
Reduce: p.reduceFlowActionRemoved,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
triggerTableSuffix = "triggers"
|
||||||
|
flowTypeCol = "flow_type"
|
||||||
|
flowTriggerTypeCol = "trigger_type"
|
||||||
|
flowResourceOwnerCol = "resource_owner"
|
||||||
|
flowActionTriggerSequenceCol = "trigger_sequence"
|
||||||
|
flowActionIDCol = "action_id"
|
||||||
|
|
||||||
|
actionTableSuffix = "actions"
|
||||||
|
actionIDCol = "id"
|
||||||
|
actionCreationDateCol = "creation_date"
|
||||||
|
actionChangeDateCol = "change_date"
|
||||||
|
actionResourceOwnerCol = "resource_owner"
|
||||||
|
actionSequenceCol = "sequence"
|
||||||
|
actionNameCol = "name"
|
||||||
|
actionScriptCol = "script"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *FlowProjection) reduceTriggerActionsSetEventType(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*org.TriggerActionsSetEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", action.AddedEventType).Error("was not an trigger actions set event")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
stmts := make([]func(reader eventstore.EventReader) crdb.Exec, len(e.ActionIDs)+1)
|
||||||
|
stmts[0] = crdb.AddDeleteStatement(
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(flowTypeCol, e.FlowType),
|
||||||
|
handler.NewCond(flowTriggerTypeCol, e.TriggerType),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(triggerTableSuffix),
|
||||||
|
)
|
||||||
|
for i, id := range e.ActionIDs {
|
||||||
|
stmts[i+1] = crdb.AddCreateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(flowResourceOwnerCol, e.Aggregate().ResourceOwner),
|
||||||
|
handler.NewCol(flowTypeCol, e.FlowType),
|
||||||
|
handler.NewCol(flowTriggerTypeCol, e.TriggerType),
|
||||||
|
handler.NewCol(flowActionIDCol, id),
|
||||||
|
handler.NewCol(flowActionTriggerSequenceCol, i),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(triggerTableSuffix),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return crdb.NewMultiStatement(e, stmts...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FlowProjection) reduceFlowClearedEventType(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*org.FlowClearedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", action.AddedEventType).Error("was not an trigger actions set event")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewDeleteStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(flowTypeCol, e.FlowType),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(triggerTableSuffix),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FlowProjection) reduceFlowActionAdded(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.AddedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", action.AddedEventType).Error("was not an flow action added event")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewCreateStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(actionIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCol(actionCreationDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionChangeDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionResourceOwnerCol, e.Aggregate().ResourceOwner),
|
||||||
|
handler.NewCol(actionSequenceCol, e.Sequence()),
|
||||||
|
handler.NewCol(actionNameCol, e.Name),
|
||||||
|
handler.NewCol(actionScriptCol, e.Script),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(actionTableSuffix),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FlowProjection) reduceFlowActionChanged(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.ChangedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence, "expected", action.ChangedEventType).Error("wrong event type")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-Bg8oM", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
values := []handler.Column{
|
||||||
|
handler.NewCol(actionChangeDateCol, e.CreationDate()),
|
||||||
|
handler.NewCol(actionSequenceCol, e.Sequence()),
|
||||||
|
}
|
||||||
|
if e.Name != nil {
|
||||||
|
values = append(values, handler.NewCol(actionNameCol, *e.Name))
|
||||||
|
}
|
||||||
|
if e.Script != nil {
|
||||||
|
values = append(values, handler.NewCol(actionScriptCol, *e.Script))
|
||||||
|
}
|
||||||
|
return crdb.NewUpdateStatement(
|
||||||
|
e,
|
||||||
|
values,
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(actionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(actionTableSuffix),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FlowProjection) reduceFlowActionRemoved(event eventstore.EventReader) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*action.RemovedEvent)
|
||||||
|
if !ok {
|
||||||
|
logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence, "expectedType", action.RemovedEventType).Error("wrong event type")
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "HANDL-4TbKT", "reduce.wrong.event.type")
|
||||||
|
}
|
||||||
|
return crdb.NewDeleteStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(actionIDCol, e.Aggregate().ID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(actionTableSuffix),
|
||||||
|
), nil
|
||||||
|
}
|
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
"github.com/caos/zitadel/internal/eventstore/handler"
|
"github.com/caos/zitadel/internal/eventstore/handler"
|
||||||
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
||||||
"github.com/caos/zitadel/internal/query/projection/org/owner"
|
"github.com/caos/zitadel/internal/query/projection/flow"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -37,9 +37,12 @@ func Start(ctx context.Context, es *eventstore.Eventstore, config Config) error
|
|||||||
BulkLimit: config.BulkLimit,
|
BulkLimit: config.BulkLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
NewOrgProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["orgs"]))
|
// turned off for this release
|
||||||
NewProjectProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["projects"]))
|
//NewOrgProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["orgs"]))
|
||||||
owner.NewOrgOwnerProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["org_owners"]))
|
//NewProjectProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["projects"]))
|
||||||
|
//owner.NewOrgOwnerProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["org_owners"]))
|
||||||
|
NewActionProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["actions"]))
|
||||||
|
flow.NewFlowProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["flows"]))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ package query
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
"github.com/caos/zitadel/internal/config/types"
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/caos/zitadel/internal/id"
|
||||||
"github.com/caos/zitadel/internal/query/projection"
|
"github.com/caos/zitadel/internal/query/projection"
|
||||||
|
"github.com/caos/zitadel/internal/repository/action"
|
||||||
iam_repo "github.com/caos/zitadel/internal/repository/iam"
|
iam_repo "github.com/caos/zitadel/internal/repository/iam"
|
||||||
"github.com/caos/zitadel/internal/repository/org"
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
"github.com/caos/zitadel/internal/repository/project"
|
"github.com/caos/zitadel/internal/repository/project"
|
||||||
@@ -22,6 +24,8 @@ type Queries struct {
|
|||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
idGenerator id.Generator
|
idGenerator id.Generator
|
||||||
secretCrypto crypto.Crypto
|
secretCrypto crypto.Crypto
|
||||||
|
|
||||||
|
client *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -29,26 +33,32 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartQueries(ctx context.Context, es *eventstore.Eventstore, projections projection.Config, defaults sd.SystemDefaults) (repo *Queries, err error) {
|
func StartQueries(ctx context.Context, es *eventstore.Eventstore, projections projection.Config, defaults sd.SystemDefaults) (repo *Queries, err error) {
|
||||||
|
sqlClient, err := projections.CRDB.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
repo = &Queries{
|
repo = &Queries{
|
||||||
iamID: defaults.IamID,
|
iamID: defaults.IamID,
|
||||||
eventstore: es,
|
eventstore: es,
|
||||||
idGenerator: id.SonyFlakeGenerator,
|
idGenerator: id.SonyFlakeGenerator,
|
||||||
|
client: sqlClient,
|
||||||
}
|
}
|
||||||
iam_repo.RegisterEventMappers(repo.eventstore)
|
iam_repo.RegisterEventMappers(repo.eventstore)
|
||||||
usr_repo.RegisterEventMappers(repo.eventstore)
|
usr_repo.RegisterEventMappers(repo.eventstore)
|
||||||
org.RegisterEventMappers(repo.eventstore)
|
org.RegisterEventMappers(repo.eventstore)
|
||||||
project.RegisterEventMappers(repo.eventstore)
|
project.RegisterEventMappers(repo.eventstore)
|
||||||
|
action.RegisterEventMappers(repo.eventstore)
|
||||||
|
|
||||||
repo.secretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey)
|
repo.secretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// turned off for this release
|
err = projection.Start(ctx, es, projections)
|
||||||
// err = projection.Start(ctx, es, projections)
|
if err != nil {
|
||||||
// if err != nil {
|
return nil, err
|
||||||
// return nil, err
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
149
internal/query/search_query.go
Normal file
149
internal/query/search_query.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchRequest struct {
|
||||||
|
Offset uint64
|
||||||
|
Limit uint64
|
||||||
|
SortingColumn string
|
||||||
|
Asc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *SearchRequest) ToQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
if req.Offset > 0 {
|
||||||
|
query = query.Offset(req.Offset)
|
||||||
|
}
|
||||||
|
if req.Limit > 0 {
|
||||||
|
query = query.Limit(req.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.SortingColumn != "" {
|
||||||
|
clause := "LOWER(?)"
|
||||||
|
if !req.Asc {
|
||||||
|
clause += " DESC"
|
||||||
|
}
|
||||||
|
query.OrderByClause(clause, req.SortingColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlPlaceholder = "?"
|
||||||
|
|
||||||
|
type SearchQuery interface {
|
||||||
|
ToQuery(sq.SelectBuilder) sq.SelectBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextQuery struct {
|
||||||
|
Column string
|
||||||
|
Text string
|
||||||
|
Compare TextComparison
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTextQuery(column, value string, compare TextComparison) (*TextQuery, error) {
|
||||||
|
if compare < 0 || compare >= textMax {
|
||||||
|
return nil, errors.New("invalid compare")
|
||||||
|
}
|
||||||
|
if column == "" {
|
||||||
|
return nil, errors.New("missing column")
|
||||||
|
}
|
||||||
|
return &TextQuery{
|
||||||
|
Column: column,
|
||||||
|
Text: value,
|
||||||
|
Compare: compare,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *TextQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
query = query.Where(q.comp())
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TextQuery) comp() map[string]interface{} {
|
||||||
|
switch s.Compare {
|
||||||
|
case TextEquals:
|
||||||
|
return sq.Eq{s.Column: s.Text}
|
||||||
|
case TextEqualsIgnore:
|
||||||
|
return sq.Eq{"LOWER(" + s.Column + ")": strings.ToLower(s.Text)}
|
||||||
|
case TextStartsWith:
|
||||||
|
return sq.Like{s.Column: s.Text + sqlPlaceholder}
|
||||||
|
case TextStartsWithIgnore:
|
||||||
|
return sq.Like{"LOWER(" + s.Column + ")": strings.ToLower(s.Text) + sqlPlaceholder}
|
||||||
|
case TextEndsWith:
|
||||||
|
return sq.Like{s.Column: sqlPlaceholder + s.Text}
|
||||||
|
case TextEndsWithIgnore:
|
||||||
|
return sq.Like{"LOWER(" + s.Column + ")": sqlPlaceholder + strings.ToLower(s.Text)}
|
||||||
|
case TextContains:
|
||||||
|
return sq.Like{s.Column: sqlPlaceholder + s.Text + sqlPlaceholder}
|
||||||
|
case TextContainsIgnore:
|
||||||
|
return sq.Like{"LOWER(" + s.Column + ")": sqlPlaceholder + strings.ToLower(s.Text) + sqlPlaceholder}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextComparison int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TextEquals TextComparison = iota
|
||||||
|
TextEqualsIgnore
|
||||||
|
TextStartsWith
|
||||||
|
TextStartsWithIgnore
|
||||||
|
TextEndsWith
|
||||||
|
TextEndsWithIgnore
|
||||||
|
TextContains
|
||||||
|
TextContainsIgnore
|
||||||
|
|
||||||
|
textMax
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntQuery struct {
|
||||||
|
Column string
|
||||||
|
Int int
|
||||||
|
Compare IntComparison
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIntQuery(column string, value int, compare IntComparison) (*IntQuery, error) {
|
||||||
|
if compare < 0 || compare >= intMax {
|
||||||
|
return nil, errors.New("invalid compare")
|
||||||
|
}
|
||||||
|
if column == "" {
|
||||||
|
return nil, errors.New("missing column")
|
||||||
|
}
|
||||||
|
return &IntQuery{
|
||||||
|
Column: column,
|
||||||
|
Int: value,
|
||||||
|
Compare: compare,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *IntQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
query = query.Where(q.comp())
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntQuery) comp() sq.Sqlizer {
|
||||||
|
switch s.Compare {
|
||||||
|
case IntEquals:
|
||||||
|
return sq.Eq{s.Column: s.Int}
|
||||||
|
case IntGreater:
|
||||||
|
return sq.Gt{s.Column: s.Int}
|
||||||
|
case IntLess:
|
||||||
|
return sq.Lt{s.Column: s.Int}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntComparison int
|
||||||
|
|
||||||
|
const (
|
||||||
|
IntEquals IntComparison = iota
|
||||||
|
IntGreater
|
||||||
|
IntLess
|
||||||
|
|
||||||
|
intMax
|
||||||
|
)
|
261
internal/repository/action/action.go
Normal file
261
internal/repository/action/action.go
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UniqueActionNameType = "action_names"
|
||||||
|
eventTypePrefix = eventstore.EventType("action.")
|
||||||
|
AddedEventType = eventTypePrefix + "added"
|
||||||
|
ChangedEventType = eventTypePrefix + "changed"
|
||||||
|
DeactivatedEventType = eventTypePrefix + "deactivated"
|
||||||
|
ReactivatedEventType = eventTypePrefix + "reactivated"
|
||||||
|
RemovedEventType = eventTypePrefix + "removed"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAddActionNameUniqueConstraint(actionName, resourceOwner string) *eventstore.EventUniqueConstraint {
|
||||||
|
return eventstore.NewAddEventUniqueConstraint(
|
||||||
|
UniqueActionNameType,
|
||||||
|
actionName+":"+resourceOwner,
|
||||||
|
"Errors.Action.AlreadyExists")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoveActionNameUniqueConstraint(actionName, resourceOwner string) *eventstore.EventUniqueConstraint {
|
||||||
|
return eventstore.NewRemoveEventUniqueConstraint(
|
||||||
|
UniqueActionNameType,
|
||||||
|
actionName+":"+resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
Script string `json:"script,omitempty"`
|
||||||
|
Timeout time.Duration `json:"timeout,omitempty"`
|
||||||
|
AllowedToFail bool `json:"allowedToFail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AddedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return []*eventstore.EventUniqueConstraint{NewAddActionNameUniqueConstraint(e.Name, e.Aggregate().ResourceOwner)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
name,
|
||||||
|
script string,
|
||||||
|
timeout time.Duration,
|
||||||
|
allowedToFail bool,
|
||||||
|
) *AddedEvent {
|
||||||
|
return &AddedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
AddedEventType,
|
||||||
|
),
|
||||||
|
Name: name,
|
||||||
|
Script: script,
|
||||||
|
Timeout: timeout,
|
||||||
|
AllowedToFail: allowedToFail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e := &AddedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(event.Data, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "ACTION-4n8vs", "unable to unmarshal action added")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Script *string `json:"script,omitempty"`
|
||||||
|
Timeout *time.Duration `json:"timeout,omitempty"`
|
||||||
|
AllowedToFail *bool `json:"allowedToFail,omitempty"`
|
||||||
|
oldName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ChangedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
if e.oldName == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []*eventstore.EventUniqueConstraint{
|
||||||
|
NewRemoveActionNameUniqueConstraint(e.oldName, e.Aggregate().ResourceOwner),
|
||||||
|
NewAddActionNameUniqueConstraint(*e.Name, e.Aggregate().ResourceOwner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChangedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
changes []ActionChanges,
|
||||||
|
) (*ChangedEvent, error) {
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "ACTION-dg4t2", "Errors.NoChangesFound")
|
||||||
|
}
|
||||||
|
changeEvent := &ChangedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
ChangedEventType,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for _, change := range changes {
|
||||||
|
change(changeEvent)
|
||||||
|
}
|
||||||
|
return changeEvent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionChanges func(event *ChangedEvent)
|
||||||
|
|
||||||
|
func ChangeName(name, oldName string) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.Name = &name
|
||||||
|
e.oldName = oldName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeScript(script string) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.Script = &script
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeTimeout(timeout time.Duration) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.Timeout = &timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeAllowedToFail(allowedToFail bool) func(event *ChangedEvent) {
|
||||||
|
return func(e *ChangedEvent) {
|
||||||
|
e.AllowedToFail = &allowedToFail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e := &ChangedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(event.Data, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "ACTION-4n8vs", "unable to unmarshal action changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeactivatedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DeactivatedEvent) Data() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DeactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeactivatedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *DeactivatedEvent {
|
||||||
|
return &DeactivatedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
DeactivatedEventType,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeactivatedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
return &DeactivatedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReactivatedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ReactivatedEvent) Data() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ReactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReactivatedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *ReactivatedEvent {
|
||||||
|
return &ReactivatedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
ReactivatedEventType,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReactivatedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
return &ReactivatedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemovedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RemovedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return []*eventstore.EventUniqueConstraint{NewRemoveActionNameUniqueConstraint(e.name, e.Aggregate().ResourceOwner)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemovedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
name string,
|
||||||
|
) *RemovedEvent {
|
||||||
|
return &RemovedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
RemovedEventType,
|
||||||
|
),
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
return &RemovedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}, nil
|
||||||
|
}
|
23
internal/repository/action/aggregate.go
Normal file
23
internal/repository/action/aggregate.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import "github.com/caos/zitadel/internal/eventstore"
|
||||||
|
|
||||||
|
const (
|
||||||
|
AggregateType = "action"
|
||||||
|
AggregateVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Aggregate struct {
|
||||||
|
eventstore.Aggregate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAggregate(id, resourceOwner string) *Aggregate {
|
||||||
|
return &Aggregate{
|
||||||
|
Aggregate: eventstore.Aggregate{
|
||||||
|
Type: AggregateType,
|
||||||
|
Version: AggregateVersion,
|
||||||
|
ID: id,
|
||||||
|
ResourceOwner: resourceOwner,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
11
internal/repository/action/eventstore.go
Normal file
11
internal/repository/action/eventstore.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import "github.com/caos/zitadel/internal/eventstore"
|
||||||
|
|
||||||
|
func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||||
|
es.RegisterFilterEventMapper(AddedEventType, AddedEventMapper).
|
||||||
|
RegisterFilterEventMapper(ChangedEventType, ChangedEventMapper).
|
||||||
|
RegisterFilterEventMapper(DeactivatedEventType, DeactivatedEventMapper).
|
||||||
|
RegisterFilterEventMapper(ReactivatedEventType, ReactivatedEventMapper).
|
||||||
|
RegisterFilterEventMapper(RemovedEventType, RemovedEventMapper)
|
||||||
|
}
|
@@ -40,6 +40,7 @@ type FeaturesSetEvent struct {
|
|||||||
CustomTextMessage *bool `json:"customTextMessage,omitempty"`
|
CustomTextMessage *bool `json:"customTextMessage,omitempty"`
|
||||||
CustomTextLogin *bool `json:"customTextLogin,omitempty"`
|
CustomTextLogin *bool `json:"customTextLogin,omitempty"`
|
||||||
LockoutPolicy *bool `json:"lockoutPolicy,omitempty"`
|
LockoutPolicy *bool `json:"lockoutPolicy,omitempty"`
|
||||||
|
Actions *bool `json:"actions,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FeaturesSetEvent) Data() interface{} {
|
func (e *FeaturesSetEvent) Data() interface{} {
|
||||||
@@ -188,6 +189,12 @@ func ChangeLockoutPolicy(lockoutPolicy bool) func(event *FeaturesSetEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ChangeActions(actions bool) func(event *FeaturesSetEvent) {
|
||||||
|
return func(e *FeaturesSetEvent) {
|
||||||
|
e.Actions = &actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
e := &FeaturesSetEvent{
|
e := &FeaturesSetEvent{
|
||||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
139
internal/repository/flow/flow.go
Normal file
139
internal/repository/flow/flow.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package flow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventTypePrefix = eventstore.EventType("flow.")
|
||||||
|
triggerActionsPrefix = eventTypePrefix + "trigger_actions."
|
||||||
|
TriggerActionsSetEventType = triggerActionsPrefix + "set"
|
||||||
|
TriggerActionsCascadeRemovedEventType = triggerActionsPrefix + "cascade.removed"
|
||||||
|
FlowClearedEventType = eventTypePrefix + "cleared"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TriggerActionsSetEvent struct {
|
||||||
|
eventstore.BaseEvent
|
||||||
|
|
||||||
|
FlowType domain.FlowType
|
||||||
|
TriggerType domain.TriggerType
|
||||||
|
ActionIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TriggerActionsSetEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TriggerActionsSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionsSetEvent(
|
||||||
|
base *eventstore.BaseEvent,
|
||||||
|
flowType domain.FlowType,
|
||||||
|
triggerType domain.TriggerType,
|
||||||
|
actionIDs []string,
|
||||||
|
) *TriggerActionsSetEvent {
|
||||||
|
return &TriggerActionsSetEvent{
|
||||||
|
BaseEvent: *base,
|
||||||
|
FlowType: flowType,
|
||||||
|
TriggerType: triggerType,
|
||||||
|
ActionIDs: actionIDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerActionsSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e := &TriggerActionsSetEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(event.Data, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "FLOW-4n8vs", "unable to unmarshal trigger actions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerActionsCascadeRemovedEvent struct {
|
||||||
|
eventstore.BaseEvent
|
||||||
|
|
||||||
|
FlowType domain.FlowType
|
||||||
|
TriggerType domain.TriggerType
|
||||||
|
ActionID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TriggerActionsCascadeRemovedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TriggerActionsCascadeRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionsCascadeRemovedEvent(
|
||||||
|
base *eventstore.BaseEvent,
|
||||||
|
flowType domain.FlowType,
|
||||||
|
actionID string,
|
||||||
|
) *TriggerActionsCascadeRemovedEvent {
|
||||||
|
return &TriggerActionsCascadeRemovedEvent{
|
||||||
|
BaseEvent: *base,
|
||||||
|
FlowType: flowType,
|
||||||
|
ActionID: actionID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerActionsCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e := &TriggerActionsCascadeRemovedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(event.Data, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "FLOW-4n8vs", "unable to unmarshal trigger actions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlowClearedEvent struct {
|
||||||
|
eventstore.BaseEvent
|
||||||
|
|
||||||
|
FlowType domain.FlowType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FlowClearedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FlowClearedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlowClearedEvent(
|
||||||
|
base *eventstore.BaseEvent,
|
||||||
|
flowType domain.FlowType,
|
||||||
|
) *FlowClearedEvent {
|
||||||
|
return &FlowClearedEvent{
|
||||||
|
BaseEvent: *base,
|
||||||
|
FlowType: flowType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlowClearedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e := &FlowClearedEvent{
|
||||||
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(event.Data, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "FLOW-BHfg2", "unable to unmarshal flow cleared")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
@@ -78,5 +78,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
|||||||
RegisterFilterEventMapper(IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
|
RegisterFilterEventMapper(IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
|
||||||
RegisterFilterEventMapper(IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
|
RegisterFilterEventMapper(IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
|
||||||
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
|
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
|
||||||
RegisterFilterEventMapper(FeaturesRemovedEventType, FeaturesRemovedEventMapper)
|
RegisterFilterEventMapper(FeaturesRemovedEventType, FeaturesRemovedEventMapper).
|
||||||
|
RegisterFilterEventMapper(TriggerActionsSetEventType, TriggerActionsSetEventMapper).
|
||||||
|
RegisterFilterEventMapper(TriggerActionsCascadeRemovedEventType, TriggerActionsCascadeRemovedEventMapper).
|
||||||
|
RegisterFilterEventMapper(FlowClearedEventType, FlowClearedEventMapper)
|
||||||
}
|
}
|
||||||
|
106
internal/repository/org/flow.go
Normal file
106
internal/repository/org/flow.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package org
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
|
"github.com/caos/zitadel/internal/repository/flow"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TriggerActionsSetEventType = orgEventTypePrefix + flow.TriggerActionsSetEventType
|
||||||
|
TriggerActionsCascadeRemovedEventType = orgEventTypePrefix + flow.TriggerActionsCascadeRemovedEventType
|
||||||
|
FlowClearedEventType = orgEventTypePrefix + flow.FlowClearedEventType
|
||||||
|
)
|
||||||
|
|
||||||
|
type TriggerActionsSetEvent struct {
|
||||||
|
flow.TriggerActionsSetEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionsSetEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
flowType domain.FlowType,
|
||||||
|
triggerType domain.TriggerType,
|
||||||
|
actionIDs []string,
|
||||||
|
) *TriggerActionsSetEvent {
|
||||||
|
return &TriggerActionsSetEvent{
|
||||||
|
TriggerActionsSetEvent: *flow.NewTriggerActionsSetEvent(
|
||||||
|
eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
TriggerActionsSetEventType),
|
||||||
|
flowType,
|
||||||
|
triggerType,
|
||||||
|
actionIDs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerActionsSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e, err := flow.TriggerActionsSetEventMapper(event)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TriggerActionsSetEvent{TriggerActionsSetEvent: *e.(*flow.TriggerActionsSetEvent)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerActionsCascadeRemovedEvent struct {
|
||||||
|
flow.TriggerActionsCascadeRemovedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTriggerActionsCascadeRemovedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
flowType domain.FlowType,
|
||||||
|
actionID string,
|
||||||
|
) *TriggerActionsCascadeRemovedEvent {
|
||||||
|
return &TriggerActionsCascadeRemovedEvent{
|
||||||
|
TriggerActionsCascadeRemovedEvent: *flow.NewTriggerActionsCascadeRemovedEvent(
|
||||||
|
eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
TriggerActionsCascadeRemovedEventType),
|
||||||
|
flowType,
|
||||||
|
actionID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TriggerActionsCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e, err := flow.TriggerActionsCascadeRemovedEventMapper(event)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TriggerActionsCascadeRemovedEvent{TriggerActionsCascadeRemovedEvent: *e.(*flow.TriggerActionsCascadeRemovedEvent)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlowClearedEvent struct {
|
||||||
|
flow.FlowClearedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlowClearedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
flowType domain.FlowType,
|
||||||
|
) *FlowClearedEvent {
|
||||||
|
return &FlowClearedEvent{
|
||||||
|
FlowClearedEvent: *flow.NewFlowClearedEvent(
|
||||||
|
eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
FlowClearedEventType),
|
||||||
|
flowType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlowClearedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||||
|
e, err := flow.FlowClearedEventMapper(event)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FlowClearedEvent{FlowClearedEvent: *e.(*flow.FlowClearedEvent)}, nil
|
||||||
|
}
|
@@ -365,6 +365,17 @@ Errors:
|
|||||||
NoData: Meta Daten Liste ist leer
|
NoData: Meta Daten Liste ist leer
|
||||||
Invalid: Meta Daten sind ungültig
|
Invalid: Meta Daten sind ungültig
|
||||||
KeyNotExisting: Ein oder mehrere Keys existiert nicht
|
KeyNotExisting: Ein oder mehrere Keys existiert nicht
|
||||||
|
Action:
|
||||||
|
Invalid: Action ist ungültig
|
||||||
|
NotFound: Action wurde nicht gefunden
|
||||||
|
NotActive: Action ist nicht aktiv
|
||||||
|
NotInactive: Action ist nicht inaktiv
|
||||||
|
Flow:
|
||||||
|
FlowTypeMissing: FlowType fehlt
|
||||||
|
Empty: Flow ist bereits leer
|
||||||
|
WrongTriggerType: TriggerType ist ungültig
|
||||||
|
NoChanges: Keine Änderungen
|
||||||
|
ActionIDsNotExist: ActionIDs existieren nicht
|
||||||
EventTypes:
|
EventTypes:
|
||||||
user:
|
user:
|
||||||
added: Benutzer hinzugefügt
|
added: Benutzer hinzugefügt
|
||||||
@@ -654,6 +665,12 @@ EventTypes:
|
|||||||
added: Datenschutzbestimmung und AGB hinzugefügt
|
added: Datenschutzbestimmung und AGB hinzugefügt
|
||||||
changed: Datenschutzbestimmung und AGB geändert
|
changed: Datenschutzbestimmung und AGB geändert
|
||||||
removed: Datenschutzbestimmung und AGB entfernt
|
removed: Datenschutzbestimmung und AGB entfernt
|
||||||
|
flow:
|
||||||
|
trigger_actions:
|
||||||
|
set: Aktionen festgelegt
|
||||||
|
cascade:
|
||||||
|
removed: Aktionen kaskadiert entfernt
|
||||||
|
removed: Aktionen entfernt
|
||||||
project:
|
project:
|
||||||
added: Projekt hinzugefügt
|
added: Projekt hinzugefügt
|
||||||
changed: Project geändert
|
changed: Project geändert
|
||||||
@@ -784,6 +801,12 @@ EventTypes:
|
|||||||
removed: Bilder und Schrift von Label Richtlinie entfernt
|
removed: Bilder und Schrift von Label Richtlinie entfernt
|
||||||
key_pair:
|
key_pair:
|
||||||
added: Schlüsselpaar hinzugefügt
|
added: Schlüsselpaar hinzugefügt
|
||||||
|
action:
|
||||||
|
added: Aktion hinzugefügt
|
||||||
|
changed: Aktion geändert
|
||||||
|
deactivated: Aktion deaktiviert
|
||||||
|
reactivated: Aktion reaktiviert
|
||||||
|
removed: Aktion gelöscht
|
||||||
Application:
|
Application:
|
||||||
OIDC:
|
OIDC:
|
||||||
V1:
|
V1:
|
||||||
|
@@ -365,6 +365,17 @@ Errors:
|
|||||||
NoData: Metadata list is empty
|
NoData: Metadata list is empty
|
||||||
Invalid: Metadata is invalid
|
Invalid: Metadata is invalid
|
||||||
KeyNotExisting: One or more keys do not exist
|
KeyNotExisting: One or more keys do not exist
|
||||||
|
Action:
|
||||||
|
Invalid: Action is invalid
|
||||||
|
NotFound: Action not found
|
||||||
|
NotActive: Action is not active
|
||||||
|
NotInactive: Action is not inactive
|
||||||
|
Flow:
|
||||||
|
FlowTypeMissing: FlowType missing
|
||||||
|
Empty: Flow is already empty
|
||||||
|
WrongTriggerType: TriggerType is invalid
|
||||||
|
NoChanges: No Changes
|
||||||
|
ActionIDsNotExist: ActionIDs do not exist
|
||||||
EventTypes:
|
EventTypes:
|
||||||
user:
|
user:
|
||||||
added: User added
|
added: User added
|
||||||
@@ -654,6 +665,12 @@ EventTypes:
|
|||||||
added: Privacy policy and TOS added
|
added: Privacy policy and TOS added
|
||||||
changed: Privacy policy and TOS changed
|
changed: Privacy policy and TOS changed
|
||||||
removed: Privacy policy and TOS removed
|
removed: Privacy policy and TOS removed
|
||||||
|
flow:
|
||||||
|
trigger_actions:
|
||||||
|
set: Action set
|
||||||
|
cascade:
|
||||||
|
removed: Actions cascade removed
|
||||||
|
removed: Actions removed
|
||||||
project:
|
project:
|
||||||
added: Project added
|
added: Project added
|
||||||
changed: Project changed
|
changed: Project changed
|
||||||
@@ -781,6 +798,12 @@ EventTypes:
|
|||||||
removed: Assets removed from Label Policy
|
removed: Assets removed from Label Policy
|
||||||
key_pair:
|
key_pair:
|
||||||
added: Key pair added
|
added: Key pair added
|
||||||
|
action:
|
||||||
|
added: Action added
|
||||||
|
changed: Action changed
|
||||||
|
deactivated: Action deactivated
|
||||||
|
reactivated: Action reactivated
|
||||||
|
removed: Action removed
|
||||||
Application:
|
Application:
|
||||||
OIDC:
|
OIDC:
|
||||||
V1:
|
V1:
|
||||||
|
75
internal/ui/login/handler/custom_action.go
Normal file
75
internal/ui/login/handler/custom_action.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"github.com/caos/zitadel/internal/actions"
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Login) customExternalUserMapping(user *domain.ExternalUser, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView) (*domain.ExternalUser, error) {
|
||||||
|
triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePostAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx := (&actions.Context{}).SetToken(tokens)
|
||||||
|
api := (&actions.API{}).SetExternalUser(user).SetMetadata(&user.Metadatas)
|
||||||
|
for _, a := range triggerActions {
|
||||||
|
err = actions.Run(ctx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) customExternalUserToLoginUserMapping(user *domain.Human, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView, metadata []*domain.Metadata) (*domain.Human, []*domain.Metadata, error) {
|
||||||
|
triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePreCreation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ctx := (&actions.Context{}).SetToken(tokens)
|
||||||
|
api := (&actions.API{}).SetHuman(user).SetMetadata(&metadata)
|
||||||
|
for _, a := range triggerActions {
|
||||||
|
err = actions.Run(ctx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) customGrants(userID string, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView) ([]*domain.UserGrant, error) {
|
||||||
|
triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePostCreation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx := (&actions.Context{}).SetToken(tokens)
|
||||||
|
actionUserGrants := make([]actions.UserGrant, 0)
|
||||||
|
api := (&actions.API{}).SetUserGrants(&actionUserGrants)
|
||||||
|
for _, a := range triggerActions {
|
||||||
|
err = actions.Run(ctx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionUserGrantsToDomain(userID, actionUserGrants), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionUserGrantsToDomain(userID string, actionUserGrants []actions.UserGrant) []*domain.UserGrant {
|
||||||
|
if actionUserGrants == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
userGrants := make([]*domain.UserGrant, len(actionUserGrants))
|
||||||
|
for i, grant := range actionUserGrants {
|
||||||
|
userGrants[i] = &domain.UserGrant{
|
||||||
|
UserID: userID,
|
||||||
|
ProjectID: grant.ProjectID,
|
||||||
|
ProjectGrantID: grant.ProjectGrantID,
|
||||||
|
RoleKeys: grant.Roles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userGrants
|
||||||
|
}
|
@@ -184,7 +184,12 @@ func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *dom
|
|||||||
|
|
||||||
func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
|
func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
|
||||||
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
|
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
|
||||||
err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser, domain.BrowserInfoFromRequest(r))
|
externalUser, err := l.customExternalUserMapping(externalUser, tokens, authReq, idpConfig)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser, domain.BrowserInfoFromRequest(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
err = nil
|
err = nil
|
||||||
@@ -201,6 +206,17 @@ func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.R
|
|||||||
l.handleAutoRegister(w, r, authReq)
|
l.handleAutoRegister(w, r, authReq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(externalUser.Metadatas) > 0 {
|
||||||
|
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, externalUser.Metadatas...)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
l.renderNextStep(w, r, authReq)
|
l.renderNextStep(w, r, authReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,12 +282,29 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
|
|||||||
}
|
}
|
||||||
|
|
||||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
linkingUser := authReq.LinkingUsers[len(authReq.LinkingUsers)-1]
|
||||||
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, memberRoles, authReq.ID, userAgentID, resourceOwner, domain.BrowserInfoFromRequest(r))
|
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, linkingUser, idpConfig)
|
||||||
|
user, metadata, err = l.customExternalUserToLoginUserMapping(user, nil, authReq, idpConfig, metadata)
|
||||||
|
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, memberRoles, authReq.ID, userAgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderExternalNotFoundOption(w, r, authReq, err)
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userGrants, err := l.customGrants(authReq.UserID, nil, authReq, idpConfig)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
l.renderNextStep(w, r, authReq)
|
l.renderNextStep(w, r, authReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +338,7 @@ func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.ID
|
|||||||
}
|
}
|
||||||
return externalUser
|
return externalUser
|
||||||
}
|
}
|
||||||
func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyView, linkingUser *domain.ExternalUser, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.ExternalIDP) {
|
func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyView, linkingUser *domain.ExternalUser, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.ExternalIDP, []*domain.Metadata) {
|
||||||
username := linkingUser.PreferredUsername
|
username := linkingUser.PreferredUsername
|
||||||
switch idpConfig.OIDCUsernameMapping {
|
switch idpConfig.OIDCUsernameMapping {
|
||||||
case iam_model.OIDCMappingFieldEmail:
|
case iam_model.OIDCMappingFieldEmail:
|
||||||
@@ -360,5 +393,5 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyV
|
|||||||
ExternalUserID: linkingUser.ExternalUserID,
|
ExternalUserID: linkingUser.ExternalUserID,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
}
|
}
|
||||||
return human, externalIDP
|
return human, externalIDP, linkingUser.Metadatas
|
||||||
}
|
}
|
||||||
|
@@ -118,6 +118,10 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, externalIDP := l.mapTokenToLoginHumanAndExternalIDP(orgIamPolicy, tokens, idpConfig)
|
user, externalIDP := l.mapTokenToLoginHumanAndExternalIDP(orgIamPolicy, tokens, idpConfig)
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !idpConfig.AutoRegister {
|
if !idpConfig.AutoRegister {
|
||||||
l.renderExternalRegisterOverview(w, r, authReq, orgIamPolicy, user, externalIDP, nil)
|
l.renderExternalRegisterOverview(w, r, authReq, orgIamPolicy, user, externalIDP, nil)
|
||||||
return
|
return
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
"github.com/caos/oidc/pkg/client/rp"
|
"github.com/caos/oidc/pkg/client/rp"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
http_util "github.com/caos/zitadel/internal/api/http"
|
http_util "github.com/caos/zitadel/internal/api/http"
|
||||||
@@ -74,28 +75,24 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth
|
|||||||
}
|
}
|
||||||
tokens := &oidc.Tokens{IDToken: token, IDTokenClaims: tokenClaims}
|
tokens := &oidc.Tokens{IDToken: token, IDTokenClaims: tokenClaims}
|
||||||
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
|
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
|
||||||
|
externalUser, err = l.customExternalUserMapping(externalUser, tokens, authReq, idpConfig)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata := externalUser.Metadatas
|
||||||
err = l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, authReq.AgentID, externalUser, domain.BrowserInfoFromRequest(r))
|
err = l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, authReq.AgentID, externalUser, domain.BrowserInfoFromRequest(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsNotFound(err) {
|
l.jwtExtractionUserNotFound(w, r, authReq, idpConfig, tokens, err)
|
||||||
err = nil
|
return
|
||||||
}
|
}
|
||||||
if !idpConfig.AutoRegister {
|
if len(metadata) > 0 {
|
||||||
l.renderExternalNotFoundOption(w, r, authReq, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
|
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resourceOwner := l.getOrgID(authReq)
|
_, err = l.command.BulkSetUserMetadata(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, metadata...)
|
||||||
orgIamPolicy, err := l.getOrgIamPolicy(r, resourceOwner)
|
|
||||||
if err != nil {
|
|
||||||
l.renderError(w, r, authReq, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
|
||||||
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, authReq.AgentID, resourceOwner, domain.BrowserInfoFromRequest(r))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
@@ -109,6 +106,72 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth
|
|||||||
http.Redirect(w, r, redirect, http.StatusFound)
|
http.Redirect(w, r, redirect, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, tokens *oidc.Tokens, err error) {
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if !idpConfig.AutoRegister {
|
||||||
|
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resourceOwner := l.getOrgID(authReq)
|
||||||
|
orgIamPolicy, err := l.getOrgIamPolicy(r, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
||||||
|
user, metadata, err = l.customExternalUserToLoginUserMapping(user, tokens, authReq, idpConfig, metadata)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, authReq.AgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r))
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userGrants, err := l.customGrants(authReq.UserID, tokens, authReq, idpConfig)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirect, err := l.redirectToJWTCallback(authReq)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, redirect, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) appendUserGrants(ctx context.Context, userGrants []*domain.UserGrant, resourceOwner string) error {
|
||||||
|
if len(userGrants) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, grant := range userGrants {
|
||||||
|
_, err := l.command.AddUserGrant(setContext(ctx, resourceOwner), grant, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Login) redirectToJWTCallback(authReq *domain.AuthRequest) (string, error) {
|
func (l *Login) redirectToJWTCallback(authReq *domain.AuthRequest) (string, error) {
|
||||||
redirect, err := url.Parse(l.baseURL + EndpointJWTCallback)
|
redirect, err := url.Parse(l.baseURL + EndpointJWTCallback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -160,6 +223,7 @@ func (l *Login) handleJWTCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateToken(ctx context.Context, token string, config *iam_model.IDPConfigView) (oidc.IDTokenClaims, error) {
|
func validateToken(ctx context.Context, token string, config *iam_model.IDPConfigView) (oidc.IDTokenClaims, error) {
|
||||||
|
logging.Log("LOGIN-ADf42").Debug("begin token validation")
|
||||||
offset := 3 * time.Second
|
offset := 3 * time.Second
|
||||||
maxAge := time.Hour
|
maxAge := time.Hour
|
||||||
claims := oidc.EmptyIDTokenClaims()
|
claims := oidc.EmptyIDTokenClaims()
|
||||||
@@ -172,6 +236,7 @@ func validateToken(ctx context.Context, token string, config *iam_model.IDPConfi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging.Log("LOGIN-Dfg22").Debug("begin signature validation")
|
||||||
keySet := rp.NewRemoteKeySet(http.DefaultClient, config.JWTKeysEndpoint)
|
keySet := rp.NewRemoteKeySet(http.DefaultClient, config.JWTKeysEndpoint)
|
||||||
if err = oidc.CheckSignature(ctx, token, payload, claims, nil, keySet); err != nil {
|
if err = oidc.CheckSignature(ctx, token, payload, claims, nil, keySet); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
63
migrations/cockroach/V1.72__actions.sql
Normal file
63
migrations/cockroach/V1.72__actions.sql
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
CREATE TABLE zitadel.projections.actions (
|
||||||
|
id TEXT,
|
||||||
|
creation_date TIMESTAMPTZ,
|
||||||
|
change_date TIMESTAMPTZ,
|
||||||
|
resource_owner TEXT,
|
||||||
|
action_state SMALLINT,
|
||||||
|
sequence BIGINT,
|
||||||
|
|
||||||
|
name TEXT,
|
||||||
|
script TEXT,
|
||||||
|
timeout BIGINT,
|
||||||
|
allowed_to_fail BOOLEAN,
|
||||||
|
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE zitadel.projections.flows_actions (
|
||||||
|
id TEXT,
|
||||||
|
creation_date TIMESTAMPTZ,
|
||||||
|
change_date TIMESTAMPTZ,
|
||||||
|
resource_owner TEXT,
|
||||||
|
sequence BIGINT,
|
||||||
|
|
||||||
|
name TEXT,
|
||||||
|
script TEXT,
|
||||||
|
timeout BIGINT,
|
||||||
|
allowed_to_fail BOOLEAN,
|
||||||
|
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE zitadel.projections.flows_triggers (
|
||||||
|
flow_type SMALLINT,
|
||||||
|
trigger_type SMALLINT,
|
||||||
|
resource_owner TEXT,
|
||||||
|
action_id TEXT,
|
||||||
|
trigger_sequence SMALLINT,
|
||||||
|
|
||||||
|
PRIMARY KEY (flow_type, trigger_type, resource_owner, action_id),
|
||||||
|
CONSTRAINT fk_action FOREIGN KEY (action_id) REFERENCES zitadel.projections.flows_actions (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW zitadel.projections.flows_actions_triggers AS (
|
||||||
|
SELECT a.id AS action_id,
|
||||||
|
a.name,
|
||||||
|
a.creation_date,
|
||||||
|
a.resource_owner,
|
||||||
|
a.sequence,
|
||||||
|
a.change_date,
|
||||||
|
a.script,
|
||||||
|
a.timeout,
|
||||||
|
a.allowed_to_fail,
|
||||||
|
t.flow_type,
|
||||||
|
t.trigger_type,
|
||||||
|
t.trigger_sequence
|
||||||
|
FROM zitadel.projections.flows_triggers t
|
||||||
|
JOIN zitadel.projections.flows_actions a ON t.action_id = a.id
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE auth.features ADD COLUMN actions BOOLEAN;
|
||||||
|
ALTER TABLE authz.features ADD COLUMN actions BOOLEAN;
|
||||||
|
ALTER TABLE adminapi.features ADD COLUMN actions BOOLEAN;
|
||||||
|
ALTER TABLE management.features ADD COLUMN actions BOOLEAN;
|
155
proto/zitadel/action.proto
Normal file
155
proto/zitadel/action.proto
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "zitadel/object.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
|
||||||
|
package zitadel.action.v1;
|
||||||
|
|
||||||
|
option go_package ="github.com/caos/zitadel/pkg/grpc/action";
|
||||||
|
|
||||||
|
message Action {
|
||||||
|
string id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.v1.ObjectDetails details = 2;
|
||||||
|
ActionState state = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "the state of the action";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string name = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"log context\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string script = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"function log(context, calls){console.log(context)}\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
google.protobuf.Duration timeout = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "after which time the action will be terminated if not finished";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool allowed_to_fail = 7 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "when true, the next action will be called even if this action fails";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ActionState {
|
||||||
|
ACTION_STATE_UNSPECIFIED = 0;
|
||||||
|
ACTION_STATE_INACTIVE = 1;
|
||||||
|
ACTION_STATE_ACTIVE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ActionIDQuery {
|
||||||
|
string id = 1 [
|
||||||
|
(validate.rules).string = {max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ActionNameQuery {
|
||||||
|
string name = 1 [
|
||||||
|
(validate.rules).string = {max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"log\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//ActionStateQuery is always equals
|
||||||
|
message ActionStateQuery {
|
||||||
|
ActionState state = 1 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "current state of the action";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ActionFieldName {
|
||||||
|
ACTION_FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
ACTION_FIELD_NAME_NAME = 1;
|
||||||
|
ACTION_FIELD_NAME_ID = 2;
|
||||||
|
ACTION_FIELD_NAME_STATE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Flow {
|
||||||
|
FlowType type = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "\"the type of the flow\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.v1.ObjectDetails details = 2;
|
||||||
|
FlowState state = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "the state of the flow";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated TriggerAction trigger_actions = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlowType {
|
||||||
|
FLOW_TYPE_UNSPECIFIED = 0;
|
||||||
|
FLOW_TYPE_EXTERNAL_AUTHENTICATION = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlowState {
|
||||||
|
FLOW_STATE_UNSPECIFIED = 0;
|
||||||
|
FLOW_STATE_INACTIVE = 1;
|
||||||
|
FLOW_STATE_ACTIVE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TriggerType {
|
||||||
|
TRIGGER_TYPE_UNSPECIFIED = 0;
|
||||||
|
TRIGGER_TYPE_POST_AUTHENTICATION = 1;
|
||||||
|
TRIGGER_TYPE_PRE_CREATION = 2;
|
||||||
|
TRIGGER_TYPE_POST_CREATION = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TriggerAction {
|
||||||
|
TriggerType trigger_type = 1;
|
||||||
|
repeated Action actions = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlowFieldName {
|
||||||
|
FLOW_FIELD_NAME_UNSPECIFIED = 0;
|
||||||
|
FLOW_FIELD_NAME_TYPE = 1;
|
||||||
|
FLOW_FIELD_NAME_STATE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//FlowTypeQuery is always equals
|
||||||
|
message FlowTypeQuery {
|
||||||
|
FlowType state = 1 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "type of the flow";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//FlowStateQuery is always equals
|
||||||
|
message FlowStateQuery {
|
||||||
|
FlowState state = 1 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "current state of the flow";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
@@ -2813,6 +2813,7 @@ message SetDefaultFeaturesRequest {
|
|||||||
bool custom_text_message = 20;
|
bool custom_text_message = 20;
|
||||||
bool custom_text_login = 21;
|
bool custom_text_login = 21;
|
||||||
bool lockout_policy = 22;
|
bool lockout_policy = 22;
|
||||||
|
bool actions = 23;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetDefaultFeaturesResponse {
|
message SetDefaultFeaturesResponse {
|
||||||
@@ -2852,6 +2853,7 @@ message SetOrgFeaturesRequest {
|
|||||||
bool custom_text_message = 21;
|
bool custom_text_message = 21;
|
||||||
bool custom_text_login = 22;
|
bool custom_text_login = 22;
|
||||||
bool lockout_policy = 23;
|
bool lockout_policy = 23;
|
||||||
|
bool actions = 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetOrgFeaturesResponse {
|
message SetOrgFeaturesResponse {
|
||||||
|
@@ -30,6 +30,7 @@ message Features {
|
|||||||
bool custom_text_message = 19;
|
bool custom_text_message = 19;
|
||||||
bool custom_text_login = 20;
|
bool custom_text_login = 20;
|
||||||
bool lockout_policy = 21;
|
bool lockout_policy = 21;
|
||||||
|
bool actions = 22;
|
||||||
}
|
}
|
||||||
|
|
||||||
message FeatureTier {
|
message FeatureTier {
|
||||||
@@ -45,4 +46,4 @@ enum FeaturesState {
|
|||||||
FEATURES_STATE_ACTION_REQUIRED = 1;
|
FEATURES_STATE_ACTION_REQUIRED = 1;
|
||||||
FEATURES_STATE_CANCELED = 2;
|
FEATURES_STATE_CANCELED = 2;
|
||||||
FEATURES_STATE_GRANDFATHERED = 3;
|
FEATURES_STATE_GRANDFATHERED = 3;
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ 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 "zitadel/metadata.proto";
|
||||||
|
import "zitadel/action.proto";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
@@ -2710,6 +2711,124 @@ service ManagementService {
|
|||||||
feature: "login_policy.idp"
|
feature: "login_policy.idp"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc ListActions(ListActionsRequest) returns (ListActionsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/actions/_search"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.action.read"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc GetAction(GetActionRequest) returns (GetActionResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/actions/{id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.action.read"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc CreateAction(CreateActionRequest) returns (CreateActionResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/actions"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.action.write"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc UpdateAction(UpdateActionRequest) returns (UpdateActionResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
put: "/actions/{id}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.action.write"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: enable in next release
|
||||||
|
// rpc DeactivateAction(DeactivateActionRequest) returns (DeactivateActionResponse) {
|
||||||
|
// option (google.api.http) = {
|
||||||
|
// post: "/actions/{id}/_deactivate"
|
||||||
|
// body: "*"
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// option (zitadel.v1.auth_option) = {
|
||||||
|
// permission: "org.action.write"
|
||||||
|
// feature: "actions"
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rpc ReactivateAction(ReactivateActionRequest) returns (ReactivateActionResponse) {
|
||||||
|
// option (google.api.http) = {
|
||||||
|
// post: "/actions/{id}/_reactivate"
|
||||||
|
// body: "*"
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// option (zitadel.v1.auth_option) = {
|
||||||
|
// permission: "org.action.write"
|
||||||
|
// feature: "actions"
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
rpc DeleteAction(DeleteActionRequest) returns (DeleteActionResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/actions/{id}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.action.delete"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc GetFlow(GetFlowRequest) returns (GetFlowResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/flows/{type}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.flow.read"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc ClearFlow(ClearFlowRequest) returns (ClearFlowResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/flows/{type}/_clear"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.flow.delete"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc SetTriggerActions(SetTriggerActionsRequest) returns (SetTriggerActionsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/flows/{flow_type}/trigger/{trigger_type}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "org.flow.write"
|
||||||
|
feature: "actions"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//This is an empty request
|
//This is an empty request
|
||||||
@@ -5117,3 +5236,151 @@ message UpdateOrgIDPJWTConfigRequest {
|
|||||||
message UpdateOrgIDPJWTConfigResponse {
|
message UpdateOrgIDPJWTConfigResponse {
|
||||||
zitadel.v1.ObjectDetails details = 1;
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ListActionsRequest {
|
||||||
|
//list limitations and ordering
|
||||||
|
zitadel.v1.ListQuery query = 1;
|
||||||
|
//the field the result is sorted
|
||||||
|
zitadel.action.v1.ActionFieldName sorting_column = 2;
|
||||||
|
//criteria the client is looking for
|
||||||
|
repeated ActionQuery queries = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ActionQuery {
|
||||||
|
oneof query {
|
||||||
|
option (validate.required) = true;
|
||||||
|
|
||||||
|
zitadel.action.v1.ActionIDQuery action_id_query = 1;
|
||||||
|
zitadel.action.v1.ActionNameQuery action_name_query = 2;
|
||||||
|
zitadel.action.v1.ActionStateQuery action_state_query = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListActionsResponse {
|
||||||
|
zitadel.v1.ListDetails details = 1;
|
||||||
|
zitadel.action.v1.ActionFieldName sorting_column = 2;
|
||||||
|
repeated zitadel.action.v1.Action result = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateActionRequest {
|
||||||
|
string name = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"log context\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string script = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 2000},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"function log(context, calls){console.log(context)}\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
google.protobuf.Duration timeout = 3 [
|
||||||
|
(validate.rules).duration = {gte: {}, lte: {seconds: 20}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "after which time the action will be terminated if not finished";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool allowed_to_fail = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "when true, the next action will be called even if this action fails";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateActionResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
string id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetActionRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetActionResponse {
|
||||||
|
zitadel.action.v1.Action action = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateActionRequest {
|
||||||
|
string id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string name = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"log context\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string script = 3 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 2000},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"function log(context, calls){console.log(context)}\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
google.protobuf.Duration timeout = 4 [
|
||||||
|
(validate.rules).duration = {gte: {}, lte: {seconds: 20}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "after which time the action will be terminated if not finished";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool allowed_to_fail = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "when true, the next action will be called even if this action fails";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateActionResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteActionRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteActionResponse {}
|
||||||
|
|
||||||
|
message DeactivateActionRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeactivateActionResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReactivateActionRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReactivateActionResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetFlowRequest {
|
||||||
|
zitadel.action.v1.FlowType type = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetFlowResponse {
|
||||||
|
zitadel.action.v1.Flow flow = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClearFlowRequest {
|
||||||
|
zitadel.action.v1.FlowType type = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClearFlowResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetTriggerActionsRequest {
|
||||||
|
zitadel.action.v1.FlowType flow_type = 1;
|
||||||
|
zitadel.action.v1.TriggerType trigger_type = 2;
|
||||||
|
repeated string action_ids = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetTriggerActionsResponse {
|
||||||
|
zitadel.v1.ObjectDetails details = 1;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user