diff --git a/.releaserc.js b/.releaserc.js
index 6500efe28c..16be63ca2c 100644
--- a/.releaserc.js
+++ b/.releaserc.js
@@ -3,7 +3,7 @@ module.exports = {
{name: 'main'},
{name: '1.x.x', range: '1.x.x', channel: '1.x.x'},
{name: 'v2-alpha', prerelease: true},
- {name: 'scheduler', prerelease: true},
+ {name: 'v2-alpha-import', prerelease: true},
],
plugins: [
"@semantic-release/commit-analyzer"
diff --git a/build/zitadel/generate-grpc.sh b/build/zitadel/generate-grpc.sh
index 89d0efa41c..f4e6b4d1e8 100755
--- a/build/zitadel/generate-grpc.sh
+++ b/build/zitadel/generate-grpc.sh
@@ -182,6 +182,9 @@ protoc \
-I=/proto/include \
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,settings.md \
${PROTO_PATH}/settings.proto
-
+protoc \
+ -I=/proto/include \
+ --doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,v1.md \
+ ${PROTO_PATH}/v1.proto
echo "done generating grpc"
diff --git a/cmd/start/start.go b/cmd/start/start.go
index 313ef460e3..92668f4d24 100644
--- a/cmd/start/start.go
+++ b/cmd/start/start.go
@@ -100,7 +100,7 @@ func startZitadel(config *Config, masterKey string) error {
return fmt.Errorf("cannot start eventstore for queries: %w", err)
}
- queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, config.InternalAuthZ.RolePermissionMappings)
+ queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, config.SystemDefaults, keys.IDPConfig, keys.OTP, keys.OIDC, config.InternalAuthZ.RolePermissionMappings)
if err != nil {
return fmt.Errorf("cannot start queries: %w", err)
}
@@ -178,7 +178,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.Database.Database, config.DefaultInstance)); err != nil {
return err
}
- if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.Database, commands, queries, adminRepo, config.ExternalSecure, keys.User)); err != nil {
+ if err := apis.RegisterServer(ctx, admin.CreateServer(config.Database.Database, commands, queries, config.SystemDefaults, adminRepo, config.ExternalSecure, keys.User)); err != nil {
return err
}
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil {
diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md
index d8b8a8332a..50d6b506cf 100644
--- a/docs/docs/apis/proto/admin.md
+++ b/docs/docs/apis/proto/admin.md
@@ -386,7 +386,7 @@ all queries need to match (AND)
> **rpc** SetUpOrg([SetUpOrgRequest](#setuporgrequest))
[SetUpOrgResponse](#setuporgresponse)
-Creates a new org and user
+Creates a new org and user
and adds the user to the orgs members as ORG_OWNER
@@ -1469,7 +1469,7 @@ they represent the delta of the event happend on the objects
[ListFailedEventsResponse](#listfailedeventsresponse)
Returns event descriptions which cannot be processed.
-It's possible that some events need some retries.
+It's possible that some events need some retries.
For example if the SMTP-API wasn't able to send an email at the first time
@@ -1493,6 +1493,30 @@ failed event. You can find out if it worked on the `failure_count`
DELETE: /failedevents/{database}/{view_name}/{failed_sequence}
+### ImportData
+
+> **rpc** ImportData([ImportDataRequest](#importdatarequest))
+[ImportDataResponse](#importdataresponse)
+
+Imports data into instance and creates different objects
+
+
+
+ POST: /import
+
+
+### ExportData
+
+> **rpc** ExportData([ExportDataRequest](#exportdatarequest))
+[ExportDataResponse](#exportdataresponse)
+
+Exports data from instance
+
+
+
+ POST: /export
+
+
@@ -1789,6 +1813,49 @@ This is an empty request
+### DataOrg
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| org_id | string | - | |
+| org | zitadel.management.v1.AddOrgRequest | - | |
+| domain_policy | AddCustomDomainPolicyRequest | - | |
+| label_policy | zitadel.management.v1.AddCustomLabelPolicyRequest | - | |
+| lockout_policy | zitadel.management.v1.AddCustomLockoutPolicyRequest | - | |
+| login_policy | zitadel.management.v1.AddCustomLoginPolicyRequest | - | |
+| password_complexity_policy | zitadel.management.v1.AddCustomPasswordComplexityPolicyRequest | - | |
+| privacy_policy | zitadel.management.v1.AddCustomPrivacyPolicyRequest | - | |
+| projects | repeated zitadel.v1.v1.DataProject | - | |
+| project_roles | repeated zitadel.management.v1.AddProjectRoleRequest | - | |
+| api_apps | repeated zitadel.v1.v1.DataAPIApplication | - | |
+| oidc_apps | repeated zitadel.v1.v1.DataOIDCApplication | - | |
+| human_users | repeated zitadel.v1.v1.DataHumanUser | - | |
+| machine_users | repeated zitadel.v1.v1.DataMachineUser | - | |
+| trigger_actions | repeated zitadel.management.v1.SetTriggerActionsRequest | - | |
+| actions | repeated zitadel.v1.v1.DataAction | - | |
+| project_grants | repeated zitadel.v1.v1.DataProjectGrant | - | |
+| user_grants | repeated zitadel.management.v1.AddUserGrantRequest | - | |
+| org_members | repeated zitadel.management.v1.AddOrgMemberRequest | - | |
+| project_members | repeated zitadel.management.v1.AddProjectMemberRequest | - | |
+| project_grant_members | repeated zitadel.management.v1.AddProjectGrantMemberRequest | - | |
+| user_metadata | repeated zitadel.management.v1.SetUserMetadataRequest | - | |
+| login_texts | repeated zitadel.management.v1.SetCustomLoginTextsRequest | - | |
+| init_messages | repeated zitadel.management.v1.SetCustomInitMessageTextRequest | - | |
+| password_reset_messages | repeated zitadel.management.v1.SetCustomPasswordResetMessageTextRequest | - | |
+| verify_email_messages | repeated zitadel.management.v1.SetCustomVerifyEmailMessageTextRequest | - | |
+| verify_phone_messages | repeated zitadel.management.v1.SetCustomVerifyPhoneMessageTextRequest | - | |
+| domain_claimed_messages | repeated zitadel.management.v1.SetCustomDomainClaimedMessageTextRequest | - | |
+| passwordless_registration_messages | repeated zitadel.management.v1.SetCustomPasswordlessRegistrationMessageTextRequest | - | |
+| oidc_idps | repeated zitadel.v1.v1.DataOIDCIDP | - | |
+| jwt_idps | repeated zitadel.v1.v1.DataJWTIDP | - | |
+| user_links | repeated zitadel.idp.v1.IDPUserLink | - | |
+| domains | repeated zitadel.org.v1.Domain | - | |
+
+
+
+
### DeactivateIDPRequest
@@ -1833,6 +1900,76 @@ This is an empty request
+### ExportDataRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| org_ids | repeated string | - | |
+| excluded_org_ids | repeated string | - | |
+| with_passwords | bool | - | |
+| with_otp | bool | - | |
+| response_output | bool | - | |
+| local_output | ExportDataRequest.LocalOutput | - | |
+| s3_output | ExportDataRequest.S3Output | - | |
+| gcs_output | ExportDataRequest.GCSOutput | - | |
+| timeout | string | - | |
+
+
+
+
+### ExportDataRequest.GCSOutput
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| bucket | string | - | |
+| serviceaccount_json | string | - | |
+| path | string | - | |
+
+
+
+
+### ExportDataRequest.LocalOutput
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| path | string | - | |
+
+
+
+
+### ExportDataRequest.S3Output
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| path | string | - | |
+| endpoint | string | - | |
+| access_key_id | string | - | |
+| secret_access_key | string | - | |
+| ssl | bool | - | |
+| bucket | string | - | |
+
+
+
+
+### ExportDataResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| orgs | repeated DataOrg | - | |
+
+
+
+
### FailedEvent
@@ -2603,6 +2740,218 @@ This is an empty response
+### ImportDataError
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| type | string | - | |
+| id | string | - | |
+| message | string | - | |
+
+
+
+
+### ImportDataOrg
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| orgs | repeated DataOrg | - | |
+
+
+
+
+### ImportDataRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgs | ImportDataOrg | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgsv1 | zitadel.v1.v1.ImportDataOrg | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgs_local | ImportDataRequest.LocalInput | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgsv1_local | ImportDataRequest.LocalInput | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgs_s3 | ImportDataRequest.S3Input | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgsv1_s3 | ImportDataRequest.S3Input | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgs_gcs | ImportDataRequest.GCSInput | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) data.data_orgsv1_gcs | ImportDataRequest.GCSInput | - | |
+| timeout | string | - | |
+
+
+
+
+### ImportDataRequest.GCSInput
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| bucket | string | - | |
+| serviceaccount_json | string | - | |
+| path | string | - | |
+
+
+
+
+### ImportDataRequest.LocalInput
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| path | string | - | |
+
+
+
+
+### ImportDataRequest.S3Input
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| path | string | - | |
+| endpoint | string | - | |
+| access_key_id | string | - | |
+| secret_access_key | string | - | |
+| ssl | bool | - | |
+| bucket | string | - | |
+
+
+
+
+### ImportDataResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| errors | repeated ImportDataError | - | |
+| success | ImportDataSuccess | - | |
+
+
+
+
+### ImportDataSuccess
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| orgs | repeated ImportDataSuccessOrg | - | |
+
+
+
+
+### ImportDataSuccessOrg
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| org_id | string | - | |
+| project_ids | repeated string | - | |
+| project_roles | repeated string | - | |
+| oidc_app_ids | repeated string | - | |
+| api_app_ids | repeated string | - | |
+| human_user_ids | repeated string | - | |
+| machine_user_ids | repeated string | - | |
+| action_ids | repeated string | - | |
+| trigger_actions | repeated zitadel.management.v1.SetTriggerActionsRequest | - | |
+| project_grants | repeated ImportDataSuccessProjectGrant | - | |
+| user_grants | repeated ImportDataSuccessUserGrant | - | |
+| org_members | repeated string | - | |
+| project_members | repeated ImportDataSuccessProjectMember | - | |
+| project_grant_members | repeated ImportDataSuccessProjectGrantMember | - | |
+| oidc_ipds | repeated string | - | |
+| jwt_idps | repeated string | - | |
+| idp_links | repeated string | - | |
+| user_links | repeated ImportDataSuccessUserLinks | - | |
+| user_metadata | repeated ImportDataSuccessUserMetadata | - | |
+| domains | repeated string | - | |
+
+
+
+
+### ImportDataSuccessProjectGrant
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| grant_id | string | - | |
+| project_id | string | - | |
+| org_id | string | - | |
+
+
+
+
+### ImportDataSuccessProjectGrantMember
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| project_id | string | - | |
+| grant_id | string | - | |
+| user_id | string | - | |
+
+
+
+
+### ImportDataSuccessProjectMember
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| project_id | string | - | |
+| user_id | string | - | |
+
+
+
+
+### ImportDataSuccessUserGrant
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| project_id | string | - | |
+| user_id | string | - | |
+
+
+
+
+### ImportDataSuccessUserLinks
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| user_id | string | - | |
+| external_user_id | string | - | |
+| display_name | string | - | |
+| idp_id | string | - | |
+
+
+
+
+### ImportDataSuccessUserMetadata
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| user_id | string | - | |
+| key | string | - | |
+
+
+
+
### IsOrgUniqueRequest
if name or domain is already in use, org is not unique
at least one argument has to be provided
diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md
index 2eac7a106c..0c5dbaa08d 100644
--- a/docs/docs/apis/proto/management.md
+++ b/docs/docs/apis/proto/management.md
@@ -5176,8 +5176,10 @@ This is an empty response
| email | ImportHumanUserRequest.Email | - | message.required: true
|
| phone | ImportHumanUserRequest.Phone | - | |
| password | string | - | |
+| hashed_password | ImportHumanUserRequest.HashedPassword | - | |
| password_change_required | bool | - | |
| request_passwordless_registration | bool | - | |
+| otp_code | string | - | |
@@ -5194,6 +5196,18 @@ This is an empty response
+### ImportHumanUserRequest.HashedPassword
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| value | string | - | |
+| algorithm | string | - | |
+
+
+
+
### ImportHumanUserRequest.Phone
diff --git a/docs/docs/apis/proto/system.md b/docs/docs/apis/proto/system.md
index 2d75c73df6..8574121251 100644
--- a/docs/docs/apis/proto/system.md
+++ b/docs/docs/apis/proto/system.md
@@ -150,8 +150,8 @@ they represent the delta of the event happend on the objects
[ClearViewResponse](#clearviewresponse)
Truncates the delta of the change stream
-be carefull with this function because ZITADEL has to
-recompute the deltas after they got cleared.
+be carefull with this function because ZITADEL has to
+recompute the deltas after they got cleared.
Search requests will return wrong results until all deltas are recomputed
@@ -165,7 +165,7 @@ Search requests will return wrong results until all deltas are recomputed
[ListFailedEventsResponse](#listfailedeventsresponse)
Returns event descriptions which cannot be processed.
-It's possible that some events need some retries.
+It's possible that some events need some retries.
For example if the SMTP-API wasn't able to send an email at the first time
@@ -180,7 +180,7 @@ For example if the SMTP-API wasn't able to send an email at the first time
Deletes the event from failed events view.
the event is not removed from the change stream
-This call is usefull if the system was able to process the event later.
+This call is usefull if the system was able to process the event later.
e.g. if the second try of sending an email was successful. the first try produced a
failed event. You can find out if it worked on the `failure_count`
diff --git a/docs/docs/apis/proto/v1.md b/docs/docs/apis/proto/v1.md
new file mode 100644
index 0000000000..b8f4638bef
--- /dev/null
+++ b/docs/docs/apis/proto/v1.md
@@ -0,0 +1,261 @@
+---
+title: zitadel/v1.proto
+---
+> This document reflects the state from API 1.0 (available from 20.04.2021)
+
+
+
+
+## Messages
+
+
+### AddCustomOrgIAMPolicyRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| org_id | string | - | string.min_len: 1
string.max_len: 200
|
+| user_login_must_be_domain | bool | the username has to end with the domain of it's organisation (uniqueness is organisation based) | |
+
+
+
+
+### DataAPIApplication
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| app_id | string | - | |
+| app | zitadel.management.v1.AddAPIAppRequest | - | |
+
+
+
+
+### DataAction
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| action_id | string | - | |
+| action | zitadel.management.v1.CreateActionRequest | - | |
+
+
+
+
+### DataHumanUser
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| user_id | string | - | |
+| user | zitadel.management.v1.ImportHumanUserRequest | - | |
+
+
+
+
+### DataJWTIDP
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| idp_id | string | - | |
+| idp | zitadel.management.v1.AddOrgJWTIDPRequest | - | |
+
+
+
+
+### DataMachineUser
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| user_id | string | - | |
+| user | zitadel.management.v1.AddMachineUserRequest | - | |
+
+
+
+
+### DataOIDCApplication
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| app_id | string | - | |
+| app | zitadel.management.v1.AddOIDCAppRequest | - | |
+
+
+
+
+### DataOIDCIDP
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| idp_id | string | - | |
+| idp | zitadel.management.v1.AddOrgOIDCIDPRequest | - | |
+
+
+
+
+### DataOrg
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| org_id | string | - | |
+| org | zitadel.management.v1.AddOrgRequest | - | |
+| iam_policy | AddCustomOrgIAMPolicyRequest | - | |
+| label_policy | zitadel.management.v1.AddCustomLabelPolicyRequest | - | |
+| lockout_policy | zitadel.management.v1.AddCustomLockoutPolicyRequest | - | |
+| login_policy | zitadel.management.v1.AddCustomLoginPolicyRequest | - | |
+| password_complexity_policy | zitadel.management.v1.AddCustomPasswordComplexityPolicyRequest | - | |
+| privacy_policy | zitadel.management.v1.AddCustomPrivacyPolicyRequest | - | |
+| projects | repeated DataProject | - | |
+| project_roles | repeated zitadel.management.v1.AddProjectRoleRequest | - | |
+| api_apps | repeated DataAPIApplication | - | |
+| oidc_apps | repeated DataOIDCApplication | - | |
+| human_users | repeated DataHumanUser | - | |
+| machine_users | repeated DataMachineUser | - | |
+| trigger_actions | repeated zitadel.management.v1.SetTriggerActionsRequest | - | |
+| actions | repeated DataAction | - | |
+| project_grants | repeated DataProjectGrant | - | |
+| user_grants | repeated zitadel.management.v1.AddUserGrantRequest | - | |
+| org_members | repeated zitadel.management.v1.AddOrgMemberRequest | - | |
+| project_members | repeated zitadel.management.v1.AddProjectMemberRequest | - | |
+| project_grant_members | repeated zitadel.management.v1.AddProjectGrantMemberRequest | - | |
+| user_metadata | repeated zitadel.management.v1.SetUserMetadataRequest | - | |
+| login_texts | repeated zitadel.management.v1.SetCustomLoginTextsRequest | - | |
+| init_messages | repeated zitadel.management.v1.SetCustomInitMessageTextRequest | - | |
+| password_reset_messages | repeated zitadel.management.v1.SetCustomPasswordResetMessageTextRequest | - | |
+| verify_email_messages | repeated zitadel.management.v1.SetCustomVerifyEmailMessageTextRequest | - | |
+| verify_phone_messages | repeated zitadel.management.v1.SetCustomVerifyPhoneMessageTextRequest | - | |
+| domain_claimed_messages | repeated zitadel.management.v1.SetCustomDomainClaimedMessageTextRequest | - | |
+| passwordless_registration_messages | repeated zitadel.management.v1.SetCustomPasswordlessRegistrationMessageTextRequest | - | |
+| oidc_idps | repeated DataOIDCIDP | - | |
+| jwt_idps | repeated DataJWTIDP | - | |
+| second_factors | repeated zitadel.management.v1.AddSecondFactorToLoginPolicyRequest | - | |
+| multi_factors | repeated zitadel.management.v1.AddMultiFactorToLoginPolicyRequest | - | |
+| idps | repeated zitadel.management.v1.AddIDPToLoginPolicyRequest | - | |
+| user_links | repeated zitadel.idp.v1.IDPUserLink | - | |
+| domains | repeated zitadel.org.v1.Domain | - | |
+
+
+
+
+### DataProject
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| project_id | string | - | |
+| project | zitadel.management.v1.AddProjectRequest | - | |
+
+
+
+
+### DataProjectGrant
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| grant_id | string | - | |
+| project_grant | zitadel.management.v1.AddProjectGrantRequest | - | |
+
+
+
+
+### ExportHumanUser
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| user_name | string | - | string.min_len: 1
string.max_len: 200
|
+| profile | ExportHumanUser.Profile | - | message.required: true
|
+| email | ExportHumanUser.Email | - | message.required: true
|
+| phone | ExportHumanUser.Phone | - | |
+| password | string | - | |
+| hashed_password | ExportHumanUser.HashedPassword | - | |
+| password_change_required | bool | - | |
+| request_passwordless_registration | bool | - | |
+| otp_code | string | - | |
+
+
+
+
+### ExportHumanUser.Email
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| email | string | TODO: check if no value is allowed | string.email: true
|
+| is_email_verified | bool | - | |
+
+
+
+
+### ExportHumanUser.HashedPassword
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| value | string | - | |
+| algorithm | string | - | |
+
+
+
+
+### ExportHumanUser.Phone
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| phone | string | has to be a global number | string.min_len: 1
string.max_len: 50
string.prefix: +
|
+| is_phone_verified | bool | - | |
+
+
+
+
+### ExportHumanUser.Profile
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| first_name | string | - | string.min_len: 1
string.max_len: 200
|
+| last_name | string | - | string.min_len: 1
string.max_len: 200
|
+| nick_name | string | - | string.max_len: 200
|
+| display_name | string | - | string.max_len: 200
|
+| preferred_language | string | - | string.max_len: 10
|
+| gender | zitadel.user.v1.Gender | - | |
+
+
+
+
+### ImportDataOrg
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| orgs | repeated DataOrg | - | |
+
+
+
+
+
+
diff --git a/docs/docs/guides/api/export-and-import.md b/docs/docs/guides/api/export-and-import.md
new file mode 100644
index 0000000000..d47df6d174
--- /dev/null
+++ b/docs/docs/guides/api/export-and-import.md
@@ -0,0 +1,129 @@
+---
+title: Export and import with ZITADEL
+---
+
+## Export from V1 to Import into V2
+
+To migrate from ZITADEL V1 to V2 the API provides you with a possibility to export all resources which are under your organizations.
+Currently, this doesn't include the following points:
+
+* Global policies
+* IAM members
+* Global IDPs
+* Global second/multi factors
+* Machine keys
+* PAT's
+* Application keys
+
+Which results in that if you want to import, and you have no defined organization-specific custom policies, the experience for your users will not be exactly like in your old instance.
+```suggestion
+
+::note Note that the ressources will be migrated without the event stream. This means that you will not have the audit trail for the imported objects.
+
+*** With this export and import the current audit trail is not included, the resources will be newly created ***
+
+### Export from V1 to import into V2 directly
+
+***To use this requests you have to have an access token with enough permissions to export and import.***
+
+To export all necessary data you only have to use one request, as an example:
+
+```bash
+curl --request POST \
+ --url {your_domain}/admin/v1/export \
+ --header 'Authorization: Bearer XXXX' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "org_ids": [ "70669144072186707", "70671105999825752" ],
+ "excluded_org_ids": [ ],
+ "with_passwords": true,
+ "with_otp": true
+ "timeout": "30s",
+ "response_output": true,
+}' -o export.json
+```
+
+* "org_ids": to select which organizations should be exported
+* "excluded_org_ids": to exclude several organization, if for example no organizations are selected
+* "with_passwords": to include the hashed_passwords of the users in the export
+* "with_otp": to include the OTP-code of the users in the export
+* "timeout": timeout of the call to export the data
+* "response_output": to output the export as response to the call
+
+***To import the exported data into you new instance, you have to have an already existing instance on a ZITADEL V2, with all desired configuration and global resources.***
+
+Then as an example you can use one request for the import:
+
+```bash
+curl --request POST \
+ --url {your_domain}/admin/v1/import \
+ --header 'Authorization: Bearer XXXX' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "data_orgsv1": '$(cat export.json)'
+}'
+```
+
+## Export from V1 to Import into V2 thorugh GCS
+
+***To use this requests you have to have an access token with enough permissions to export and import.***
+
+***The used serviceaccount has to have at least the role "Storage Object Creator" to create objects on GCS***
+
+To export all necessary data you only have to use one request which results in a file in your GCS, as an example:
+
+```bash
+curl --request POST \
+ --url {your_domain}/admin/v1/export \
+ --header 'Authorization: Bearer XXXX' \
+ --header 'Content-Type: application/json' \
+ --data ' "{
+ "org_ids": [ "70669144072186707", "70671105999825752" ],
+ "excluded_org_ids": [ ],
+ "with_passwords": true,
+ "with_otp": true,
+ "timeout": "10m",
+ "gcs_output": {
+ "path": "export.json",
+ "bucket": "caos-zitadel-exports",
+ "serviceaccount_json": "XXXX"
+ }' -o export.json
+```
+
+* "org_ids": to select which organizations should be exported
+* "excluded_org_ids": to exclude several organization, if for example no organizations are selected
+* "with_passwords": to include the hashed_passwords of the users in the export
+* "with_otp": to include the OTP-code of the users in the export
+* "timeout": timeout for the call to export the data
+* "gcs_output": to write a file into GCS as output to the call
+ * "path": path to the output file on GCS
+ * "bucket": used bucket for output on GCS
+ * "serviceaccount_json": base64-encoded serviceaccount.json used to output the file on GCS
+
+***To import the exported data into you new instance, you have to have an already existing instance on a ZITADEL V2, with all desired configuration and global resources.***
+
+***The used serviceaccount has to have at least the role "Storage Object Viewer" to create objects on GCS***
+
+Then as an example you can use one request for the import:
+
+```bash
+curl --request POST \
+ --url {your_domain}/admin/v1/import \
+ --header 'Authorization: Bearer XXXX' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "timeout": "10m",
+ "data_orgsv1_gcs": {
+ "path": "export.json",
+ "bucket": "caos-zitadel-exports",
+ "serviceaccount_json": "XXXX"
+ }
+}'
+```
+
+* "timeout": timeout for the import task
+* "data_orgsv1_gcs": to read the export from GCS directly
+ * "path": path to the exported file on GCS
+ * "bucket": used bucket to read from GCS
+ * "serviceaccount_json": base64-encoded serviceaccount.json used to read the file from GCS
+
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 271d14a54f..6b3f880432 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -100,7 +100,10 @@ module.exports = {
type: "category",
label: "API",
collapsed: false,
- items: ["guides/api/access-zitadel-apis"],
+ items: [
+ "guides/api/access-zitadel-apis",
+ "guides/api/export-and-import"
+ ],
},
{
type: "category",
diff --git a/go.mod b/go.mod
index 679d91caea..f56d089b5a 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/zitadel/zitadel
go 1.17
require (
+ cloud.google.com/go/storage v1.14.0
github.com/BurntSushi/toml v0.4.1
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.0.0
@@ -171,7 +172,7 @@ require (
golang.org/x/mod v0.5.1 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
- google.golang.org/api v0.63.0 // indirect
+ google.golang.org/api v0.63.0
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 7f10eaf680..4110d1ee0a 100644
--- a/go.sum
+++ b/go.sum
@@ -49,6 +49,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cloud.google.com/go/trace v1.0.0 h1:laKx2y7IWMjguCe5zZx6n7qLtREk4kyE69SXVC0VSN8=
cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
@@ -366,9 +367,11 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
diff --git a/internal/api/grpc/admin/domain_policy.go b/internal/api/grpc/admin/domain_policy.go
index c86ca13723..b7628d33bb 100644
--- a/internal/api/grpc/admin/domain_policy.go
+++ b/internal/api/grpc/admin/domain_policy.go
@@ -27,7 +27,7 @@ func (s *Server) GetCustomDomainPolicy(ctx context.Context, req *admin_pb.GetCus
}
func (s *Server) AddCustomDomainPolicy(ctx context.Context, req *admin_pb.AddCustomDomainPolicyRequest) (*admin_pb.AddCustomDomainPolicyResponse, error) {
- policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, domainPolicyToDomain(req.UserLoginMustBeDomain, req.ValidateOrgDomains, req.SmtpSenderAddressMatchesInstanceDomain))
+ policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, DomainPolicyToDomain(req.UserLoginMustBeDomain, req.ValidateOrgDomains, req.SmtpSenderAddressMatchesInstanceDomain))
if err != nil {
return nil, err
}
@@ -76,7 +76,7 @@ func (s *Server) ResetCustomDomainPolicyToDefault(ctx context.Context, req *admi
return &admin_pb.ResetCustomDomainPolicyToDefaultResponse{Details: object.DomainToChangeDetailsPb(details)}, nil
}
-func domainPolicyToDomain(userLoginMustBeDomain, validateOrgDomains, smtpSenderAddressMatchesInstanceDomain bool) *domain.DomainPolicy {
+func DomainPolicyToDomain(userLoginMustBeDomain, validateOrgDomains, smtpSenderAddressMatchesInstanceDomain bool) *domain.DomainPolicy {
return &domain.DomainPolicy{
UserLoginMustBeDomain: userLoginMustBeDomain,
ValidateOrgDomains: validateOrgDomains,
@@ -104,7 +104,7 @@ func updateCustomDomainPolicyToDomain(req *admin_pb.UpdateCustomDomainPolicyRequ
}
func (s *Server) AddCustomOrgIAMPolicy(ctx context.Context, req *admin_pb.AddCustomOrgIAMPolicyRequest) (*admin_pb.AddCustomOrgIAMPolicyResponse, error) {
- policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, domainPolicyToDomain(req.UserLoginMustBeDomain, true, true))
+ policy, err := s.command.AddOrgDomainPolicy(ctx, req.OrgId, DomainPolicyToDomain(req.UserLoginMustBeDomain, true, true))
if err != nil {
return nil, err
}
diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go
new file mode 100644
index 0000000000..e7d637ac2f
--- /dev/null
+++ b/internal/api/grpc/admin/export.go
@@ -0,0 +1,1151 @@
+package admin
+
+import (
+ "context"
+ text_grpc "github.com/zitadel/zitadel/internal/api/grpc/text"
+ "github.com/zitadel/zitadel/internal/domain"
+ caos_errors "github.com/zitadel/zitadel/internal/errors"
+ "github.com/zitadel/zitadel/internal/query"
+ "github.com/zitadel/zitadel/internal/telemetry/tracing"
+ action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
+ admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
+ app_pb "github.com/zitadel/zitadel/pkg/grpc/app"
+ idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
+ management_pb "github.com/zitadel/zitadel/pkg/grpc/management"
+ org_pb "github.com/zitadel/zitadel/pkg/grpc/org"
+ policy_pb "github.com/zitadel/zitadel/pkg/grpc/policy"
+ project_pb "github.com/zitadel/zitadel/pkg/grpc/project"
+ user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
+ v1_pb "github.com/zitadel/zitadel/pkg/grpc/v1"
+ "google.golang.org/protobuf/types/known/durationpb"
+)
+
+func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest) (_ *admin_pb.ExportDataResponse, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ orgSearchQuery := &query.OrgSearchQueries{}
+ if len(req.OrgIds) > 0 {
+ orgIDsSearchQuery, err := query.NewOrgIDsSearchQuery(req.OrgIds...)
+ if err != nil {
+ return nil, err
+ }
+ orgSearchQuery.Queries = []query.SearchQuery{orgIDsSearchQuery}
+ }
+ queriedOrgs, err := s.query.SearchOrgs(ctx, orgSearchQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ orgs := make([]*admin_pb.DataOrg, len(queriedOrgs.Orgs))
+ processedOrgs := make([]string, len(queriedOrgs.Orgs))
+ processedProjects := make([]string, 0)
+ processedGrants := make([]string, 0)
+ processedUsers := make([]string, 0)
+ processedActions := make([]string, 0)
+
+ for i, queriedOrg := range queriedOrgs.Orgs {
+ if req.ExcludedOrgIds != nil {
+ found := false
+ for _, excludedOrg := range req.ExcludedOrgIds {
+ if excludedOrg == queriedOrg.ID {
+ found = true
+ }
+ }
+ if found {
+ continue
+ }
+ }
+ processedOrgs = append(processedOrgs, queriedOrg.ID)
+
+ /******************************************************************************************************************
+ Organization
+ ******************************************************************************************************************/
+ org := &admin_pb.DataOrg{OrgId: queriedOrg.ID, Org: &management_pb.AddOrgRequest{Name: queriedOrg.Name}}
+ orgs[i] = org
+ }
+
+ for _, org := range orgs {
+
+ org.DomainPolicy, err = s.getDomainPolicy(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.Domains, err = s.getDomains(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.OidcIdps, org.JwtIdps, err = s.getIDPs(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.LabelPolicy, err = s.getLabelPolicy(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.LoginPolicy, err = s.getLoginPolicy(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.UserLinks, err = s.getUserLinks(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.LockoutPolicy, err = s.getLockoutPolicy(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.PasswordComplexityPolicy, err = s.getPasswordComplexityPolicy(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ org.PrivacyPolicy, err = s.getPrivacyPolicy(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+
+ langResp, err := s.GetSupportedLanguages(ctx, &admin_pb.GetSupportedLanguagesRequest{})
+ if err != nil {
+ return nil, err
+ }
+
+ org.LoginTexts, err = s.getCustomLoginTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ org.InitMessages, err = s.getCustomInitMessageTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ org.PasswordResetMessages, err = s.getCustomPasswordResetMessageTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ org.VerifyEmailMessages, err = s.getCustomVerifyEmailMessageTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ org.VerifyPhoneMessages, err = s.getCustomVerifyPhoneMessageTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ org.DomainClaimedMessages, err = s.getCustomDomainClaimedMessageTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ org.PasswordlessRegistrationMessages, err = s.getCustomPasswordlessRegistrationMessageTexts(ctx, org.GetOrgId(), langResp.Languages)
+ if err != nil {
+ return nil, err
+ }
+
+ /******************************************************************************************************************
+ Users
+ ******************************************************************************************************************/
+ org.HumanUsers, org.MachineUsers, org.UserMetadata, err = s.getUsers(ctx, org.GetOrgId(), req.WithPasswords, req.WithOtp)
+ if err != nil {
+ return nil, err
+ }
+ for _, processedUser := range org.HumanUsers {
+ processedUsers = append(processedUsers, processedUser.UserId)
+ }
+ for _, processedUser := range org.MachineUsers {
+ processedUsers = append(processedUsers, processedUser.UserId)
+ }
+
+ /******************************************************************************************************************
+ Project and Applications
+ ******************************************************************************************************************/
+ org.Projects, org.ProjectRoles, org.OidcApps, org.ApiApps, err = s.getProjectsAndApps(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+ for _, processedProject := range org.Projects {
+ processedProjects = append(processedProjects, processedProject.ProjectId)
+ }
+
+ /******************************************************************************************************************
+ Actions
+ ******************************************************************************************************************/
+ org.Actions, err = s.getActions(ctx, org.GetOrgId())
+ if err != nil {
+ return nil, err
+ }
+ for _, processedAction := range org.Actions {
+ processedActions = append(processedActions, processedAction.ActionId)
+ }
+ }
+
+ for _, org := range orgs {
+ /******************************************************************************************************************
+ Flows
+ ******************************************************************************************************************/
+ org.TriggerActions, err = s.getTriggerActions(ctx, org.OrgId, processedActions)
+ if err != nil {
+ return nil, err
+ }
+
+ /******************************************************************************************************************
+ Grants
+ ******************************************************************************************************************/
+ org.ProjectGrants, err = s.getNecessaryProjectGrantsForOrg(ctx, org.OrgId, processedOrgs, processedProjects)
+ if err != nil {
+ return nil, err
+ }
+ for _, processedGrant := range org.ProjectGrants {
+ processedGrants = append(processedGrants, processedGrant.GrantId)
+ }
+
+ org.UserGrants, err = s.getNecessaryUserGrantsForOrg(ctx, org.OrgId, processedProjects, processedGrants, processedUsers)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ for _, org := range orgs {
+ /******************************************************************************************************************
+ Members
+ ******************************************************************************************************************/
+ org.OrgMembers, err = s.getNecessaryOrgMembersForOrg(ctx, org.OrgId, processedUsers)
+ if err != nil {
+ return nil, err
+ }
+
+ org.ProjectMembers, err = s.getNecessaryProjectMembersForOrg(ctx, processedProjects, processedUsers)
+ if err != nil {
+ return nil, err
+ }
+
+ org.ProjectGrantMembers, err = s.getNecessaryProjectGrantMembersForOrg(ctx, org.OrgId, processedProjects, processedGrants, processedUsers)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &admin_pb.ExportDataResponse{
+ Orgs: orgs,
+ }, nil
+}
+
+func (s *Server) getDomainPolicy(ctx context.Context, orgID string) (_ *admin_pb.AddCustomDomainPolicyRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ queriedDomain, err := s.query.DomainPolicyByOrg(ctx, true, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if !queriedDomain.IsDefault {
+ return &admin_pb.AddCustomDomainPolicyRequest{
+ OrgId: orgID,
+ UserLoginMustBeDomain: queriedDomain.UserLoginMustBeDomain,
+ ValidateOrgDomains: queriedDomain.ValidateOrgDomains,
+ SmtpSenderAddressMatchesInstanceDomain: queriedDomain.SMTPSenderAddressMatchesInstanceDomain,
+ }, nil
+ }
+ return nil, nil
+}
+
+func (s *Server) getDomains(ctx context.Context, orgID string) (_ []*org_pb.Domain, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ orgDomainOrgIDQuery, err := query.NewOrgDomainOrgIDSearchQuery(orgID)
+ if err != nil {
+ return nil, err
+ }
+ orgDomainsQuery, err := s.query.SearchOrgDomains(ctx, &query.OrgDomainSearchQueries{Queries: []query.SearchQuery{orgDomainOrgIDQuery}})
+ if err != nil {
+ return nil, err
+ }
+ orgDomains := make([]*org_pb.Domain, len(orgDomainsQuery.Domains))
+ for i, orgDomain := range orgDomainsQuery.Domains {
+ orgDomains[i] = &org_pb.Domain{
+ OrgId: orgDomain.OrgID,
+ DomainName: orgDomain.Domain,
+ IsVerified: orgDomain.IsVerified,
+ IsPrimary: orgDomain.IsPrimary,
+ ValidationType: org_pb.DomainValidationType(orgDomain.ValidationType),
+ }
+ }
+ return orgDomains, nil
+}
+
+func (s *Server) getIDPs(ctx context.Context, orgID string) (_ []*v1_pb.DataOIDCIDP, _ []*v1_pb.DataJWTIDP, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ ownerType, err := query.NewIDPOwnerTypeSearchQuery(domain.IdentityProviderTypeOrg)
+ if err != nil {
+ return nil, nil, err
+ }
+ idpQuery, err := query.NewIDPResourceOwnerSearchQuery(orgID)
+ if err != nil {
+ return nil, nil, err
+ }
+ idps, err := s.query.IDPs(ctx, &query.IDPSearchQueries{Queries: []query.SearchQuery{idpQuery, ownerType}})
+ if err != nil {
+ return nil, nil, err
+ }
+ oidcIdps := make([]*v1_pb.DataOIDCIDP, 0)
+ jwtIdps := make([]*v1_pb.DataJWTIDP, 0)
+ for _, idp := range idps.IDPs {
+ if idp.OIDCIDP != nil {
+ clientSecret, err := s.query.GetOIDCIDPClientSecret(ctx, false, orgID, idp.ID)
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, err
+ }
+ oidcIdps = append(oidcIdps, &v1_pb.DataOIDCIDP{
+ IdpId: idp.ID,
+ Idp: &management_pb.AddOrgOIDCIDPRequest{
+ Name: idp.Name,
+ StylingType: idp_pb.IDPStylingType(idp.StylingType),
+ ClientId: idp.ClientID,
+ ClientSecret: clientSecret,
+ Issuer: idp.OIDCIDP.Issuer,
+ Scopes: idp.Scopes,
+ DisplayNameMapping: idp_pb.OIDCMappingField(idp.DisplayNameMapping),
+ UsernameMapping: idp_pb.OIDCMappingField(idp.UsernameMapping),
+ AutoRegister: idp.AutoRegister,
+ },
+ })
+ } else if idp.JWTIDP != nil {
+ jwtIdps = append(jwtIdps, &v1_pb.DataJWTIDP{
+ IdpId: idp.ID,
+ Idp: &management_pb.AddOrgJWTIDPRequest{
+ Name: idp.Name,
+ StylingType: idp_pb.IDPStylingType(idp.StylingType),
+ JwtEndpoint: idp.JWTIDP.Endpoint,
+ Issuer: idp.JWTIDP.Issuer,
+ KeysEndpoint: idp.KeysEndpoint,
+ HeaderName: idp.HeaderName,
+ AutoRegister: idp.AutoRegister,
+ },
+ })
+ }
+ }
+ return oidcIdps, jwtIdps, nil
+}
+
+func (s *Server) getLabelPolicy(ctx context.Context, orgID string) (_ *management_pb.AddCustomLabelPolicyRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ queriedLabel, err := s.query.ActiveLabelPolicyByOrg(ctx, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if !queriedLabel.IsDefault {
+ return &management_pb.AddCustomLabelPolicyRequest{
+ PrimaryColor: queriedLabel.Light.PrimaryColor,
+ HideLoginNameSuffix: queriedLabel.HideLoginNameSuffix,
+ WarnColor: queriedLabel.Light.WarnColor,
+ BackgroundColor: queriedLabel.Light.BackgroundColor,
+ FontColor: queriedLabel.Light.FontColor,
+ PrimaryColorDark: queriedLabel.Dark.PrimaryColor,
+ BackgroundColorDark: queriedLabel.Dark.BackgroundColor,
+ WarnColorDark: queriedLabel.Dark.WarnColor,
+ FontColorDark: queriedLabel.Dark.FontColor,
+ DisableWatermark: queriedLabel.WatermarkDisabled,
+ }, nil
+ }
+ return nil, nil
+}
+
+func (s *Server) getLoginPolicy(ctx context.Context, orgID string) (_ *management_pb.AddCustomLoginPolicyRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ queriedLogin, err := s.query.LoginPolicyByID(ctx, false, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if !queriedLogin.IsDefault {
+ pwCheck := durationpb.New(queriedLogin.PasswordCheckLifetime)
+ externalLogin := durationpb.New(queriedLogin.ExternalLoginCheckLifetime)
+ mfaInitSkip := durationpb.New(queriedLogin.MFAInitSkipLifetime)
+ secondFactor := durationpb.New(queriedLogin.SecondFactorCheckLifetime)
+ multiFactor := durationpb.New(queriedLogin.MultiFactorCheckLifetime)
+
+ secondFactors := []policy_pb.SecondFactorType{}
+ for _, factor := range queriedLogin.SecondFactors {
+ secondFactors = append(secondFactors, policy_pb.SecondFactorType(factor))
+ }
+
+ multiFactors := []policy_pb.MultiFactorType{}
+ for _, factor := range queriedLogin.MultiFactors {
+ multiFactors = append(multiFactors, policy_pb.MultiFactorType(factor))
+ }
+
+ idpLinksQuery, err := s.query.IDPLoginPolicyLinks(ctx, orgID, &query.IDPLoginPolicyLinksSearchQuery{})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, err
+ }
+ idpLinks := make([]*management_pb.AddCustomLoginPolicyRequest_IDP, 0)
+ if !caos_errors.IsNotFound(err) && idpLinksQuery != nil {
+ for _, idpLink := range idpLinksQuery.Links {
+ idpLinks = append(idpLinks, &management_pb.AddCustomLoginPolicyRequest_IDP{
+ IdpId: idpLink.IDPID,
+ OwnerType: idp_pb.IDPOwnerType(idpLink.IDPType),
+ })
+ }
+ }
+
+ return &management_pb.AddCustomLoginPolicyRequest{
+ AllowUsernamePassword: queriedLogin.AllowUsernamePassword,
+ AllowRegister: queriedLogin.AllowRegister,
+ AllowExternalIdp: queriedLogin.AllowExternalIDPs,
+ ForceMfa: queriedLogin.ForceMFA,
+ PasswordlessType: policy_pb.PasswordlessType(queriedLogin.PasswordlessType),
+ HidePasswordReset: queriedLogin.HidePasswordReset,
+ IgnoreUnknownUsernames: queriedLogin.IgnoreUnknownUsernames,
+ DefaultRedirectUri: queriedLogin.DefaultRedirectURI,
+ PasswordCheckLifetime: pwCheck,
+ ExternalLoginCheckLifetime: externalLogin,
+ MfaInitSkipLifetime: mfaInitSkip,
+ SecondFactorCheckLifetime: secondFactor,
+ MultiFactorCheckLifetime: multiFactor,
+ SecondFactors: secondFactors,
+ MultiFactors: multiFactors,
+ Idps: idpLinks,
+ }, nil
+ }
+
+ return nil, nil
+}
+
+func (s *Server) getUserLinks(ctx context.Context, orgID string) (_ []*idp_pb.IDPUserLink, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ userLinksResourceOwner, err := query.NewIDPUserLinksResourceOwnerSearchQuery(orgID)
+ if err != nil {
+ return nil, err
+ }
+ idpUserLinks, err := s.query.IDPUserLinks(ctx, &query.IDPUserLinksSearchQuery{Queries: []query.SearchQuery{userLinksResourceOwner}})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, err
+ }
+ userLinks := make([]*idp_pb.IDPUserLink, 0)
+ if !caos_errors.IsNotFound(err) && idpUserLinks != nil {
+ for _, idpUserLink := range idpUserLinks.Links {
+ userLinks = append(userLinks, &idp_pb.IDPUserLink{
+ UserId: idpUserLink.UserID,
+ IdpId: idpUserLink.IDPID,
+ IdpName: idpUserLink.IDPName,
+ ProvidedUserId: idpUserLink.ProvidedUserID,
+ ProvidedUserName: idpUserLink.ProvidedUsername,
+ IdpType: idp_pb.IDPType(idpUserLink.IDPType),
+ })
+ }
+ }
+
+ return userLinks, nil
+}
+
+func (s *Server) getLockoutPolicy(ctx context.Context, orgID string) (_ *management_pb.AddCustomLockoutPolicyRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ queriedLockout, err := s.query.LockoutPolicyByOrg(ctx, false, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if !queriedLockout.IsDefault {
+ return &management_pb.AddCustomLockoutPolicyRequest{
+ MaxPasswordAttempts: uint32(queriedLockout.MaxPasswordAttempts),
+ }, nil
+ }
+ return nil, nil
+}
+
+func (s *Server) getPasswordComplexityPolicy(ctx context.Context, orgID string) (_ *management_pb.AddCustomPasswordComplexityPolicyRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ queriedPasswordComplexity, err := s.query.PasswordComplexityPolicyByOrg(ctx, false, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if !queriedPasswordComplexity.IsDefault {
+ return &management_pb.AddCustomPasswordComplexityPolicyRequest{
+ MinLength: queriedPasswordComplexity.MinLength,
+ HasUppercase: queriedPasswordComplexity.HasUppercase,
+ HasLowercase: queriedPasswordComplexity.HasLowercase,
+ HasNumber: queriedPasswordComplexity.HasNumber,
+ HasSymbol: queriedPasswordComplexity.HasSymbol,
+ }, nil
+ }
+ return nil, nil
+}
+
+func (s *Server) getPrivacyPolicy(ctx context.Context, orgID string) (_ *management_pb.AddCustomPrivacyPolicyRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ queriedPrivacy, err := s.query.PrivacyPolicyByOrg(ctx, false, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if !queriedPrivacy.IsDefault {
+ return &management_pb.AddCustomPrivacyPolicyRequest{
+ TosLink: queriedPrivacy.TOSLink,
+ PrivacyLink: queriedPrivacy.PrivacyLink,
+ HelpLink: queriedPrivacy.HelpLink,
+ }, nil
+ }
+ return nil, nil
+}
+
+func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, withOTP bool) (_ []*v1_pb.DataHumanUser, _ []*v1_pb.DataMachineUser, _ []*management_pb.SetUserMetadataRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ orgSearch, err := query.NewUserResourceOwnerSearchQuery(org, query.TextEquals)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{orgSearch}})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, nil, err
+ }
+ humanUsers := make([]*v1_pb.DataHumanUser, 0)
+ machineUsers := make([]*v1_pb.DataMachineUser, 0)
+ userMetadata := make([]*management_pb.SetUserMetadataRequest, 0)
+ if err != nil && caos_errors.IsNotFound(err) {
+ return humanUsers, machineUsers, userMetadata, nil
+ }
+ for _, user := range users.Users {
+ switch user.Type {
+ case domain.UserTypeHuman:
+ dataUser := &v1_pb.DataHumanUser{
+ UserId: user.ID,
+ User: &management_pb.ImportHumanUserRequest{
+ UserName: user.Username,
+ Profile: &management_pb.ImportHumanUserRequest_Profile{
+ FirstName: user.Human.FirstName,
+ LastName: user.Human.LastName,
+ NickName: user.Human.NickName,
+ DisplayName: user.Human.DisplayName,
+ PreferredLanguage: user.Human.PreferredLanguage.String(),
+ Gender: user_pb.Gender(user.Human.Gender),
+ },
+ },
+ }
+ if user.Human.Email != "" {
+ dataUser.User.Email = &management_pb.ImportHumanUserRequest_Email{
+ Email: user.Human.Email,
+ IsEmailVerified: user.Human.IsEmailVerified,
+ }
+ }
+ if user.Human.Phone != "" {
+ dataUser.User.Phone = &management_pb.ImportHumanUserRequest_Phone{
+ Phone: user.Human.Phone,
+ IsPhoneVerified: user.Human.IsPhoneVerified,
+ }
+ }
+ if withPasswords {
+ ctx, pwspan := tracing.NewSpan(ctx)
+ hashedPassword, hashAlgorithm, err := s.query.GetHumanPassword(ctx, org, user.ID)
+ pwspan.EndWithError(err)
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, nil, err
+ }
+ if err == nil && hashedPassword != nil {
+ dataUser.User.HashedPassword = &management_pb.ImportHumanUserRequest_HashedPassword{
+ Value: string(hashedPassword),
+ Algorithm: hashAlgorithm,
+ }
+ }
+ }
+ if withOTP {
+ ctx, otpspan := tracing.NewSpan(ctx)
+ code, err := s.query.GetHumanOTPSecret(ctx, user.ID, org)
+ otpspan.EndWithError(err)
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, nil, err
+ }
+ if err == nil && code != "" {
+ dataUser.User.OtpCode = code
+ }
+ }
+
+ humanUsers = append(humanUsers, dataUser)
+ case domain.UserTypeMachine:
+ machineUsers = append(machineUsers, &v1_pb.DataMachineUser{
+ UserId: user.ID,
+ User: &management_pb.AddMachineUserRequest{
+ UserName: user.Username,
+ Name: user.Machine.Name,
+ Description: user.Machine.Description,
+ },
+ })
+ }
+
+ ctx, metaspan := tracing.NewSpan(ctx)
+ metadataOrgSearch, err := query.NewUserMetadataResourceOwnerSearchQuery(org)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ metadataList, err := s.query.SearchUserMetadata(ctx, false, user.ID, &query.UserMetadataSearchQueries{Queries: []query.SearchQuery{metadataOrgSearch}})
+ metaspan.EndWithError(err)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ for _, metadata := range metadataList.Metadata {
+ userMetadata = append(userMetadata, &management_pb.SetUserMetadataRequest{
+ Id: user.ID,
+ Key: metadata.Key,
+ Value: metadata.Value,
+ })
+ }
+ }
+ return humanUsers, machineUsers, userMetadata, nil
+}
+
+func (s *Server) getTriggerActions(ctx context.Context, org string, processedActions []string) (_ []*management_pb.SetTriggerActionsRequest, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+ flowTypes := []domain.FlowType{domain.FlowTypeExternalAuthentication}
+ triggerActions := make([]*management_pb.SetTriggerActionsRequest, 0)
+
+ for _, flowType := range flowTypes {
+ flow, err := s.query.GetFlow(ctx, flowType, org)
+ if err != nil {
+ return nil, err
+ }
+
+ for triggerType, triggerAction := range flow.TriggerActions {
+ actions := make([]string, 0)
+ for _, action := range triggerAction {
+ for _, actionID := range processedActions {
+ if action.ID == actionID {
+ actions = append(actions, action.ID)
+ }
+ }
+ }
+
+ triggerActions = append(triggerActions, &management_pb.SetTriggerActionsRequest{
+ FlowType: action_pb.FlowType(flowType),
+ TriggerType: action_pb.TriggerType(triggerType),
+ ActionIds: actions,
+ })
+ }
+ }
+ return triggerActions, nil
+}
+
+func (s *Server) getActions(ctx context.Context, org string) ([]*v1_pb.DataAction, error) {
+ actionSearch, err := query.NewActionResourceOwnerQuery(org)
+ if err != nil {
+ return nil, err
+ }
+ queriedActions, err := s.query.SearchActions(ctx, &query.ActionSearchQueries{Queries: []query.SearchQuery{actionSearch}})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, err
+ }
+ actions := make([]*v1_pb.DataAction, len(queriedActions.Actions))
+ if err != nil && caos_errors.IsNotFound(err) {
+ return actions, nil
+ }
+ for i, action := range queriedActions.Actions {
+ timeout := durationpb.New(action.Timeout)
+
+ actions[i] = &v1_pb.DataAction{
+ ActionId: action.ID,
+ Action: &management_pb.CreateActionRequest{
+ Name: action.Name,
+ Script: action.Script,
+ Timeout: timeout,
+ AllowedToFail: action.AllowedToFail,
+ },
+ }
+ }
+
+ return actions, nil
+}
+
+func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.DataProject, []*management_pb.AddProjectRoleRequest, []*v1_pb.DataOIDCApplication, []*v1_pb.DataAPIApplication, error) {
+ projectSearch, err := query.NewProjectResourceOwnerSearchQuery(org)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ queriedProjects, err := s.query.SearchProjects(ctx, &query.ProjectSearchQueries{Queries: []query.SearchQuery{projectSearch}})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, nil, nil, err
+ }
+
+ projects := make([]*v1_pb.DataProject, len(queriedProjects.Projects))
+ orgProjectRoles := make([]*management_pb.AddProjectRoleRequest, 0)
+ oidcApps := make([]*v1_pb.DataOIDCApplication, 0)
+ apiApps := make([]*v1_pb.DataAPIApplication, 0)
+ if err != nil && caos_errors.IsNotFound(err) {
+ return projects, orgProjectRoles, oidcApps, apiApps, nil
+ }
+ for i, queriedProject := range queriedProjects.Projects {
+ projects[i] = &v1_pb.DataProject{
+ ProjectId: queriedProject.ID,
+ Project: &management_pb.AddProjectRequest{
+ Name: queriedProject.Name,
+ ProjectRoleAssertion: queriedProject.ProjectRoleAssertion,
+ ProjectRoleCheck: queriedProject.ProjectRoleCheck,
+ HasProjectCheck: queriedProject.HasProjectCheck,
+ PrivateLabelingSetting: project_pb.PrivateLabelingSetting(queriedProject.PrivateLabelingSetting),
+ },
+ }
+
+ projectRoleSearch, err := query.NewProjectRoleProjectIDSearchQuery(queriedProject.ID)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ queriedProjectRoles, err := s.query.SearchProjectRoles(ctx, false, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectRoleSearch}})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, nil, nil, err
+ }
+ if queriedProjectRoles != nil {
+ for _, role := range queriedProjectRoles.ProjectRoles {
+ orgProjectRoles = append(orgProjectRoles, &management_pb.AddProjectRoleRequest{
+ ProjectId: role.ProjectID,
+ RoleKey: role.Key,
+ DisplayName: role.DisplayName,
+ Group: role.Group,
+ })
+ }
+ }
+
+ appSearch, err := query.NewAppProjectIDSearchQuery(queriedProject.ID)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+ apps, err := s.query.SearchApps(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{appSearch}})
+ if err != nil && !caos_errors.IsNotFound(err) {
+ return nil, nil, nil, nil, err
+ }
+ if apps != nil {
+ for _, app := range apps.Apps {
+ if app.OIDCConfig != nil {
+ responseTypes := make([]app_pb.OIDCResponseType, 0)
+ for _, ty := range app.OIDCConfig.ResponseTypes {
+ responseTypes = append(responseTypes, app_pb.OIDCResponseType(ty))
+ }
+
+ grantTypes := make([]app_pb.OIDCGrantType, 0)
+ for _, ty := range app.OIDCConfig.GrantTypes {
+ grantTypes = append(grantTypes, app_pb.OIDCGrantType(ty))
+ }
+
+ oidcApps = append(oidcApps, &v1_pb.DataOIDCApplication{
+ AppId: app.ID,
+ App: &management_pb.AddOIDCAppRequest{
+ ProjectId: app.ProjectID,
+ Name: app.Name,
+ RedirectUris: app.OIDCConfig.RedirectURIs,
+ ResponseTypes: responseTypes,
+ GrantTypes: grantTypes,
+ AppType: app_pb.OIDCAppType(app.OIDCConfig.AppType),
+ AuthMethodType: app_pb.OIDCAuthMethodType(app.OIDCConfig.AuthMethodType),
+ PostLogoutRedirectUris: app.OIDCConfig.PostLogoutRedirectURIs,
+ Version: app_pb.OIDCVersion(app.OIDCConfig.Version),
+ DevMode: app.OIDCConfig.IsDevMode,
+ AccessTokenType: app_pb.OIDCTokenType(app.OIDCConfig.AccessTokenType),
+ AccessTokenRoleAssertion: app.OIDCConfig.AssertAccessTokenRole,
+ IdTokenRoleAssertion: app.OIDCConfig.AssertIDTokenRole,
+ IdTokenUserinfoAssertion: app.OIDCConfig.AssertIDTokenUserinfo,
+ ClockSkew: durationpb.New(app.OIDCConfig.ClockSkew),
+ AdditionalOrigins: app.OIDCConfig.AdditionalOrigins,
+ },
+ })
+ }
+ if app.APIConfig != nil {
+ apiApps = append(apiApps, &v1_pb.DataAPIApplication{
+ AppId: app.ID,
+ App: &management_pb.AddAPIAppRequest{
+ ProjectId: app.ProjectID,
+ Name: app.Name,
+ AuthMethodType: app_pb.APIAuthMethodType(app.APIConfig.AuthMethodType),
+ },
+ })
+ }
+ }
+ }
+ }
+ return projects, orgProjectRoles, oidcApps, apiApps, nil
+}
+
+func (s *Server) getNecessaryProjectGrantMembersForOrg(ctx context.Context, org string, processedProjects []string, processedGrants []string, processedUsers []string) ([]*management_pb.AddProjectGrantMemberRequest, error) {
+ projectMembers := make([]*management_pb.AddProjectGrantMemberRequest, 0)
+
+ for _, projectID := range processedProjects {
+ for _, grantID := range processedGrants {
+ search, err := query.NewMemberResourceOwnerSearchQuery(org)
+ if err != nil {
+ return nil, err
+ }
+
+ queriedProjectMembers, err := s.query.ProjectGrantMembers(ctx, &query.ProjectGrantMembersQuery{ProjectID: projectID, OrgID: org, GrantID: grantID, MembersQuery: query.MembersQuery{Queries: []query.SearchQuery{search}}})
+ if err != nil {
+ return nil, err
+ }
+ for _, projectMember := range queriedProjectMembers.Members {
+ for _, userID := range processedUsers {
+ if userID == projectMember.UserID {
+
+ projectMembers = append(projectMembers, &management_pb.AddProjectGrantMemberRequest{
+ ProjectId: projectID,
+ UserId: userID,
+ GrantId: grantID,
+ Roles: projectMember.Roles,
+ })
+ break
+ }
+ }
+
+ }
+ }
+ }
+ return projectMembers, nil
+}
+
+func (s *Server) getNecessaryProjectMembersForOrg(ctx context.Context, processedProjects []string, processedUsers []string) ([]*management_pb.AddProjectMemberRequest, error) {
+ projectMembers := make([]*management_pb.AddProjectMemberRequest, 0)
+
+ for _, projectID := range processedProjects {
+ queriedProjectMembers, err := s.query.ProjectMembers(ctx, &query.ProjectMembersQuery{ProjectID: projectID})
+ if err != nil {
+ return nil, err
+ }
+ for _, projectMember := range queriedProjectMembers.Members {
+ for _, userID := range processedUsers {
+ if userID == projectMember.UserID {
+ projectMembers = append(projectMembers, &management_pb.AddProjectMemberRequest{
+ ProjectId: projectID,
+ UserId: userID,
+ Roles: projectMember.Roles,
+ })
+ break
+ }
+ }
+ }
+ }
+ return projectMembers, nil
+}
+
+func (s *Server) getNecessaryOrgMembersForOrg(ctx context.Context, org string, processedUsers []string) ([]*management_pb.AddOrgMemberRequest, error) {
+ queriedOrgMembers, err := s.query.OrgMembers(ctx, &query.OrgMembersQuery{OrgID: org})
+ if err != nil {
+ return nil, err
+ }
+ orgMembers := make([]*management_pb.AddOrgMemberRequest, 0, len(queriedOrgMembers.Members))
+ for _, orgMember := range queriedOrgMembers.Members {
+ for _, userID := range processedUsers {
+ if userID == orgMember.UserID {
+ orgMembers = append(orgMembers, &management_pb.AddOrgMemberRequest{
+ UserId: orgMember.UserID,
+ Roles: orgMember.Roles,
+ })
+ break
+ }
+ }
+ }
+ return orgMembers, nil
+}
+
+func (s *Server) getNecessaryProjectGrantsForOrg(ctx context.Context, org string, processedOrgs []string, processedProjects []string) ([]*v1_pb.DataProjectGrant, error) {
+
+ projectGrantSearchOrg, err := query.NewProjectGrantResourceOwnerSearchQuery(org)
+ if err != nil {
+ return nil, err
+ }
+ queriedProjectGrants, err := s.query.SearchProjectGrants(ctx, &query.ProjectGrantSearchQueries{Queries: []query.SearchQuery{projectGrantSearchOrg}})
+ if err != nil {
+ return nil, err
+ }
+ projectGrants := make([]*v1_pb.DataProjectGrant, 0, len(queriedProjectGrants.ProjectGrants))
+ for _, projectGrant := range queriedProjectGrants.ProjectGrants {
+ for _, projectID := range processedProjects {
+ if projectID == projectGrant.ProjectID {
+ foundOrg := false
+ for _, orgID := range processedOrgs {
+ if orgID == projectGrant.GrantedOrgID {
+ projectGrants = append(projectGrants, &v1_pb.DataProjectGrant{
+ GrantId: projectGrant.GrantID,
+ ProjectGrant: &management_pb.AddProjectGrantRequest{
+ ProjectId: projectGrant.ProjectID,
+ GrantedOrgId: projectGrant.GrantedOrgID,
+ RoleKeys: projectGrant.GrantedRoleKeys,
+ },
+ })
+ foundOrg = true
+ break
+ }
+ }
+ if foundOrg {
+ break
+ }
+ }
+ }
+ }
+ return projectGrants, nil
+}
+
+func (s *Server) getNecessaryUserGrantsForOrg(ctx context.Context, org string, processedProjects []string, processedGrants []string, processedUsers []string) ([]*management_pb.AddUserGrantRequest, error) {
+ userGrantSearchOrg, err := query.NewUserGrantResourceOwnerSearchQuery(org)
+ if err != nil {
+ return nil, err
+ }
+
+ queriedUserGrants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{Queries: []query.SearchQuery{userGrantSearchOrg}})
+ if err != nil {
+ return nil, err
+ }
+ userGrants := make([]*management_pb.AddUserGrantRequest, 0, len(queriedUserGrants.UserGrants))
+ for _, userGrant := range queriedUserGrants.UserGrants {
+ for _, projectID := range processedProjects {
+ if projectID == userGrant.ProjectID {
+ //if usergrant is on a granted project
+ if userGrant.GrantID != "" {
+ for _, grantID := range processedGrants {
+ if grantID == userGrant.GrantID {
+ for _, userID := range processedUsers {
+ if userID == userGrant.UserID {
+ userGrants = append(userGrants, &management_pb.AddUserGrantRequest{
+ UserId: userGrant.UserID,
+ ProjectId: userGrant.ProjectID,
+ ProjectGrantId: userGrant.GrantID,
+ RoleKeys: userGrant.Roles,
+ })
+ }
+ }
+ }
+ }
+ } else {
+ for _, userID := range processedUsers {
+ if userID == userGrant.UserID {
+ userGrants = append(userGrants, &management_pb.AddUserGrantRequest{
+ UserId: userGrant.UserID,
+ ProjectId: userGrant.ProjectID,
+ ProjectGrantId: userGrant.GrantID,
+ RoleKeys: userGrant.Roles,
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+ return userGrants, nil
+}
+func (s *Server) getCustomLoginTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomLoginTextsRequest, error) {
+ customTexts := make([]*management_pb.SetCustomLoginTextsRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.GetCustomLoginTexts(ctx, org, lang)
+ if err != nil {
+ return nil, err
+ }
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomLoginTextsRequest{
+ Language: lang,
+ SelectAccountText: text_grpc.SelectAccountScreenToPb(text.SelectAccount),
+ LoginText: text_grpc.LoginScreenTextToPb(text.Login),
+ PasswordText: text_grpc.PasswordScreenTextToPb(text.Password),
+ UsernameChangeText: text_grpc.UsernameChangeScreenTextToPb(text.UsernameChange),
+ UsernameChangeDoneText: text_grpc.UsernameChangeDoneScreenTextToPb(text.UsernameChangeDone),
+ InitPasswordText: text_grpc.InitPasswordScreenTextToPb(text.InitPassword),
+ InitPasswordDoneText: text_grpc.InitPasswordDoneScreenTextToPb(text.InitPasswordDone),
+ EmailVerificationText: text_grpc.EmailVerificationScreenTextToPb(text.EmailVerification),
+ EmailVerificationDoneText: text_grpc.EmailVerificationDoneScreenTextToPb(text.EmailVerificationDone),
+ InitializeUserText: text_grpc.InitializeUserScreenTextToPb(text.InitUser),
+ InitializeDoneText: text_grpc.InitializeUserDoneScreenTextToPb(text.InitUserDone),
+ InitMfaPromptText: text_grpc.InitMFAPromptScreenTextToPb(text.InitMFAPrompt),
+ InitMfaOtpText: text_grpc.InitMFAOTPScreenTextToPb(text.InitMFAOTP),
+ InitMfaU2FText: text_grpc.InitMFAU2FScreenTextToPb(text.InitMFAU2F),
+ InitMfaDoneText: text_grpc.InitMFADoneScreenTextToPb(text.InitMFADone),
+ MfaProvidersText: text_grpc.MFAProvidersTextToPb(text.MFAProvider),
+ VerifyMfaOtpText: text_grpc.VerifyMFAOTPScreenTextToPb(text.VerifyMFAOTP),
+ VerifyMfaU2FText: text_grpc.VerifyMFAU2FScreenTextToPb(text.VerifyMFAU2F),
+ PasswordlessText: text_grpc.PasswordlessScreenTextToPb(text.Passwordless),
+ PasswordlessPromptText: text_grpc.PasswordlessPromptScreenTextToPb(text.PasswordlessPrompt),
+ PasswordlessRegistrationText: text_grpc.PasswordlessRegistrationScreenTextToPb(text.PasswordlessRegistration),
+ PasswordlessRegistrationDoneText: text_grpc.PasswordlessRegistrationDoneScreenTextToPb(text.PasswordlessRegistrationDone),
+ PasswordChangeText: text_grpc.PasswordChangeScreenTextToPb(text.PasswordChange),
+ PasswordChangeDoneText: text_grpc.PasswordChangeDoneScreenTextToPb(text.PasswordChangeDone),
+ PasswordResetDoneText: text_grpc.PasswordResetDoneScreenTextToPb(text.PasswordResetDone),
+ RegistrationOptionText: text_grpc.RegistrationOptionScreenTextToPb(text.RegisterOption),
+ RegistrationUserText: text_grpc.RegistrationUserScreenTextToPb(text.RegistrationUser),
+ ExternalRegistrationUserOverviewText: text_grpc.ExternalRegistrationUserOverviewScreenTextToPb(text.ExternalRegistrationUserOverview),
+ RegistrationOrgText: text_grpc.RegistrationOrgScreenTextToPb(text.RegistrationOrg),
+ LinkingUserDoneText: text_grpc.LinkingUserDoneScreenTextToPb(text.LinkingUsersDone),
+ ExternalUserNotFoundText: text_grpc.ExternalUserNotFoundScreenTextToPb(text.ExternalNotFoundOption),
+ SuccessLoginText: text_grpc.SuccessLoginScreenTextToPb(text.LoginSuccess),
+ LogoutText: text_grpc.LogoutDoneScreenTextToPb(text.LogoutDone),
+ FooterText: text_grpc.FooterTextToPb(text.Footer),
+ })
+ }
+ }
+
+ return customTexts, nil
+}
+
+func (s *Server) getCustomInitMessageTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomInitMessageTextRequest, error) {
+ customTexts := make([]*management_pb.SetCustomInitMessageTextRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, org, domain.InitCodeMessageType, lang)
+ if err != nil {
+ return nil, err
+ }
+
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomInitMessageTextRequest{
+ Language: lang,
+ Title: text.Title,
+ PreHeader: text.PreHeader,
+ Subject: text.Subject,
+ Greeting: text.Greeting,
+ Text: text.Text,
+ ButtonText: text.ButtonText,
+ FooterText: text.Footer,
+ })
+ }
+ }
+
+ return customTexts, nil
+}
+
+func (s *Server) getCustomPasswordResetMessageTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomPasswordResetMessageTextRequest, error) {
+ customTexts := make([]*management_pb.SetCustomPasswordResetMessageTextRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, org, domain.PasswordResetMessageType, lang)
+ if err != nil {
+ return nil, err
+ }
+
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomPasswordResetMessageTextRequest{
+ Language: lang,
+ Title: text.Title,
+ PreHeader: text.PreHeader,
+ Subject: text.Subject,
+ Greeting: text.Greeting,
+ Text: text.Text,
+ ButtonText: text.ButtonText,
+ FooterText: text.Footer,
+ })
+ }
+ }
+
+ return customTexts, nil
+}
+
+func (s *Server) getCustomVerifyEmailMessageTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomVerifyEmailMessageTextRequest, error) {
+ customTexts := make([]*management_pb.SetCustomVerifyEmailMessageTextRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, org, domain.VerifyEmailMessageType, lang)
+ if err != nil {
+ return nil, err
+ }
+
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomVerifyEmailMessageTextRequest{
+ Language: lang,
+ Title: text.Title,
+ PreHeader: text.PreHeader,
+ Subject: text.Subject,
+ Greeting: text.Greeting,
+ Text: text.Text,
+ ButtonText: text.ButtonText,
+ FooterText: text.Footer,
+ })
+ }
+ }
+
+ return customTexts, nil
+}
+
+func (s *Server) getCustomVerifyPhoneMessageTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomVerifyPhoneMessageTextRequest, error) {
+ customTexts := make([]*management_pb.SetCustomVerifyPhoneMessageTextRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, org, domain.VerifyPhoneMessageType, lang)
+ if err != nil {
+ return nil, err
+ }
+
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomVerifyPhoneMessageTextRequest{
+ Language: lang,
+ Title: text.Title,
+ PreHeader: text.PreHeader,
+ Subject: text.Subject,
+ Greeting: text.Greeting,
+ Text: text.Text,
+ ButtonText: text.ButtonText,
+ FooterText: text.Footer,
+ })
+ }
+ }
+
+ return customTexts, nil
+}
+
+func (s *Server) getCustomDomainClaimedMessageTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomDomainClaimedMessageTextRequest, error) {
+ customTexts := make([]*management_pb.SetCustomDomainClaimedMessageTextRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, org, domain.DomainClaimedMessageType, lang)
+ if err != nil {
+ return nil, err
+ }
+
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomDomainClaimedMessageTextRequest{
+ Language: lang,
+ Title: text.Title,
+ PreHeader: text.PreHeader,
+ Subject: text.Subject,
+ Greeting: text.Greeting,
+ Text: text.Text,
+ ButtonText: text.ButtonText,
+ FooterText: text.Footer,
+ })
+ }
+ }
+
+ return customTexts, nil
+}
+
+func (s *Server) getCustomPasswordlessRegistrationMessageTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomPasswordlessRegistrationMessageTextRequest, error) {
+ customTexts := make([]*management_pb.SetCustomPasswordlessRegistrationMessageTextRequest, 0, len(languages))
+ for _, lang := range languages {
+ text, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, org, domain.DomainClaimedMessageType, lang)
+ if err != nil {
+ return nil, err
+ }
+
+ if !text.IsDefault {
+ customTexts = append(customTexts, &management_pb.SetCustomPasswordlessRegistrationMessageTextRequest{
+ Language: lang,
+ Title: text.Title,
+ PreHeader: text.PreHeader,
+ Subject: text.Subject,
+ Greeting: text.Greeting,
+ Text: text.Text,
+ ButtonText: text.ButtonText,
+ FooterText: text.Footer,
+ })
+ }
+ }
+
+ return customTexts, nil
+}
diff --git a/internal/api/grpc/admin/import.go b/internal/api/grpc/admin/import.go
new file mode 100644
index 0000000000..66765bf6f4
--- /dev/null
+++ b/internal/api/grpc/admin/import.go
@@ -0,0 +1,898 @@
+package admin
+
+import (
+ "cloud.google.com/go/storage"
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "github.com/minio/minio-go/v7"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+ "github.com/zitadel/logging"
+ "github.com/zitadel/zitadel/internal/api/authz"
+ "github.com/zitadel/zitadel/internal/api/grpc/management"
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/eventstore/v1/models"
+ es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
+ "github.com/zitadel/zitadel/internal/telemetry/tracing"
+ admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
+ management_pb "github.com/zitadel/zitadel/pkg/grpc/management"
+ "github.com/zitadel/zitadel/pkg/grpc/policy"
+ v1_pb "github.com/zitadel/zitadel/pkg/grpc/v1"
+ "google.golang.org/api/option"
+ "google.golang.org/protobuf/types/known/durationpb"
+ "io/ioutil"
+ "strconv"
+ "time"
+)
+
+type importResponse struct {
+ ret *admin_pb.ImportDataResponse
+ count *count
+ err error
+}
+type count struct {
+ humanUserCount int
+ humanUserLen int
+ machineUserCount int
+ machineUserLen int
+ userMetadataCount int
+ userMetadataLen int
+ userLinksCount int
+ userLinksLen int
+ projectCount int
+ projectLen int
+ oidcAppCount int
+ oidcAppLen int
+ apiAppCount int
+ apiAppLen int
+ actionCount int
+ actionLen int
+ projectRolesCount int
+ projectRolesLen int
+ projectGrantCount int
+ projectGrantLen int
+ userGrantCount int
+ userGrantLen int
+ projectMembersCount int
+ projectMembersLen int
+ orgMemberCount int
+ orgMemberLen int
+ projectGrantMemberCount int
+ projectGrantMemberLen int
+}
+
+func (c *count) getProgress() string {
+ return "progress:" +
+ "human_users " + strconv.Itoa(c.humanUserCount) + "/" + strconv.Itoa(c.humanUserLen) + ", " +
+ "machine_users " + strconv.Itoa(c.machineUserCount) + "/" + strconv.Itoa(c.machineUserLen) + ", " +
+ "user_metadata " + strconv.Itoa(c.userMetadataCount) + "/" + strconv.Itoa(c.userMetadataLen) + ", " +
+ "user_links " + strconv.Itoa(c.userLinksCount) + "/" + strconv.Itoa(c.userLinksLen) + ", " +
+ "projects " + strconv.Itoa(c.projectCount) + "/" + strconv.Itoa(c.projectLen) + ", " +
+ "oidc_apps " + strconv.Itoa(c.oidcAppCount) + "/" + strconv.Itoa(c.oidcAppLen) + ", " +
+ "api_apps " + strconv.Itoa(c.apiAppCount) + "/" + strconv.Itoa(c.apiAppLen) + ", " +
+ "actions " + strconv.Itoa(c.actionCount) + "/" + strconv.Itoa(c.actionLen) + ", " +
+ "project_roles " + strconv.Itoa(c.projectRolesCount) + "/" + strconv.Itoa(c.projectRolesLen) + ", " +
+ "project_grant " + strconv.Itoa(c.projectGrantCount) + "/" + strconv.Itoa(c.projectGrantLen) + ", " +
+ "user_grants " + strconv.Itoa(c.userGrantCount) + "/" + strconv.Itoa(c.userGrantLen) + ", " +
+ "project_members " + strconv.Itoa(c.projectMembersCount) + "/" + strconv.Itoa(c.projectMembersLen) + ", " +
+ "org_members " + strconv.Itoa(c.orgMemberCount) + "/" + strconv.Itoa(c.orgMemberLen) + ", " +
+ "project_grant_members " + strconv.Itoa(c.projectGrantMemberCount) + "/" + strconv.Itoa(c.projectGrantMemberLen)
+}
+
+func Detach(ctx context.Context) context.Context { return detachedContext{ctx} }
+
+type detachedContext struct {
+ parent context.Context
+}
+
+func (v detachedContext) Deadline() (time.Time, bool) { return time.Time{}, false }
+func (v detachedContext) Done() <-chan struct{} { return nil }
+func (v detachedContext) Err() error { return nil }
+func (v detachedContext) Value(key interface{}) interface{} { return v.parent.Value(key) }
+
+func (s *Server) ImportData(ctx context.Context, req *admin_pb.ImportDataRequest) (_ *admin_pb.ImportDataResponse, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ if req.GetDataOrgs() != nil || req.GetDataOrgsv1() != nil {
+ timeoutDuration, err := time.ParseDuration(req.Timeout)
+ if err != nil {
+ return nil, err
+ }
+ ch := make(chan importResponse, 1)
+ ctxTimeout, cancel := context.WithTimeout(ctx, timeoutDuration)
+ defer cancel()
+
+ go func() {
+ orgs := make([]*admin_pb.DataOrg, 0)
+ if req.GetDataOrgsv1() != nil {
+ dataOrgs, err := s.dataOrgsV1ToDataOrgs(ctx, req.GetDataOrgsv1())
+ if err != nil {
+ ch <- importResponse{ret: nil, err: err}
+ return
+ }
+ orgs = dataOrgs.GetOrgs()
+ } else {
+ orgs = req.GetDataOrgs().GetOrgs()
+ }
+
+ ret, count, err := s.importData(ctx, orgs)
+ ch <- importResponse{ret: ret, count: count, err: err}
+ }()
+
+ select {
+ case <-ctxTimeout.Done():
+ logging.Errorf("Import to response timeout: %v", ctxTimeout.Err())
+ return nil, ctxTimeout.Err()
+ case result := <-ch:
+ logging.OnError(result.err).Errorf("error while importing: %v", result.err)
+ logging.Infof("Import done: %s", result.count.getProgress())
+ return result.ret, result.err
+ }
+ } else {
+ v1Transformation := false
+ var gcsInput *admin_pb.ImportDataRequest_GCSInput
+ var s3Input *admin_pb.ImportDataRequest_S3Input
+ var localInput *admin_pb.ImportDataRequest_LocalInput
+ if req.GetDataOrgsGcs() != nil {
+ gcsInput = req.GetDataOrgsGcs()
+ }
+ if req.GetDataOrgsv1Gcs() != nil {
+ gcsInput = req.GetDataOrgsv1Gcs()
+ v1Transformation = true
+ }
+ if req.GetDataOrgsS3() != nil {
+ s3Input = req.GetDataOrgsS3()
+ }
+ if req.GetDataOrgsv1S3() != nil {
+ s3Input = req.GetDataOrgsv1S3()
+ v1Transformation = true
+ }
+ if req.GetDataOrgsLocal() != nil {
+ localInput = req.GetDataOrgsLocal()
+ }
+ if req.GetDataOrgsv1Local() != nil {
+ localInput = req.GetDataOrgsv1Local()
+ v1Transformation = true
+ }
+
+ timeoutDuration, err := time.ParseDuration(req.Timeout)
+ if err != nil {
+ return nil, err
+ }
+ dctx := Detach(ctx)
+ go func() {
+ ch := make(chan importResponse, 1)
+ ctxTimeout, cancel := context.WithTimeout(dctx, timeoutDuration)
+ defer cancel()
+ go func() {
+ dataOrgs, err := s.transportDataFromFile(ctxTimeout, v1Transformation, gcsInput, s3Input, localInput)
+ if err != nil {
+ ch <- importResponse{nil, nil, err}
+ return
+ }
+ resp, count, err := s.importData(ctxTimeout, dataOrgs)
+ if err != nil {
+ ch <- importResponse{nil, count, err}
+ return
+ }
+ ch <- importResponse{resp, count, nil}
+ }()
+
+ select {
+ case <-ctxTimeout.Done():
+ logging.Errorf("Export to response timeout: %v", ctxTimeout.Err())
+ return
+ case result := <-ch:
+ logging.OnError(result.err).Errorf("error while importing: %v", err)
+ if result.count != nil {
+ logging.Infof("Import done: %s", result.count.getProgress())
+ }
+ }
+ }()
+ }
+ return &admin_pb.ImportDataResponse{}, nil
+}
+
+func (s *Server) transportDataFromFile(ctx context.Context, v1Transformation bool, gcsInput *admin_pb.ImportDataRequest_GCSInput, s3Input *admin_pb.ImportDataRequest_S3Input, localInput *admin_pb.ImportDataRequest_LocalInput) (_ []*admin_pb.DataOrg, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ dataOrgs := make([]*admin_pb.DataOrg, 0)
+ data := make([]byte, 0)
+ if gcsInput != nil {
+ gcsData, err := getFileFromGCS(ctx, gcsInput)
+ if err != nil {
+ return nil, err
+ }
+ data = gcsData
+ }
+ if s3Input != nil {
+ s3Data, err := getFileFromS3(ctx, s3Input)
+ if err != nil {
+ return nil, err
+ }
+ data = s3Data
+ }
+ if localInput != nil {
+ localData, err := ioutil.ReadFile(localInput.Path)
+ if err != nil {
+ return nil, err
+ }
+ data = localData
+ }
+
+ if v1Transformation {
+ dataImportV1 := new(v1_pb.ImportDataOrg)
+ if err := json.Unmarshal(data, dataImportV1); err != nil {
+ return nil, err
+ }
+
+ dataImport, err := s.dataOrgsV1ToDataOrgs(ctx, dataImportV1)
+ if err != nil {
+ return nil, err
+ }
+ dataOrgs = dataImport.Orgs
+ } else {
+ dataImport := new(admin_pb.ImportDataOrg)
+ if err := json.Unmarshal(data, dataImport); err != nil {
+ return nil, err
+ }
+ dataOrgs = dataImport.Orgs
+ }
+
+ return dataOrgs, nil
+}
+
+func getFileFromS3(ctx context.Context, input *admin_pb.ImportDataRequest_S3Input) ([]byte, error) {
+ minioClient, err := minio.New(input.Endpoint, &minio.Options{
+ Creds: credentials.NewStaticV4(input.AccessKeyId, input.SecretAccessKey, ""),
+ Secure: input.Ssl,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ exists, err := minioClient.BucketExists(ctx, input.Bucket)
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ return nil, fmt.Errorf("bucket not existing: %v", err)
+ }
+
+ object, err := minioClient.GetObject(ctx, input.Bucket, input.Path, minio.GetObjectOptions{})
+ if err != nil {
+ return nil, err
+ }
+
+ defer object.Close()
+ return ioutil.ReadAll(object)
+}
+
+func getFileFromGCS(ctx context.Context, input *admin_pb.ImportDataRequest_GCSInput) ([]byte, error) {
+ saJson, err := base64.StdEncoding.DecodeString(input.ServiceaccountJson)
+ if err != nil {
+ return nil, err
+ }
+
+ client, err := storage.NewClient(ctx, option.WithCredentialsJSON(saJson))
+ if err != nil {
+ return nil, err
+ }
+
+ bucket := client.Bucket(input.Bucket)
+ reader, err := bucket.Object(input.Path).NewReader(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer reader.Close()
+ return ioutil.ReadAll(reader)
+}
+
+func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*admin_pb.ImportDataResponse, *count, error) {
+ errors := make([]*admin_pb.ImportDataError, 0)
+ success := &admin_pb.ImportDataSuccess{}
+ count := &count{}
+
+ appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg)
+ if err != nil {
+ return nil, nil, err
+ }
+ initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg)
+ if err != nil {
+ return nil, nil, err
+ }
+ phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg)
+ if err != nil {
+ return nil, nil, err
+ }
+ passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.userCodeAlg)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ctxData := authz.GetCtxData(ctx)
+ for _, org := range orgs {
+ count.humanUserLen += len(org.GetHumanUsers())
+ count.machineUserLen += len(org.GetMachineUsers())
+ count.userMetadataLen += len(org.GetUserMetadata())
+ count.userLinksLen += len(org.GetUserLinks())
+ count.projectLen += len(org.GetProjects())
+ count.oidcAppLen += len(org.GetOidcApps())
+ count.apiAppLen += len(org.GetApiApps())
+ count.actionLen += len(org.GetActions())
+ count.projectRolesLen += len(org.GetProjectRoles())
+ count.projectGrantLen += len(org.GetProjectGrants())
+ count.userGrantLen += len(org.GetUserGrants())
+ count.projectMembersLen += len(org.GetProjectMembers())
+ count.orgMemberLen += len(org.GetOrgMembers())
+ count.projectGrantMemberLen += len(org.GetProjectGrantMembers())
+ }
+
+ for _, org := range orgs {
+ _, err := s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), []string{})
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "org", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ successOrg := &admin_pb.ImportDataSuccessOrg{
+ OrgId: org.GetOrgId(),
+ ProjectIds: []string{},
+ OidcAppIds: []string{},
+ ApiAppIds: []string{},
+ HumanUserIds: []string{},
+ MachineUserIds: []string{},
+ ActionIds: []string{},
+ ProjectGrants: []*admin_pb.ImportDataSuccessProjectGrant{},
+ UserGrants: []*admin_pb.ImportDataSuccessUserGrant{},
+ OrgMembers: []string{},
+ ProjectMembers: []*admin_pb.ImportDataSuccessProjectMember{},
+ ProjectGrantMembers: []*admin_pb.ImportDataSuccessProjectGrantMember{},
+ }
+ logging.Debugf("successful org: %s", successOrg.OrgId)
+ success.Orgs = append(success.Orgs, successOrg)
+
+ domainPolicy := org.GetDomainPolicy()
+ if org.DomainPolicy != nil {
+ _, err := s.command.AddOrgDomainPolicy(ctx, org.GetOrgId(), DomainPolicyToDomain(domainPolicy.UserLoginMustBeDomain, domainPolicy.ValidateOrgDomains, domainPolicy.SmtpSenderAddressMatchesInstanceDomain))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "domain_policy", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ }
+ if org.Domains != nil {
+ for _, domainR := range org.Domains {
+ orgDomain := &domain.OrgDomain{
+ ObjectRoot: models.ObjectRoot{
+ AggregateID: org.GetOrgId(),
+ },
+ Domain: domainR.DomainName,
+ Verified: domainR.IsVerified,
+ Primary: domainR.IsPrimary,
+ }
+ _, err := s.command.AddOrgDomain(ctx, org.GetOrgId(), domainR.DomainName, []string{})
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "domain", Id: org.GetOrgId() + "_" + domainR.DomainName, Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ logging.Debugf("successful domain: %s", domainR.DomainName)
+ successOrg.Domains = append(successOrg.Domains, domainR.DomainName)
+
+ if domainR.IsVerified {
+ if _, err := s.command.VerifyOrgDomain(ctx, org.GetOrgId(), domainR.DomainName); err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "domain_isverified", Id: org.GetOrgId() + "_" + domainR.DomainName, Message: err.Error()})
+ }
+ }
+ if domainR.IsPrimary {
+ if _, err := s.command.SetPrimaryOrgDomain(ctx, orgDomain); err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "domain_isprimary", Id: org.GetOrgId() + "_" + domainR.DomainName, Message: err.Error()})
+ }
+ }
+ }
+ }
+ if org.LabelPolicy != nil {
+ _, err = s.command.AddLabelPolicy(ctx, org.GetOrgId(), management.AddLabelPolicyToDomain(org.GetLabelPolicy()))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "label_policy", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ }
+ if org.LockoutPolicy != nil {
+ _, err = s.command.AddLockoutPolicy(ctx, org.GetOrgId(), management.AddLockoutPolicyToDomain(org.GetLockoutPolicy()))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "lockout_policy", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ }
+ if org.OidcIdps != nil {
+ for _, idp := range org.OidcIdps {
+ logging.Debugf("import oidcidp: %s", idp.IdpId)
+ _, err := s.command.ImportIDPConfig(ctx, management.AddOIDCIDPRequestToDomain(idp.Idp), idp.IdpId, org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "oidc_idp", Id: idp.IdpId, Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ logging.Debugf("successful oidcidp: %s", idp.GetIdpId())
+ successOrg.OidcIpds = append(successOrg.OidcIpds, idp.GetIdpId())
+ }
+ }
+ if org.JwtIdps != nil {
+ for _, idp := range org.JwtIdps {
+ logging.Debugf("import jwtidp: %s", idp.IdpId)
+ _, err := s.command.ImportIDPConfig(ctx, management.AddJWTIDPRequestToDomain(idp.Idp), idp.IdpId, org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "jwt_idp", Id: idp.IdpId, Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ logging.Debugf("successful jwtidp: %s", idp.GetIdpId())
+ successOrg.JwtIdps = append(successOrg.JwtIdps, idp.GetIdpId())
+ }
+ }
+ if org.LoginPolicy != nil {
+ _, err = s.command.AddLoginPolicy(ctx, org.GetOrgId(), management.AddLoginPolicyToDomain(org.GetLoginPolicy()))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "login_policy", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ }
+ if org.PasswordComplexityPolicy != nil {
+ _, err = s.command.AddPasswordComplexityPolicy(ctx, org.GetOrgId(), management.AddPasswordComplexityPolicyToDomain(org.GetPasswordComplexityPolicy()))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "password_complexity_policy", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ }
+ if org.PrivacyPolicy != nil {
+ _, err = s.command.AddPrivacyPolicy(ctx, org.GetOrgId(), management.AddPrivacyPolicyToDomain(org.GetPrivacyPolicy()))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "privacy_policy", Id: org.GetOrgId(), Message: err.Error()})
+ }
+ }
+ if org.LoginTexts != nil {
+ for _, text := range org.GetLoginTexts() {
+ _, err := s.command.SetOrgLoginText(ctx, org.GetOrgId(), management.SetLoginCustomTextToDomain(text))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "login_texts", Id: org.GetOrgId() + "_" + text.Language, Message: err.Error()})
+ }
+ }
+ }
+ if org.InitMessages != nil {
+ for _, message := range org.GetInitMessages() {
+ _, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetInitCustomTextToDomain(message))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "init_message", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
+ }
+ }
+ }
+ if org.PasswordResetMessages != nil {
+ for _, message := range org.GetPasswordResetMessages() {
+ _, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetPasswordResetCustomTextToDomain(message))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "password_reset_message", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
+ }
+ }
+ }
+ if org.VerifyEmailMessages != nil {
+ for _, message := range org.GetVerifyEmailMessages() {
+ _, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetVerifyEmailCustomTextToDomain(message))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "verify_email_message", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
+ }
+ }
+ }
+ if org.VerifyPhoneMessages != nil {
+ for _, message := range org.GetVerifyPhoneMessages() {
+ _, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetVerifyPhoneCustomTextToDomain(message))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "verify_phone_message", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
+ }
+ }
+ }
+ if org.DomainClaimedMessages != nil {
+ for _, message := range org.GetDomainClaimedMessages() {
+ _, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetDomainClaimedCustomTextToDomain(message))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "domain_claimed_message", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
+ }
+ }
+ }
+ if org.PasswordlessRegistrationMessages != nil {
+ for _, message := range org.GetPasswordlessRegistrationMessages() {
+ _, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, management.SetPasswordlessRegistrationCustomTextToDomain(message))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "passwordless_registration_message", Id: org.GetOrgId() + "_" + message.Language, Message: err.Error()})
+ }
+ }
+ }
+
+ if org.HumanUsers != nil {
+ for _, user := range org.GetHumanUsers() {
+ logging.Debugf("import user: %s", user.GetUserId())
+ human, passwordless := management.ImportHumanUserRequestToDomain(user.User)
+ human.AggregateID = user.UserId
+ _, _, err := s.command.ImportHuman(ctx, org.GetOrgId(), human, passwordless, initCodeGenerator, phoneCodeGenerator, passwordlessInitCode)
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "human_user", Id: user.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ } else {
+ count.humanUserCount += 1
+ logging.Debugf("successful user %d: %s", count.humanUserCount, user.GetUserId())
+ successOrg.HumanUserIds = append(successOrg.HumanUserIds, user.GetUserId())
+ }
+
+ if user.User.OtpCode != "" {
+ logging.Debugf("import user otp: %s", user.GetUserId())
+ if err := s.command.ImportHumanOTP(ctx, user.UserId, "", org.GetOrgId(), user.User.OtpCode); err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "human_user_otp", Id: user.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ } else {
+ logging.Debugf("successful user otp: %s", user.GetUserId())
+ }
+ }
+ }
+ }
+ if org.MachineUsers != nil {
+ for _, user := range org.GetMachineUsers() {
+ logging.Debugf("import user: %s", user.GetUserId())
+ _, err := s.command.AddMachineWithID(ctx, org.GetOrgId(), user.GetUserId(), management.AddMachineUserRequestToDomain(user.GetUser()))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "machine_user", Id: user.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.machineUserCount += 1
+ logging.Debugf("successful user %d: %s", count.machineUserCount, user.GetUserId())
+ successOrg.MachineUserIds = append(successOrg.MachineUserIds, user.GetUserId())
+ }
+ }
+ if org.UserMetadata != nil {
+ for _, userMetadata := range org.GetUserMetadata() {
+ logging.Debugf("import usermetadata: %s", userMetadata.GetId()+"_"+userMetadata.GetKey())
+ _, err := s.command.SetUserMetadata(ctx, &domain.Metadata{Key: userMetadata.GetKey(), Value: userMetadata.GetValue()}, userMetadata.GetId(), org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "user_metadata", Id: userMetadata.GetId() + "_" + userMetadata.GetKey(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.userMetadataCount += 1
+ logging.Debugf("successful usermetadata %d: %s", count.userMetadataCount, userMetadata.GetId()+"_"+userMetadata.GetKey())
+ successOrg.UserMetadata = append(successOrg.UserMetadata, &admin_pb.ImportDataSuccessUserMetadata{UserId: userMetadata.GetId(), Key: userMetadata.GetKey()})
+ }
+ }
+ if org.UserLinks != nil {
+ for _, userLinks := range org.GetUserLinks() {
+ logging.Debugf("import userlink: %s", userLinks.GetUserId()+"_"+userLinks.GetIdpId()+"_"+userLinks.GetProvidedUserId()+"_"+userLinks.GetProvidedUserName())
+ externalIDP := &domain.UserIDPLink{
+ ObjectRoot: es_models.ObjectRoot{AggregateID: userLinks.UserId},
+ IDPConfigID: userLinks.IdpId,
+ ExternalUserID: userLinks.ProvidedUserId,
+ DisplayName: userLinks.ProvidedUserName,
+ }
+ if err := s.command.AddUserIDPLink(ctx, userLinks.UserId, org.GetOrgId(), externalIDP); err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "user_link", Id: userLinks.UserId + "_" + userLinks.IdpId, Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.userLinksCount += 1
+ logging.Debugf("successful userlink %d: %s", count.userLinksCount, userLinks.GetUserId()+"_"+userLinks.GetIdpId()+"_"+userLinks.GetProvidedUserId()+"_"+userLinks.GetProvidedUserName())
+ successOrg.UserLinks = append(successOrg.UserLinks, &admin_pb.ImportDataSuccessUserLinks{UserId: userLinks.GetUserId(), IdpId: userLinks.GetIdpId(), ExternalUserId: userLinks.GetProvidedUserId(), DisplayName: userLinks.GetProvidedUserName()})
+ }
+ }
+ if org.Projects != nil {
+ for _, project := range org.GetProjects() {
+ logging.Debugf("import project: %s", project.GetProjectId())
+ _, err := s.command.AddProjectWithID(ctx, management.ProjectCreateToDomain(project.GetProject()), org.GetOrgId(), project.GetProjectId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "project", Id: project.GetProjectId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.projectCount += 1
+ logging.Debugf("successful project %d: %s", count.projectCount, project.GetProjectId())
+ successOrg.ProjectIds = append(successOrg.ProjectIds, project.GetProjectId())
+ }
+ }
+ if org.OidcApps != nil {
+ for _, app := range org.GetOidcApps() {
+ logging.Debugf("import oidcapplication: %s", app.GetAppId())
+ _, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId(), appSecretGenerator)
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.oidcAppCount += 1
+ logging.Debugf("successful oidcapplication %d: %s", count.oidcAppCount, app.GetAppId())
+ successOrg.OidcAppIds = append(successOrg.OidcAppIds, app.GetAppId())
+ }
+ }
+ if org.ApiApps != nil {
+ for _, app := range org.GetApiApps() {
+ logging.Debugf("import apiapplication: %s", app.GetAppId())
+ _, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId(), appSecretGenerator)
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "api_app", Id: app.GetAppId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.apiAppCount += 1
+ logging.Debugf("successful apiapplication %d: %s", count.apiAppCount, app.GetAppId())
+ successOrg.ApiAppIds = append(successOrg.ApiAppIds, app.GetAppId())
+ }
+ }
+ if org.Actions != nil {
+ for _, action := range org.GetActions() {
+ logging.Debugf("import action: %s", action.GetActionId())
+ _, _, err := s.command.AddActionWithID(ctx, management.CreateActionRequestToDomain(action.GetAction()), org.GetOrgId(), action.GetActionId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "action", Id: action.GetActionId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.actionCount += 1
+ logging.Debugf("successful action %d: %s", count.actionCount, action.GetActionId())
+ successOrg.ActionIds = append(successOrg.ActionIds, action.ActionId)
+ }
+ }
+ if org.ProjectRoles != nil {
+ for _, role := range org.GetProjectRoles() {
+ logging.Debugf("import projectroles: %s", role.ProjectId+"_"+role.RoleKey)
+ _, err := s.command.AddProjectRole(ctx, management.AddProjectRoleRequestToDomain(role), org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "project_role", Id: role.ProjectId + "_" + role.RoleKey, Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.projectRolesCount += 1
+ logging.Debugf("successful projectroles %d: %s", count.projectRolesCount, role.ProjectId+"_"+role.RoleKey)
+ successOrg.ProjectRoles = append(successOrg.ActionIds, role.ProjectId+"_"+role.RoleKey)
+ }
+ }
+ }
+
+ for _, org := range orgs {
+ var successOrg *admin_pb.ImportDataSuccessOrg
+ for _, oldOrd := range success.Orgs {
+ if org.OrgId == oldOrd.OrgId {
+ successOrg = oldOrd
+ }
+ }
+ if org.TriggerActions != nil {
+ for _, triggerAction := range org.GetTriggerActions() {
+ _, err := s.command.SetTriggerActions(ctx, domain.FlowType(triggerAction.FlowType), domain.TriggerType(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType.String() + "_" + triggerAction.TriggerType.String(), Message: err.Error()})
+ continue
+ }
+ successOrg.TriggerActions = append(successOrg.TriggerActions, &management_pb.SetTriggerActionsRequest{FlowType: triggerAction.FlowType, TriggerType: triggerAction.TriggerType, ActionIds: triggerAction.GetActionIds()})
+ }
+ }
+ if org.ProjectGrants != nil {
+ for _, grant := range org.GetProjectGrants() {
+ logging.Debugf("import projectgrant: %s", grant.GetGrantId()+"_"+grant.GetProjectGrant().GetProjectId()+"_"+grant.GetProjectGrant().GetGrantedOrgId())
+ _, err := s.command.AddProjectGrantWithID(ctx, management.AddProjectGrantRequestToDomain(grant.GetProjectGrant()), grant.GetGrantId(), org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "project_grant", Id: org.GetOrgId() + "_" + grant.GetProjectGrant().GetProjectId() + "_" + grant.GetProjectGrant().GetGrantedOrgId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.projectGrantCount += 1
+ logging.Debugf("successful projectgrant %d: %s", count.projectGrantCount, grant.GetGrantId()+"_"+grant.GetProjectGrant().GetProjectId()+"_"+grant.GetProjectGrant().GetGrantedOrgId())
+ successOrg.ProjectGrants = append(successOrg.ProjectGrants, &admin_pb.ImportDataSuccessProjectGrant{GrantId: grant.GetGrantId(), ProjectId: grant.GetProjectGrant().GetProjectId(), OrgId: grant.GetProjectGrant().GetGrantedOrgId()})
+ }
+ }
+ if org.UserGrants != nil {
+ for _, grant := range org.GetUserGrants() {
+ logging.Debugf("import usergrant: %s", grant.GetProjectId()+"_"+grant.GetUserId())
+ _, err := s.command.AddUserGrant(ctx, management.AddUserGrantRequestToDomain(grant), org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "user_grant", Id: org.GetOrgId() + "_" + grant.GetProjectId() + "_" + grant.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.userGrantCount += 1
+ logging.Debugf("successful usergrant %d: %s", count.userGrantCount, grant.GetProjectId()+"_"+grant.GetUserId())
+ successOrg.UserGrants = append(successOrg.UserGrants, &admin_pb.ImportDataSuccessUserGrant{ProjectId: grant.GetProjectId(), UserId: grant.GetUserId()})
+ }
+ }
+ }
+
+ if success != nil && success.Orgs != nil {
+ for _, org := range orgs {
+ var successOrg *admin_pb.ImportDataSuccessOrg
+ for _, oldOrd := range success.Orgs {
+ if org.OrgId == oldOrd.OrgId {
+ successOrg = oldOrd
+ }
+ }
+ if successOrg == nil {
+ continue
+ }
+
+ if org.OrgMembers != nil {
+ for _, member := range org.GetOrgMembers() {
+ logging.Debugf("import orgmember: %s", member.GetUserId())
+ _, err := s.command.AddOrgMember(ctx, org.GetOrgId(), member.GetUserId(), member.GetRoles()...)
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "org_member", Id: org.GetOrgId() + "_" + member.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.orgMemberCount += 1
+ logging.Debugf("successful orgmember %d: %s", count.orgMemberCount, member.GetUserId())
+ successOrg.OrgMembers = append(successOrg.OrgMembers, member.GetUserId())
+ }
+ }
+ if org.ProjectGrantMembers != nil {
+ for _, member := range org.GetProjectGrantMembers() {
+ logging.Debugf("import projectgrantmember: %s", member.GetProjectId()+"_"+member.GetGrantId()+"_"+member.GetUserId())
+ _, err := s.command.AddProjectGrantMember(ctx, management.AddProjectGrantMemberRequestToDomain(member))
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "project_grant_member", Id: org.GetOrgId() + "_" + member.GetProjectId() + "_" + member.GetGrantId() + "_" + member.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.projectGrantMemberCount += 1
+ logging.Debugf("successful projectgrantmember %d: %s", count.projectGrantMemberCount, member.GetProjectId()+"_"+member.GetGrantId()+"_"+member.GetUserId())
+ successOrg.ProjectGrantMembers = append(successOrg.ProjectGrantMembers, &admin_pb.ImportDataSuccessProjectGrantMember{ProjectId: member.GetProjectId(), GrantId: member.GetGrantId(), UserId: member.GetUserId()})
+ }
+ }
+ if org.ProjectMembers != nil {
+ for _, member := range org.GetProjectMembers() {
+ logging.Debugf("import orgmember: %s", member.GetProjectId()+"_"+member.GetUserId())
+ _, err := s.command.AddProjectMember(ctx, management.AddProjectMemberRequestToDomain(member), org.GetOrgId())
+ if err != nil {
+ errors = append(errors, &admin_pb.ImportDataError{Type: "project_member", Id: org.GetOrgId() + "_" + member.GetProjectId() + "_" + member.GetUserId(), Message: err.Error()})
+ if isCtxTimeout(ctx) {
+ return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
+ }
+ continue
+ }
+ count.projectMembersCount += 1
+ logging.Debugf("successful orgmember %d: %s", count.projectMembersCount, member.GetProjectId()+"_"+member.GetUserId())
+ successOrg.ProjectMembers = append(successOrg.ProjectMembers, &admin_pb.ImportDataSuccessProjectMember{ProjectId: member.GetProjectId(), UserId: member.GetUserId()})
+ }
+ }
+ }
+ }
+ return &admin_pb.ImportDataResponse{
+ Errors: errors,
+ Success: success,
+ }, count, nil
+}
+
+func (s *Server) dataOrgsV1ToDataOrgs(ctx context.Context, dataOrgs *v1_pb.ImportDataOrg) (_ *admin_pb.ImportDataOrg, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ orgs := make([]*admin_pb.DataOrg, 0)
+ for _, orgV1 := range dataOrgs.Orgs {
+ org := &admin_pb.DataOrg{
+ OrgId: orgV1.GetOrgId(),
+ Org: orgV1.GetOrg(),
+ DomainPolicy: nil,
+ LabelPolicy: orgV1.GetLabelPolicy(),
+ LockoutPolicy: orgV1.GetLockoutPolicy(),
+ LoginPolicy: orgV1.GetLoginPolicy(),
+ PasswordComplexityPolicy: orgV1.GetPasswordComplexityPolicy(),
+ PrivacyPolicy: orgV1.GetPrivacyPolicy(),
+ Projects: orgV1.GetProjects(),
+ ProjectRoles: orgV1.GetProjectRoles(),
+ ApiApps: orgV1.GetApiApps(),
+ OidcApps: orgV1.GetOidcApps(),
+ HumanUsers: orgV1.GetHumanUsers(),
+ MachineUsers: orgV1.GetMachineUsers(),
+ TriggerActions: orgV1.GetTriggerActions(),
+ Actions: orgV1.GetActions(),
+ ProjectGrants: orgV1.GetProjectGrants(),
+ UserGrants: orgV1.GetUserGrants(),
+ OrgMembers: orgV1.GetOrgMembers(),
+ ProjectMembers: orgV1.GetProjectMembers(),
+ ProjectGrantMembers: orgV1.GetProjectGrantMembers(),
+ UserMetadata: orgV1.GetUserMetadata(),
+ LoginTexts: orgV1.GetLoginTexts(),
+ InitMessages: orgV1.GetInitMessages(),
+ PasswordResetMessages: orgV1.GetPasswordResetMessages(),
+ VerifyEmailMessages: orgV1.GetVerifyEmailMessages(),
+ VerifyPhoneMessages: orgV1.GetVerifyPhoneMessages(),
+ DomainClaimedMessages: orgV1.GetDomainClaimedMessages(),
+ PasswordlessRegistrationMessages: orgV1.GetPasswordlessRegistrationMessages(),
+ OidcIdps: orgV1.GetOidcIdps(),
+ JwtIdps: orgV1.GetJwtIdps(),
+ UserLinks: orgV1.GetUserLinks(),
+ Domains: orgV1.GetDomains(),
+ }
+ if orgV1.IamPolicy != nil {
+ defaultDomainPolicy, err := s.query.DefaultDomainPolicy(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ org.DomainPolicy = &admin_pb.AddCustomDomainPolicyRequest{
+ UserLoginMustBeDomain: orgV1.IamPolicy.UserLoginMustBeDomain,
+ ValidateOrgDomains: defaultDomainPolicy.ValidateOrgDomains,
+ SmtpSenderAddressMatchesInstanceDomain: defaultDomainPolicy.SMTPSenderAddressMatchesInstanceDomain,
+ }
+ }
+ if org.LoginPolicy != nil {
+ defaultLoginPolicy, err := s.query.DefaultLoginPolicy(ctx)
+ if err != nil {
+ return nil, err
+ }
+ org.LoginPolicy.ExternalLoginCheckLifetime = durationpb.New(defaultLoginPolicy.ExternalLoginCheckLifetime)
+ org.LoginPolicy.MultiFactorCheckLifetime = durationpb.New(defaultLoginPolicy.MultiFactorCheckLifetime)
+ org.LoginPolicy.SecondFactorCheckLifetime = durationpb.New(defaultLoginPolicy.SecondFactorCheckLifetime)
+ org.LoginPolicy.PasswordCheckLifetime = durationpb.New(defaultLoginPolicy.PasswordCheckLifetime)
+ org.LoginPolicy.MfaInitSkipLifetime = durationpb.New(defaultLoginPolicy.MFAInitSkipLifetime)
+
+ if orgV1.SecondFactors != nil {
+ org.LoginPolicy.SecondFactors = make([]policy.SecondFactorType, len(orgV1.SecondFactors))
+ for i, factor := range orgV1.SecondFactors {
+ org.LoginPolicy.SecondFactors[i] = factor.GetType()
+ }
+ }
+ if orgV1.MultiFactors != nil {
+ org.LoginPolicy.MultiFactors = make([]policy.MultiFactorType, len(orgV1.MultiFactors))
+ for i, factor := range orgV1.MultiFactors {
+ org.LoginPolicy.MultiFactors[i] = factor.GetType()
+ }
+ }
+ if orgV1.Idps != nil {
+ org.LoginPolicy.Idps = make([]*management_pb.AddCustomLoginPolicyRequest_IDP, len(orgV1.Idps))
+ for i, idpR := range orgV1.Idps {
+ org.LoginPolicy.Idps[i] = &management_pb.AddCustomLoginPolicyRequest_IDP{
+ IdpId: idpR.GetIdpId(),
+ OwnerType: idpR.GetOwnerType(),
+ }
+ }
+ }
+ }
+ orgs = append(orgs, org)
+ }
+
+ return &admin_pb.ImportDataOrg{
+ Orgs: orgs,
+ }, nil
+}
+
+func isCtxTimeout(ctx context.Context) bool {
+ select {
+ case <-ctx.Done():
+ return true
+ default:
+ return false
+ }
+}
diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go
index 1665895847..60808f5b90 100644
--- a/internal/api/grpc/admin/server.go
+++ b/internal/api/grpc/admin/server.go
@@ -2,7 +2,6 @@ package admin
import (
"context"
-
"google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/admin/repository"
@@ -11,6 +10,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command"
+ "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/admin"
@@ -30,6 +30,7 @@ type Server struct {
administrator repository.AdministratorRepository
assetsAPIDomain func(context.Context) string
userCodeAlg crypto.EncryptionAlgorithm
+ passwordHashAlg crypto.HashAlgorithm
}
type Config struct {
@@ -40,6 +41,7 @@ func CreateServer(
database string,
command *command.Commands,
query *query.Queries,
+ sd systemdefaults.SystemDefaults,
repo repository.Repository,
externalSecure bool,
userCodeAlg crypto.EncryptionAlgorithm,
@@ -51,6 +53,7 @@ func CreateServer(
administrator: repo,
assetsAPIDomain: assets.AssetAPI(externalSecure),
userCodeAlg: userCodeAlg,
+ passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost),
}
}
diff --git a/internal/api/grpc/management/actions.go b/internal/api/grpc/management/actions.go
index 9c479155c9..67a25d16e4 100644
--- a/internal/api/grpc/management/actions.go
+++ b/internal/api/grpc/management/actions.go
@@ -35,7 +35,7 @@ func (s *Server) GetAction(ctx context.Context, req *mgmt_pb.GetActionRequest) (
}
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)
+ id, details, err := s.command.AddAction(ctx, CreateActionRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
diff --git a/internal/api/grpc/management/actions_converter.go b/internal/api/grpc/management/actions_converter.go
index 873a36072a..d3e08a8bbe 100644
--- a/internal/api/grpc/management/actions_converter.go
+++ b/internal/api/grpc/management/actions_converter.go
@@ -9,7 +9,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
-func createActionRequestToDomain(req *mgmt_pb.CreateActionRequest) *domain.Action {
+func CreateActionRequestToDomain(req *mgmt_pb.CreateActionRequest) *domain.Action {
return &domain.Action{
Name: req.Name,
Script: req.Script,
diff --git a/internal/api/grpc/management/idp.go b/internal/api/grpc/management/idp.go
index 87041826d8..01f430c342 100644
--- a/internal/api/grpc/management/idp.go
+++ b/internal/api/grpc/management/idp.go
@@ -34,7 +34,7 @@ func (s *Server) ListOrgIDPs(ctx context.Context, req *mgmt_pb.ListOrgIDPsReques
}
func (s *Server) AddOrgOIDCIDP(ctx context.Context, req *mgmt_pb.AddOrgOIDCIDPRequest) (*mgmt_pb.AddOrgOIDCIDPResponse, error) {
- config, err := s.command.AddIDPConfig(ctx, addOIDCIDPRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
+ config, err := s.command.AddIDPConfig(ctx, AddOIDCIDPRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
@@ -49,7 +49,7 @@ func (s *Server) AddOrgOIDCIDP(ctx context.Context, req *mgmt_pb.AddOrgOIDCIDPRe
}
func (s *Server) AddOrgJWTIDP(ctx context.Context, req *mgmt_pb.AddOrgJWTIDPRequest) (*mgmt_pb.AddOrgJWTIDPResponse, error) {
- config, err := s.command.AddIDPConfig(ctx, addJWTIDPRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
+ config, err := s.command.AddIDPConfig(ctx, AddJWTIDPRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go
index 653375ed3c..b8dc6ba046 100644
--- a/internal/api/grpc/management/idp_converter.go
+++ b/internal/api/grpc/management/idp_converter.go
@@ -14,7 +14,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
-func addOIDCIDPRequestToDomain(req *mgmt_pb.AddOrgOIDCIDPRequest) *domain.IDPConfig {
+func AddOIDCIDPRequestToDomain(req *mgmt_pb.AddOrgOIDCIDPRequest) *domain.IDPConfig {
return &domain.IDPConfig{
Name: req.Name,
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
@@ -35,7 +35,7 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *mgmt_pb.AddOrgOIDCIDPRequest) *
}
}
-func addJWTIDPRequestToDomain(req *mgmt_pb.AddOrgJWTIDPRequest) *domain.IDPConfig {
+func AddJWTIDPRequestToDomain(req *mgmt_pb.AddOrgJWTIDPRequest) *domain.IDPConfig {
return &domain.IDPConfig{
Name: req.Name,
JWTConfig: addJWTIDPRequestToDomainJWTIDPConfig(req),
diff --git a/internal/api/grpc/management/idp_converter_test.go b/internal/api/grpc/management/idp_converter_test.go
index 7e5bbf0cb5..999be2ea06 100644
--- a/internal/api/grpc/management/idp_converter_test.go
+++ b/internal/api/grpc/management/idp_converter_test.go
@@ -35,7 +35,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := addOIDCIDPRequestToDomain(tt.args.req)
+ got := AddOIDCIDPRequestToDomain(tt.args.req)
test.AssertFieldsMapped(t, got,
"ObjectRoot",
"OIDCConfig.ClientSecret",
diff --git a/internal/api/grpc/management/policy_label.go b/internal/api/grpc/management/policy_label.go
index 3eaec40ebd..e76f97fb00 100644
--- a/internal/api/grpc/management/policy_label.go
+++ b/internal/api/grpc/management/policy_label.go
@@ -34,7 +34,7 @@ func (s *Server) GetDefaultLabelPolicy(ctx context.Context, req *mgmt_pb.GetDefa
}
func (s *Server) AddCustomLabelPolicy(ctx context.Context, req *mgmt_pb.AddCustomLabelPolicyRequest) (*mgmt_pb.AddCustomLabelPolicyResponse, error) {
- policy, err := s.command.AddLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID, addLabelPolicyToDomain(req))
+ policy, err := s.command.AddLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddLabelPolicyToDomain(req))
if err != nil {
return nil, err
}
diff --git a/internal/api/grpc/management/policy_label_converter.go b/internal/api/grpc/management/policy_label_converter.go
index 9918d2ea93..3ad0b3329e 100644
--- a/internal/api/grpc/management/policy_label_converter.go
+++ b/internal/api/grpc/management/policy_label_converter.go
@@ -5,7 +5,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
-func addLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.LabelPolicy {
+func AddLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
BackgroundColor: p.BackgroundColor,
diff --git a/internal/api/grpc/management/policy_login.go b/internal/api/grpc/management/policy_login.go
index 25273b81de..78029c79a5 100644
--- a/internal/api/grpc/management/policy_login.go
+++ b/internal/api/grpc/management/policy_login.go
@@ -30,7 +30,7 @@ func (s *Server) GetDefaultLoginPolicy(ctx context.Context, req *mgmt_pb.GetDefa
}
func (s *Server) AddCustomLoginPolicy(ctx context.Context, req *mgmt_pb.AddCustomLoginPolicyRequest) (*mgmt_pb.AddCustomLoginPolicyResponse, error) {
- policy, err := s.command.AddLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, addLoginPolicyToDomain(req))
+ policy, err := s.command.AddLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddLoginPolicyToDomain(req))
if err != nil {
return nil, err
}
diff --git a/internal/api/grpc/management/policy_login_converter.go b/internal/api/grpc/management/policy_login_converter.go
index 6b2c6cb88a..8f7a632ece 100644
--- a/internal/api/grpc/management/policy_login_converter.go
+++ b/internal/api/grpc/management/policy_login_converter.go
@@ -9,7 +9,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
-func addLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.LoginPolicy {
+func AddLoginPolicyToDomain(p *mgmt_pb.AddCustomLoginPolicyRequest) *domain.LoginPolicy {
return &domain.LoginPolicy{
AllowUsernamePassword: p.AllowUsernamePassword,
AllowRegister: p.AllowRegister,
diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go
index 7aa0e1a3e0..507fcce199 100644
--- a/internal/api/grpc/management/user.go
+++ b/internal/api/grpc/management/user.go
@@ -184,6 +184,21 @@ func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRe
}
func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) {
+ details, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToAddHuman(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.AddHumanUserResponse{
+ UserId: details.ID,
+ Details: obj_grpc.AddToDetailsPb(
+ details.Sequence,
+ details.EventDate,
+ details.ResourceOwner,
+ ),
+ }, nil
+}
+
+func AddHumanUserRequestToAddHuman(req *mgmt_pb.AddHumanUserRequest) *command.AddHuman {
lang, err := language.Parse(req.Profile.PreferredLanguage)
logging.OnError(err).Debug("unable to parse language")
@@ -211,18 +226,7 @@ func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequ
Verified: req.Phone.IsPhoneVerified,
}
}
- details, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, human)
- if err != nil {
- return nil, err
- }
- return &mgmt_pb.AddHumanUserResponse{
- UserId: details.ID,
- Details: obj_grpc.AddToDetailsPb(
- details.Sequence,
- details.EventDate,
- details.ResourceOwner,
- ),
- }, nil
+ return human
}
func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) {
diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go
index 77f1d7c09b..97ff4ec443 100644
--- a/internal/api/grpc/management/user_converter.go
+++ b/internal/api/grpc/management/user_converter.go
@@ -142,11 +142,16 @@ func ImportHumanUserRequestToDomain(req *mgmt_pb.ImportHumanUserRequest) (human
IsPhoneVerified: req.Phone.IsPhoneVerified,
}
}
+
if req.Password != "" {
- human.Password = &domain.Password{SecretString: req.Password}
+ human.Password = domain.NewPassword(req.Password)
human.Password.ChangeRequired = req.PasswordChangeRequired
}
+ if req.HashedPassword != nil && req.HashedPassword.Value != "" && req.HashedPassword.Algorithm != "" {
+ human.HashedPassword = domain.NewHashedPassword(req.HashedPassword.Value, req.HashedPassword.Algorithm)
+ }
+
return human, req.RequestPasswordlessRegistration
}
diff --git a/internal/command/org.go b/internal/command/org.go
index 6eb3aa6a8c..d40c7eec36 100644
--- a/internal/command/org.go
+++ b/internal/command/org.go
@@ -21,17 +21,19 @@ type OrgSetup struct {
Roles []string
}
-func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string) (string, *domain.ObjectDetails, error) {
- orgID, err := c.idGenerator.Next()
+func (c *Commands) SetUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, *domain.ObjectDetails, error) {
+ existingOrg, err := c.getOrgWriteModelByID(ctx, orgID)
if err != nil {
return "", nil, err
}
-
- userID, err := c.idGenerator.Next()
- if err != nil {
- return "", nil, err
+ if existingOrg != nil {
+ return "", nil, errors.ThrowPreconditionFailed(nil, "COMMAND-poaj2", "Errors.Org.AlreadyExisting")
}
+ return c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
+}
+
+func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(orgID)
userAgg := user_repo.NewAggregate(userID, orgID)
@@ -65,6 +67,20 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string)
}, nil
}
+func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string) (string, *domain.ObjectDetails, error) {
+ orgID, err := c.idGenerator.Next()
+ if err != nil {
+ return "", nil, err
+ }
+
+ userID, err := c.idGenerator.Next()
+ if err != nil {
+ return "", nil, err
+ }
+
+ return c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
+}
+
//AddOrgCommand defines the commands to create a new org,
// this includes the verified default domain
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string, userIDs ...string) preparation.Validation {
@@ -106,8 +122,33 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
return nil
}
+func (c *Commands) AddOrgWithID(ctx context.Context, name, userID, resourceOwner, orgID string, claimedUserIDs []string) (*domain.Org, error) {
+ existingOrg, err := c.getOrgWriteModelByID(ctx, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if existingOrg.State != domain.OrgStateUnspecified {
+ return nil, caos_errs.ThrowNotFound(nil, "ORG-lapo2m", "Errors.Org.AlreadyExisting")
+ }
+
+ return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, claimedUserIDs)
+}
+
func (c *Commands) AddOrg(ctx context.Context, name, userID, resourceOwner string, claimedUserIDs []string) (*domain.Org, error) {
- orgAgg, addedOrg, events, err := c.addOrg(ctx, &domain.Org{Name: name}, claimedUserIDs)
+ if name == "" {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
+ }
+
+ orgID, err := c.idGenerator.Next()
+ if err != nil {
+ return nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal")
+ }
+
+ return c.addOrgWithIDAndMember(ctx, name, userID, resourceOwner, orgID, claimedUserIDs)
+}
+
+func (c *Commands) addOrgWithIDAndMember(ctx context.Context, name, userID, resourceOwner, orgID string, claimedUserIDs []string) (*domain.Org, error) {
+ orgAgg, addedOrg, events, err := c.addOrgWithID(ctx, &domain.Org{Name: name}, orgID, claimedUserIDs)
if err != nil {
return nil, err
}
@@ -136,6 +177,7 @@ func (c *Commands) ChangeOrg(ctx context.Context, orgID, name string) (*domain.O
if orgID == "" || name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
}
+
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
@@ -242,15 +284,12 @@ func ExistsOrg(ctx context.Context, filter preparation.FilterToQueryReducer, id
return exists, nil
}
-func (c *Commands) addOrg(ctx context.Context, organisation *domain.Org, claimedUserIDs []string) (_ *eventstore.Aggregate, _ *OrgWriteModel, _ []eventstore.Command, err error) {
+func (c *Commands) addOrgWithID(ctx context.Context, organisation *domain.Org, orgID string, claimedUserIDs []string) (_ *eventstore.Aggregate, _ *OrgWriteModel, _ []eventstore.Command, err error) {
if !organisation.IsValid() {
return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid")
}
- organisation.AggregateID, err = c.idGenerator.Next()
- if err != nil {
- return nil, nil, nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal")
- }
+ organisation.AggregateID = orgID
organisation.AddIAMDomain(authz.GetInstance(ctx).RequestedDomain())
addedOrg := NewOrgWriteModel(organisation.AggregateID)
diff --git a/internal/command/org_action.go b/internal/command/org_action.go
index aeeb5f5879..45cf13acfa 100644
--- a/internal/command/org_action.go
+++ b/internal/command/org_action.go
@@ -11,14 +11,33 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
+func (c *Commands) AddActionWithID(ctx context.Context, addAction *domain.Action, resourceOwner, actionID string) (_ string, _ *domain.ObjectDetails, err error) {
+ existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
+ if err != nil {
+ return "", nil, err
+ }
+ if existingAction.State != domain.ActionStateUnspecified {
+ return "", nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-nau2k", "Errors.Action.AlreadyExisting")
+ }
+
+ return c.addActionWithID(ctx, addAction, resourceOwner, actionID)
+}
+
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()
+
+ actionID, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
+
+ return c.addActionWithID(ctx, addAction, resourceOwner, actionID)
+}
+
+func (c *Commands) addActionWithID(ctx context.Context, addAction *domain.Action, resourceOwner, actionID string) (_ string, _ *domain.ObjectDetails, err error) {
+ addAction.AggregateID = actionID
actionModel := NewActionWriteModel(addAction.AggregateID, resourceOwner)
actionAgg := ActionAggregateFromWriteModel(&actionModel.WriteModel)
diff --git a/internal/command/org_domain.go b/internal/command/org_domain.go
index 5270bbc19b..dc108001ba 100644
--- a/internal/command/org_domain.go
+++ b/internal/command/org_domain.go
@@ -51,7 +51,7 @@ func (c *Commands) prepareAddOrgDomain(a *org.Aggregate, addDomain string, userI
}
}
-func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
+func verifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument")
@@ -63,7 +63,7 @@ func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
}
}
-func SetPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
+func setPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument")
@@ -101,6 +101,19 @@ func orgDomain(ctx context.Context, filter preparation.FilterToQueryReducer, org
return wm, nil
}
+func (c *Commands) VerifyOrgDomain(ctx context.Context, orgID, domain string) (*domain.ObjectDetails, error) {
+ orgAgg := org.NewAggregate(orgID)
+ cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, verifyOrgDomain(orgAgg, domain))
+ if err != nil {
+ return nil, err
+ }
+ pushedEvents, err := c.eventstore.Push(ctx, cmds...)
+ if err != nil {
+ return nil, err
+ }
+ return pushedEventsToObjectDetails(pushedEvents), nil
+}
+
func (c *Commands) AddOrgDomain(ctx context.Context, orgID, domain string, claimedUserIDs []string) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(orgID)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgDomain(orgAgg, domain, claimedUserIDs))
diff --git a/internal/command/org_domain_test.go b/internal/command/org_domain_test.go
index 001992406e..230fc5601a 100644
--- a/internal/command/org_domain_test.go
+++ b/internal/command/org_domain_test.go
@@ -178,7 +178,7 @@ func TestVerifyDomain(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- AssertValidation(t, context.Background(), VerifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
+ AssertValidation(t, context.Background(), verifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
})
}
}
@@ -273,7 +273,7 @@ func TestSetDomainPrimary(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- AssertValidation(t, context.Background(), SetPrimaryOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want)
+ AssertValidation(t, context.Background(), setPrimaryOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want)
})
}
}
diff --git a/internal/command/org_idp_config.go b/internal/command/org_idp_config.go
index 21647263ac..f135a4109a 100644
--- a/internal/command/org_idp_config.go
+++ b/internal/command/org_idp_config.go
@@ -11,6 +11,17 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
+func (c *Commands) ImportIDPConfig(ctx context.Context, config *domain.IDPConfig, idpConfigID, resourceOwner string) (*domain.IDPConfig, error) {
+ existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, idpConfigID, resourceOwner)
+ if err != nil {
+ return nil, err
+ }
+ if existingIDP.State != domain.IDPConfigStateRemoved && existingIDP.State != domain.IDPConfigStateUnspecified {
+ return nil, errors.ThrowNotFound(nil, "Org-1J8fs", "Errors.Org.IDPConfig.AlreadyExisting")
+ }
+ return c.addIDPConfig(ctx, config, idpConfigID, resourceOwner)
+}
+
func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, resourceOwner string) (*domain.IDPConfig, error) {
if resourceOwner == "" {
return nil, errors.ThrowInvalidArgument(nil, "Org-0j8gs", "Errors.ResourceOwnerMissing")
@@ -18,11 +29,16 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
if config.OIDCConfig == nil && config.JWTConfig == nil {
return nil, errors.ThrowInvalidArgument(nil, "Org-eUpQU", "Errors.idp.config.notset")
}
-
idpConfigID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
+
+ return c.addIDPConfig(ctx, config, idpConfigID, resourceOwner)
+}
+
+func (c *Commands) addIDPConfig(ctx context.Context, config *domain.IDPConfig, idpConfigID, resourceOwner string) (*domain.IDPConfig, error) {
+
addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, resourceOwner)
orgAgg := OrgAggregateFromWriteModel(&addedConfig.WriteModel)
diff --git a/internal/command/project.go b/internal/command/project.go
index 07a8bbc478..8b795ed07b 100644
--- a/internal/command/project.go
+++ b/internal/command/project.go
@@ -13,11 +13,46 @@ import (
"github.com/zitadel/zitadel/internal/repository/project"
)
-func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner, ownerUserID string) (_ *domain.Project, err error) {
- events, addedProject, err := c.addProject(ctx, project, resourceOwner, ownerUserID)
+func (c *Commands) AddProjectWithID(ctx context.Context, project *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
+ existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {
return nil, err
}
+ if existingProject.State != domain.ProjectStateUnspecified {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-opamwu", "Errors.Project.AlreadyExisting")
+ }
+ return c.addProjectWithID(ctx, project, resourceOwner, projectID)
+}
+
+func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner, ownerUserID string) (_ *domain.Project, err error) {
+ if !project.IsValid() {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
+ }
+
+ projectID, err := c.idGenerator.Next()
+ if err != nil {
+ return nil, err
+ }
+
+ return c.addProjectWithIDWithOwner(ctx, project, resourceOwner, ownerUserID, projectID)
+}
+
+func (c *Commands) addProjectWithID(ctx context.Context, projectAdd *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
+ projectAdd.AggregateID = projectID
+ addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
+ projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
+
+ events := []eventstore.Command{
+ project.NewProjectAddedEvent(
+ ctx,
+ projectAgg,
+ projectAdd.Name,
+ projectAdd.ProjectRoleAssertion,
+ projectAdd.ProjectRoleCheck,
+ projectAdd.HasProjectCheck,
+ projectAdd.PrivateLabelingSetting),
+ }
+
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
@@ -29,14 +64,11 @@ func (c *Commands) AddProject(ctx context.Context, project *domain.Project, reso
return projectWriteModelToProject(addedProject), nil
}
-func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, resourceOwner, ownerUserID string) (_ []eventstore.Command, _ *ProjectWriteModel, err error) {
+func (c *Commands) addProjectWithIDWithOwner(ctx context.Context, projectAdd *domain.Project, resourceOwner, ownerUserID, projectID string) (_ *domain.Project, err error) {
if !projectAdd.IsValid() {
- return nil, nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
- }
- projectAdd.AggregateID, err = c.idGenerator.Next()
- if err != nil {
- return nil, nil, err
+ return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
+ projectAdd.AggregateID = projectID
addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
@@ -52,7 +84,16 @@ func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, r
projectAdd.PrivateLabelingSetting),
project.NewProjectMemberAddedEvent(ctx, projectAgg, ownerUserID, projectRole),
}
- return events, addedProject, nil
+
+ pushedEvents, err := c.eventstore.Push(ctx, events...)
+ if err != nil {
+ return nil, err
+ }
+ err = AppendAndReduce(addedProject, pushedEvents...)
+ if err != nil {
+ return nil, err
+ }
+ return projectWriteModelToProject(addedProject), nil
}
func AddProjectCommand(
diff --git a/internal/command/project_application_api.go b/internal/command/project_application_api.go
index 87a1dabec2..2046d3dc62 100644
--- a/internal/command/project_application_api.go
+++ b/internal/command/project_application_api.go
@@ -70,21 +70,70 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA
}
}
-func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
- if application == nil || application.AggregateID == "" {
- return nil, errors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
- }
- project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
- if err != nil {
- return nil, errors.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
- }
- addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
- projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
- events, stringPw, err := c.addAPIApplication(ctx, projectAgg, project, application, resourceOwner, appSecretGenerator)
+func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
+ existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, appID, resourceOwner)
if err != nil {
return nil, err
}
- addedApplication.AppID = application.AppID
+ if existingAPI.State != domain.AppStateUnspecified {
+ return nil, errors.ThrowPreconditionFailed(nil, "PROJECT-mabu12", "Errors.Application.AlreadyExisting")
+ }
+ project, err := c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
+ if err != nil {
+ return nil, errors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound")
+ }
+
+ return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator)
+}
+
+func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
+ if apiApp == nil || apiApp.AggregateID == "" {
+ return nil, errors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
+ }
+ project, err := c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner)
+ if err != nil {
+ return nil, errors.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
+ }
+
+ if !apiApp.IsValid() {
+ return nil, errors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
+ }
+
+ appID, err := c.idGenerator.Next()
+ if err != nil {
+ return nil, err
+ }
+
+ return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator)
+}
+
+func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
+ apiApp.AppID = appID
+
+ addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner)
+ projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
+
+ events := []eventstore.Command{
+ project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiApp.AppID, apiApp.AppName),
+ }
+
+ var stringPw string
+ err = domain.SetNewClientID(apiApp, c.idGenerator, project)
+ if err != nil {
+ return nil, err
+ }
+ stringPw, err = domain.SetNewClientSecretIfNeeded(apiApp, appSecretGenerator)
+ if err != nil {
+ return nil, err
+ }
+ events = append(events, project_repo.NewAPIConfigAddedEvent(ctx,
+ projectAgg,
+ apiApp.AppID,
+ apiApp.ClientID,
+ apiApp.ClientSecret,
+ apiApp.AuthMethodType))
+
+ addedApplication.AppID = apiApp.AppID
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
@@ -98,38 +147,6 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
return result, nil
}
-func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (events []eventstore.Command, stringPW string, err error) {
- if !apiAppApp.IsValid() {
- return nil, "", errors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
- }
- apiAppApp.AppID, err = c.idGenerator.Next()
- if err != nil {
- return nil, "", err
- }
-
- events = []eventstore.Command{
- project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
- }
-
- var stringPw string
- err = domain.SetNewClientID(apiAppApp, c.idGenerator, proj)
- if err != nil {
- return nil, "", err
- }
- stringPw, err = domain.SetNewClientSecretIfNeeded(apiAppApp, appSecretGenerator)
- if err != nil {
- return nil, "", err
- }
- events = append(events, project_repo.NewAPIConfigAddedEvent(ctx,
- projectAgg,
- apiAppApp.AppID,
- apiAppApp.ClientID,
- apiAppApp.ClientSecret,
- apiAppApp.AuthMethodType))
-
- return events, stringPw, nil
-}
-
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
if apiApp.AppID == "" || apiApp.AggregateID == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go
index aabb310448..4deb39653c 100644
--- a/internal/command/project_application_oidc.go
+++ b/internal/command/project_application_oidc.go
@@ -116,56 +116,63 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has
}
}
-func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
- if application == nil || application.AggregateID == "" {
+func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
+ existingApp, err := c.getOIDCAppWriteModel(ctx, oidcApp.AggregateID, appID, resourceOwner)
+ if err != nil {
+ return nil, err
+ }
+ if existingApp.State != domain.AppStateUnspecified {
+ return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Application.AlreadyExisting")
+ }
+
+ project, err := c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
+ if err != nil {
+ return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound")
+ }
+
+ return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator)
+}
+
+func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
+ if oidcApp == nil || oidcApp.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid")
}
- project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
+ project, err := c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-3m9ss", "Errors.Project.NotFound")
}
- addedApplication := NewOIDCApplicationWriteModel(application.AggregateID, resourceOwner)
- projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
- events, stringPw, err := c.addOIDCApplication(ctx, projectAgg, project, application, resourceOwner, appSecretGenerator)
+
+ if oidcApp.AppName == "" || !oidcApp.IsValid() {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Application.Invalid")
+ }
+
+ appID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
- addedApplication.AppID = application.AppID
- pushedEvents, err := c.eventstore.Push(ctx, events...)
- if err != nil {
- return nil, err
- }
- err = AppendAndReduce(addedApplication, pushedEvents...)
- if err != nil {
- return nil, err
- }
- result := oidcWriteModelToOIDCConfig(addedApplication)
- result.ClientSecretString = stringPw
- result.FillCompliance()
- return result, nil
+
+ return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator)
}
-func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, oidcApp *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (events []eventstore.Command, stringPW string, err error) {
- if oidcApp.AppName == "" || !oidcApp.IsValid() {
- return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Application.Invalid")
- }
- oidcApp.AppID, err = c.idGenerator.Next()
- if err != nil {
- return nil, "", err
- }
+func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
- events = []eventstore.Command{
+ addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner)
+ projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
+
+ oidcApp.AppID = appID
+
+ events := []eventstore.Command{
project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
}
var stringPw string
- err = domain.SetNewClientID(oidcApp, c.idGenerator, proj)
+ err = domain.SetNewClientID(oidcApp, c.idGenerator, project)
if err != nil {
- return nil, "", err
+ return nil, err
}
stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator)
if err != nil {
- return nil, "", err
+ return nil, err
}
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
projectAgg,
@@ -187,7 +194,19 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
oidcApp.ClockSkew,
oidcApp.AdditionalOrigins))
- return events, stringPw, nil
+ addedApplication.AppID = oidcApp.AppID
+ pushedEvents, err := c.eventstore.Push(ctx, events...)
+ if err != nil {
+ return nil, err
+ }
+ err = AppendAndReduce(addedApplication, pushedEvents...)
+ if err != nil {
+ return nil, err
+ }
+ result := oidcWriteModelToOIDCConfig(addedApplication)
+ result.ClientSecretString = stringPw
+ result.FillCompliance()
+ return result, nil
}
func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
diff --git a/internal/command/project_grant.go b/internal/command/project_grant.go
index e484f33cfc..9e83044e3d 100644
--- a/internal/command/project_grant.go
+++ b/internal/command/project_grant.go
@@ -12,6 +12,18 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
+func (c *Commands) AddProjectGrantWithID(ctx context.Context, grant *domain.ProjectGrant, grantID string, resourceOwner string) (_ *domain.ProjectGrant, err error) {
+ existingMember, err := c.projectGrantWriteModelByID(ctx, grantID, grant.AggregateID, resourceOwner)
+ if err != nil && !caos_errs.IsNotFound(err) {
+ return nil, err
+ }
+ if existingMember != nil && existingMember.State != domain.ProjectGrantStateUnspecified {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-2b8fs", "Errors.Project.Grant.AlreadyExisting")
+ }
+
+ return c.addProjectGrantWithID(ctx, grant, grantID, resourceOwner)
+}
+
func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string) (_ *domain.ProjectGrant, err error) {
if !grant.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-3b8fs", "Errors.Project.Grant.Invalid")
@@ -20,10 +32,18 @@ func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGra
if err != nil {
return nil, err
}
- grant.GrantID, err = c.idGenerator.Next()
+
+ grantID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
+
+ return c.addProjectGrantWithID(ctx, grant, grantID, resourceOwner)
+}
+
+func (c *Commands) addProjectGrantWithID(ctx context.Context, grant *domain.ProjectGrant, grantID string, resourceOwner string) (_ *domain.ProjectGrant, err error) {
+ grant.GrantID = grantID
+
addedGrant := NewProjectGrantWriteModel(grant.GrantID, grant.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(
diff --git a/internal/command/user_human.go b/internal/command/user_human.go
index dfed12d4e6..b2efd78d42 100644
--- a/internal/command/user_human.go
+++ b/internal/command/user_human.go
@@ -47,6 +47,8 @@ type AddHuman struct {
Phone Phone
//Password is optional
Password string
+ //BcryptedPassword is optional
+ BcryptedPassword string
//PasswordChangeRequired is used if the `Password`-field is set
PasswordChangeRequired bool
Passwordless bool
@@ -54,14 +56,19 @@ type AddHuman struct {
Register bool
}
-func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) {
- if resourceOwner == "" {
- return nil, errors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
- }
- userID, err := c.idGenerator.Next()
+func (c *Commands) AddHumanWithID(ctx context.Context, resourceOwner string, userID string, human *AddHuman) (*domain.HumanDetails, error) {
+ existingHuman, err := c.getHumanWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
+ if isUserStateExists(existingHuman.UserState) {
+ return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-k2unb", "Errors.User.AlreadyExisting")
+ }
+
+ return c.addHumanWithID(ctx, resourceOwner, userID, human)
+}
+
+func (c *Commands) addHumanWithID(ctx context.Context, resourceOwner string, userID string, human *AddHuman) (*domain.HumanDetails, error) {
agg := user.NewAggregate(userID, resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, AddHumanCommand(agg, human, c.userPasswordAlg, c.userEncryption))
if err != nil {
@@ -83,6 +90,18 @@ func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *Ad
}, nil
}
+func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) {
+ if resourceOwner == "" {
+ return nil, errors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
+ }
+ userID, err := c.idGenerator.Next()
+ if err != nil {
+ return nil, err
+ }
+
+ return c.addHumanWithID(ctx, resourceOwner, userID, human)
+}
+
type humanCreationCommand interface {
eventstore.Command
AddPhoneData(phoneNumber string)
@@ -167,6 +186,10 @@ func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.Hash
createCmd.AddPasswordData(secret, human.PasswordChangeRequired)
}
+ if human.BcryptedPassword != "" {
+ createCmd.AddPasswordData(crypto.FillHash([]byte(human.BcryptedPassword), passwordAlg), human.PasswordChangeRequired)
+ }
+
cmds := make([]eventstore.Command, 0, 3)
cmds = append(cmds, createCmd)
@@ -274,6 +297,18 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
if err != nil {
return nil, nil, errors.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
+
+ if human.AggregateID != "" {
+ existing, err := c.getHumanWriteModelByID(ctx, human.AggregateID, human.ResourceOwner)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if existing.UserState != domain.UserStateUnspecified {
+ return nil, nil, errors.ThrowPreconditionFailed(nil, "COMMAND-ziuna", "Errors.User.AlreadyExisting")
+ }
+ }
+
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
if err != nil {
return nil, nil, err
@@ -355,8 +390,8 @@ func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Hum
if orgID == "" || !human.IsValid() {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
}
- if human.Password != nil && human.SecretString != "" {
- human.ChangeRequired = true
+ if human.Password != nil && human.Password.SecretString != "" {
+ human.Password.ChangeRequired = true
}
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
@@ -384,11 +419,11 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
if human != nil && human.Username == "" {
human.Username = human.EmailAddress
}
- if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.SecretString == "") {
+ if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.Password.SecretString == "") {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
- if human.Password != nil && human.SecretString != "" {
- human.ChangeRequired = false
+ if human.Password != nil && human.Password.SecretString != "" {
+ human.Password.ChangeRequired = false
}
return c.createHuman(ctx, orgID, human, link, true, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
@@ -410,14 +445,18 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
}
}
- userID, err := c.idGenerator.Next()
- if err != nil {
- return nil, nil, err
+
+ if human.AggregateID == "" {
+ userID, err := c.idGenerator.Next()
+ if err != nil {
+ return nil, nil, err
+ }
+ human.AggregateID = userID
}
- human.AggregateID = userID
+
human.SetNamesAsDisplayname()
if human.Password != nil {
- if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, human.ChangeRequired); err != nil {
+ if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, human.Password.ChangeRequired); err != nil {
return nil, nil, err
}
}
@@ -510,7 +549,10 @@ func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, h
human.StreetAddress)
}
if human.Password != nil {
- addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
+ addEvent.AddPasswordData(human.Password.SecretCrypto, human.Password.ChangeRequired)
+ }
+ if human.HashedPassword != nil {
+ addEvent.AddPasswordData(human.HashedPassword.SecretCrypto, false)
}
return addEvent
}
@@ -541,7 +583,10 @@ func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggrega
human.StreetAddress)
}
if human.Password != nil {
- addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
+ addEvent.AddPasswordData(human.Password.SecretCrypto, human.Password.ChangeRequired)
+ }
+ if human.HashedPassword != nil {
+ addEvent.AddPasswordData(human.HashedPassword.SecretCrypto, false)
}
return addEvent
}
diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go
index 960c346ae0..725a0b10e7 100644
--- a/internal/command/user_human_otp.go
+++ b/internal/command/user_human_otp.go
@@ -2,6 +2,7 @@ package command
import (
"context"
+ "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/domain"
@@ -11,6 +12,28 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
+func (c *Commands) ImportHumanOTP(ctx context.Context, userID, userAgentID, resourceowner string, key string) error {
+ encryptedSecret, err := crypto.Encrypt([]byte(key), c.multifactors.OTP.CryptoMFA)
+ if err != nil {
+ return err
+ }
+
+ otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner)
+ if err != nil {
+ return err
+ }
+ if otpWriteModel.State == domain.MFAStateReady {
+ return caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
+ }
+ userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
+
+ _, err = c.eventstore.Push(ctx,
+ user.NewHumanOTPAddedEvent(ctx, userAgg, encryptedSecret),
+ user.NewHumanOTPVerifiedEvent(ctx, userAgg, userAgentID),
+ )
+ return err
+}
+
func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string) (*domain.OTP, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
@@ -30,6 +53,7 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
}
+
otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner)
if err != nil {
return nil, err
@@ -38,6 +62,7 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
}
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
+
accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = human.EmailAddress
@@ -46,8 +71,8 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
if err != nil {
return nil, err
}
- _, err = c.eventstore.Push(ctx, user.NewHumanOTPAddedEvent(ctx, userAgg, secret))
+ _, err = c.eventstore.Push(ctx, user.NewHumanOTPAddedEvent(ctx, userAgg, secret))
if err != nil {
return nil, err
}
diff --git a/internal/command/user_idp_link.go b/internal/command/user_idp_link.go
index 266911aa25..4440b64927 100644
--- a/internal/command/user_idp_link.go
+++ b/internal/command/user_idp_link.go
@@ -11,6 +11,23 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
+func (c *Commands) AddUserIDPLink(ctx context.Context, userID, resourceOwner string, link *domain.UserIDPLink) (err error) {
+ if userID == "" {
+ return caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing")
+ }
+
+ linkWriteModel := NewUserIDPLinkWriteModel(userID, link.IDPConfigID, link.ExternalUserID, resourceOwner)
+ userAgg := UserAggregateFromWriteModel(&linkWriteModel.WriteModel)
+
+ event, err := c.addUserIDPLink(ctx, userAgg, link)
+ if err != nil {
+ return err
+ }
+
+ _, err = c.eventstore.Push(ctx, event)
+ return err
+}
+
func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOwner string, links []*domain.UserIDPLink) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing")
diff --git a/internal/command/user_machine.go b/internal/command/user_machine.go
index c7fae09fc4..38e8cff2a6 100644
--- a/internal/command/user_machine.go
+++ b/internal/command/user_machine.go
@@ -2,7 +2,6 @@ package command
import (
"context"
-
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/repository/user"
@@ -24,6 +23,29 @@ func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain
if err != nil {
return nil, err
}
+ return c.addMachineWithID(ctx, orgID, userID, machine, domainPolicy)
+}
+
+func (c *Commands) AddMachineWithID(ctx context.Context, orgID string, userID string, machine *domain.Machine) (*domain.Machine, error) {
+ existingMachine, err := c.machineWriteModelByID(ctx, userID, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if isUserStateExists(existingMachine.UserState) {
+ return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-k2una", "Errors.User.AlreadyExisting")
+ }
+ domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
+ if err != nil {
+ return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
+ }
+ if !domainPolicy.UserLoginMustBeDomain {
+ return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0dd", "Errors.User.Invalid")
+ }
+ return c.addMachineWithID(ctx, orgID, userID, machine, domainPolicy)
+}
+
+func (c *Commands) addMachineWithID(ctx context.Context, orgID string, userID string, machine *domain.Machine, domainPolicy *domain.DomainPolicy) (*domain.Machine, error) {
+
machine.AggregateID = userID
addedMachine := NewMachineWriteModel(machine.AggregateID, orgID)
userAgg := UserAggregateFromWriteModel(&addedMachine.WriteModel)
diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go
index 36de8d6155..9799faea60 100644
--- a/internal/crypto/crypto.go
+++ b/internal/crypto/crypto.go
@@ -124,3 +124,11 @@ func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error {
}
return alg.CompareHash(value.Crypted, comparer)
}
+
+func FillHash(value []byte, alg HashAlgorithm) *CryptoValue {
+ return &CryptoValue{
+ CryptoType: TypeHash,
+ Algorithm: alg.Algorithm(),
+ Crypted: value,
+ }
+}
diff --git a/internal/domain/human.go b/internal/domain/human.go
index ce3c2bf42b..e83797a12e 100644
--- a/internal/domain/human.go
+++ b/internal/domain/human.go
@@ -19,6 +19,7 @@ type Human struct {
Username string
State UserState
*Password
+ *HashedPassword
*Profile
*Email
*Phone
@@ -88,7 +89,7 @@ func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwor
}
func (u *Human) IsInitialState(passwordless, externalIDPs bool) bool {
- return u.Email == nil || !u.IsEmailVerified || !externalIDPs && !passwordless && (u.Password == nil || u.SecretString == "")
+ return u.Email == nil || !u.IsEmailVerified || !externalIDPs && !passwordless && (u.Password == nil || u.Password.SecretString == "") && (u.HashedPassword == nil || u.HashedPassword.SecretString == "")
}
func NewInitUserCode(generator crypto.Generator) (*InitUserCode, error) {
diff --git a/internal/domain/human_hashed_password.go b/internal/domain/human_hashed_password.go
new file mode 100644
index 0000000000..5aa144fe52
--- /dev/null
+++ b/internal/domain/human_hashed_password.go
@@ -0,0 +1,24 @@
+package domain
+
+import (
+ "github.com/zitadel/zitadel/internal/crypto"
+ es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
+)
+
+type HashedPassword struct {
+ es_models.ObjectRoot
+
+ SecretString string
+ SecretCrypto *crypto.CryptoValue
+}
+
+func NewHashedPassword(password, algorithm string) *HashedPassword {
+ return &HashedPassword{
+ SecretString: password,
+ SecretCrypto: &crypto.CryptoValue{
+ CryptoType: crypto.TypeHash,
+ Algorithm: algorithm,
+ Crypted: []byte(password),
+ },
+ }
+}
diff --git a/internal/eventstore/handler/handler_projection.go b/internal/eventstore/handler/handler_projection.go
index e495caf71a..ae2c4c2c80 100644
--- a/internal/eventstore/handler/handler_projection.go
+++ b/internal/eventstore/handler/handler_projection.go
@@ -168,7 +168,7 @@ func (h *ProjectionHandler) subscribe(ctx context.Context) {
index, err := h.Process(ctx, events...)
if err != nil || index < len(events)-1 {
- logging.WithFields("projection", h.ProjectionName).WithError(err).Error("unable to process all events from subscription")
+ logging.WithFields("projection", h.ProjectionName).WithError(err).Warn("unable to process all events from subscription")
}
}
}
diff --git a/internal/query/idp.go b/internal/query/idp.go
index 2e6ea3e7e6..dee1c5e6cf 100644
--- a/internal/query/idp.go
+++ b/internal/query/idp.go
@@ -502,3 +502,15 @@ func prepareIDPsQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPs, error)) {
}, nil
}
}
+
+func (q *Queries) GetOIDCIDPClientSecret(ctx context.Context, shouldRealTime bool, resourceowner, idpID string) (string, error) {
+ idp, err := q.IDPByIDAndResourceOwner(ctx, shouldRealTime, idpID, resourceowner)
+ if err != nil {
+ return "", err
+ }
+
+ if idp.ClientSecret != nil && idp.ClientSecret.Crypted != nil {
+ return crypto.DecryptString(idp.ClientSecret, q.idpConfigEncryption)
+ }
+ return "", errors.ThrowNotFound(nil, "QUERY-bsm2o", "Errors.Query.NotFound")
+}
diff --git a/internal/query/query.go b/internal/query/query.go
index 328eec42fa..75dca49d66 100644
--- a/internal/query/query.go
+++ b/internal/query/query.go
@@ -4,6 +4,8 @@ import (
"context"
"database/sql"
"fmt"
+ sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
+ "github.com/zitadel/zitadel/internal/domain"
"net/http"
"sync"
@@ -27,6 +29,8 @@ type Queries struct {
eventstore *eventstore.Eventstore
client *sql.DB
+ idpConfigEncryption crypto.EncryptionAlgorithm
+
DefaultLanguage language.Tag
LoginDir http.FileSystem
NotificationDir http.FileSystem
@@ -35,9 +39,10 @@ type Queries struct {
NotificationTranslationFileContents map[string][]byte
supportedLangs []language.Tag
zitadelRoles []authz.RoleMapping
+ multifactors domain.MultifactorConfigs
}
-func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
+func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, defaults sd.SystemDefaults, idpConfigEncryption, otpEncryption, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
statikLoginFS, err := fs.NewWithNamespace("login")
if err != nil {
return nil, fmt.Errorf("unable to start login statik dir")
@@ -66,6 +71,14 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql
keypair.RegisterEventMappers(repo.eventstore)
usergrant.RegisterEventMappers(repo.eventstore)
+ repo.idpConfigEncryption = idpConfigEncryption
+ repo.multifactors = domain.MultifactorConfigs{
+ OTP: domain.OTPConfig{
+ CryptoMFA: otpEncryption,
+ Issuer: defaults.Multifactors.OTP.Issuer,
+ },
+ }
+
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
if err != nil {
return nil, err
diff --git a/internal/query/user_otp.go b/internal/query/user_otp.go
new file mode 100644
index 0000000000..9e1029a511
--- /dev/null
+++ b/internal/query/user_otp.go
@@ -0,0 +1,91 @@
+package query
+
+import (
+ "context"
+ "github.com/zitadel/zitadel/internal/crypto"
+ "github.com/zitadel/zitadel/internal/domain"
+ caos_errs "github.com/zitadel/zitadel/internal/errors"
+ "github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/repository/user"
+ "github.com/zitadel/zitadel/internal/telemetry/tracing"
+)
+
+func (q *Queries) GetHumanOTPSecret(ctx context.Context, userID, resourceowner string) (string, error) {
+ if userID == "" {
+ return "", caos_errs.ThrowPreconditionFailed(nil, "QUERY-8N9ds", "Errors.User.UserIDMissing")
+ }
+ existingOTP, err := q.otpWriteModelByID(ctx, userID, resourceowner)
+ if err != nil {
+ return "", err
+ }
+ if existingOTP.State != domain.MFAStateReady {
+ return "", caos_errs.ThrowNotFound(nil, "QUERY-01982h", "Errors.User.NotFound")
+ }
+
+ return crypto.DecryptString(existingOTP.Secret, q.multifactors.OTP.CryptoMFA)
+}
+
+func (q *Queries) otpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPWriteModel, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ writeModel = NewHumanOTPWriteModel(userID, resourceOwner)
+ err = q.eventstore.FilterToQueryReducer(ctx, writeModel)
+ if err != nil {
+ return nil, err
+ }
+ return writeModel, nil
+}
+
+type HumanOTPWriteModel struct {
+ eventstore.WriteModel
+
+ State domain.MFAState
+ Secret *crypto.CryptoValue
+}
+
+func NewHumanOTPWriteModel(userID, resourceOwner string) *HumanOTPWriteModel {
+ return &HumanOTPWriteModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: userID,
+ ResourceOwner: resourceOwner,
+ },
+ }
+}
+
+func (wm *HumanOTPWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *user.HumanOTPAddedEvent:
+ wm.Secret = e.Secret
+ wm.State = domain.MFAStateNotReady
+ case *user.HumanOTPVerifiedEvent:
+ wm.State = domain.MFAStateReady
+ case *user.HumanOTPRemovedEvent:
+ wm.State = domain.MFAStateRemoved
+ case *user.UserRemovedEvent:
+ wm.State = domain.MFAStateRemoved
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+func (wm *HumanOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
+ query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ AddQuery().
+ AggregateTypes(user.AggregateType).
+ AggregateIDs(wm.AggregateID).
+ EventTypes(user.HumanMFAOTPAddedType,
+ user.HumanMFAOTPVerifiedType,
+ user.HumanMFAOTPRemovedType,
+ user.UserRemovedType,
+ user.UserV1MFAOTPAddedType,
+ user.UserV1MFAOTPVerifiedType,
+ user.UserV1MFAOTPRemovedType).
+ Builder()
+
+ if wm.ResourceOwner != "" {
+ query.ResourceOwner(wm.ResourceOwner)
+ }
+ return query
+}
diff --git a/internal/query/user_password.go b/internal/query/user_password.go
new file mode 100644
index 0000000000..7fd48564bc
--- /dev/null
+++ b/internal/query/user_password.go
@@ -0,0 +1,140 @@
+package query
+
+import (
+ "context"
+ "github.com/zitadel/zitadel/internal/crypto"
+ "github.com/zitadel/zitadel/internal/domain"
+ caos_errs "github.com/zitadel/zitadel/internal/errors"
+ "github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/repository/user"
+ "github.com/zitadel/zitadel/internal/telemetry/tracing"
+ "time"
+)
+
+type HumanPasswordWriteModel struct {
+ eventstore.WriteModel
+
+ Secret *crypto.CryptoValue
+ SecretChangeRequired bool
+
+ Code *crypto.CryptoValue
+ CodeCreationDate time.Time
+ CodeExpiry time.Duration
+ PasswordCheckFailedCount uint64
+
+ UserState domain.UserState
+}
+
+func (q *Queries) GetHumanPassword(ctx context.Context, orgID, userID string) (passwordHash []byte, algorithm string, err error) {
+ if userID == "" {
+ return nil, "", caos_errs.ThrowInvalidArgument(nil, "QUERY-4Mfsf", "Errors.User.UserIDMissing")
+ }
+ existingPassword, err := q.passwordWriteModel(ctx, userID, orgID)
+ if err != nil {
+ return nil, "", caos_errs.ThrowInternal(nil, "QUERY-p1k1n2i", "Errors.User.NotFound")
+ }
+ if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
+ return nil, "", caos_errs.ThrowPreconditionFailed(nil, "QUERY-3n77z", "Errors.User.NotFound")
+ }
+
+ if existingPassword.Secret != nil && existingPassword.Secret.Crypted != nil {
+ return existingPassword.Secret.Crypted, existingPassword.Secret.Algorithm, nil
+ }
+
+ return nil, "", nil
+}
+
+func (q *Queries) passwordWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPasswordWriteModel, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ writeModel = NewHumanPasswordWriteModel(userID, resourceOwner)
+ err = q.eventstore.FilterToQueryReducer(ctx, writeModel)
+ if err != nil {
+ return nil, err
+ }
+ return writeModel, nil
+}
+
+func NewHumanPasswordWriteModel(userID, resourceOwner string) *HumanPasswordWriteModel {
+ return &HumanPasswordWriteModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: userID,
+ ResourceOwner: resourceOwner,
+ },
+ }
+}
+
+func (wm *HumanPasswordWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *user.HumanAddedEvent:
+ wm.Secret = e.Secret
+ wm.SecretChangeRequired = e.ChangeRequired
+ wm.UserState = domain.UserStateActive
+ case *user.HumanRegisteredEvent:
+ wm.Secret = e.Secret
+ wm.SecretChangeRequired = e.ChangeRequired
+ wm.UserState = domain.UserStateActive
+ case *user.HumanInitialCodeAddedEvent:
+ wm.UserState = domain.UserStateInitial
+ case *user.HumanInitializedCheckSucceededEvent:
+ wm.UserState = domain.UserStateActive
+ case *user.HumanPasswordChangedEvent:
+ wm.Secret = e.Secret
+ wm.SecretChangeRequired = e.ChangeRequired
+ wm.Code = nil
+ wm.PasswordCheckFailedCount = 0
+ case *user.HumanPasswordCodeAddedEvent:
+ wm.Code = e.Code
+ wm.CodeCreationDate = e.CreationDate()
+ wm.CodeExpiry = e.Expiry
+ case *user.HumanEmailVerifiedEvent:
+ if wm.UserState == domain.UserStateInitial {
+ wm.UserState = domain.UserStateActive
+ }
+ case *user.HumanPasswordCheckFailedEvent:
+ wm.PasswordCheckFailedCount += 1
+ case *user.HumanPasswordCheckSucceededEvent:
+ wm.PasswordCheckFailedCount = 0
+ case *user.UserUnlockedEvent:
+ wm.PasswordCheckFailedCount = 0
+ case *user.UserRemovedEvent:
+ wm.UserState = domain.UserStateDeleted
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder {
+ query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ AddQuery().
+ AggregateTypes(user.AggregateType).
+ AggregateIDs(wm.AggregateID).
+ EventTypes(user.HumanAddedType,
+ user.HumanRegisteredType,
+ user.HumanInitialCodeAddedType,
+ user.HumanInitializedCheckSucceededType,
+ user.HumanPasswordChangedType,
+ user.HumanPasswordCodeAddedType,
+ user.HumanEmailVerifiedType,
+ user.HumanPasswordCheckFailedType,
+ user.HumanPasswordCheckSucceededType,
+ user.UserRemovedType,
+ user.UserUnlockedType,
+ user.UserV1AddedType,
+ user.UserV1RegisteredType,
+ user.UserV1InitialCodeAddedType,
+ user.UserV1InitializedCheckSucceededType,
+ user.UserV1PasswordChangedType,
+ user.UserV1PasswordCodeAddedType,
+ user.UserV1EmailVerifiedType,
+ user.UserV1PasswordCheckFailedType,
+ user.UserV1PasswordCheckSucceededType).
+ Builder()
+
+ if wm.ResourceOwner != "" {
+ query.ResourceOwner(wm.ResourceOwner)
+ }
+ return query
+}
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index fceb2a2848..21e44a2ed9 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -10,6 +10,8 @@ import "zitadel/policy.proto";
import "zitadel/settings.proto";
import "zitadel/text.proto";
import "zitadel/member.proto";
+import "zitadel/management.proto";
+import "zitadel/v1.proto";
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
@@ -460,7 +462,7 @@ service AdminService {
};
}
- //Checks whether an organisation exists by the given parameters
+ //Checks whether an organisation exists by the given parameters
rpc IsOrgUnique(IsOrgUniqueRequest) returns (IsOrgUniqueResponse) {
option (google.api.http) = {
get: "/orgs/_is_unique";
@@ -553,7 +555,7 @@ service AdminService {
};
}
- //Creates a new org and user
+ //Creates a new org and user
// and adds the user to the orgs members as ORG_OWNER
rpc SetUpOrg(SetUpOrgRequest) returns (SetUpOrgResponse) {
option (google.api.http) = {
@@ -1471,7 +1473,7 @@ service AdminService {
};
};
}
-
+
//Updates the default login policy of ZITADEL
// it impacts all organisations without a customised policy
rpc UpdateLoginPolicy(UpdateLoginPolicyRequest) returns (UpdateLoginPolicyResponse) {
@@ -1536,7 +1538,7 @@ service AdminService {
post: "/policies/login/idps";
body: "*";
};
-
+
option (zitadel.v1.auth_option) = {
permission: "iam.policy.write";
};
@@ -1887,7 +1889,7 @@ service AdminService {
};
};
}
-
+
//Updates the default password age policy of ZITADEL
// it impacts all organisations without a customised policy
rpc UpdatePasswordAgePolicy(UpdatePasswordAgePolicyRequest) returns (UpdatePasswordAgePolicyResponse) {
@@ -1945,7 +1947,7 @@ service AdminService {
};
};
}
-
+
//Updates the default lockout policy of ZITADEL
// it impacts all organisations without a customised policy
rpc UpdateLockoutPolicy(UpdateLockoutPolicyRequest) returns (UpdateLockoutPolicyResponse) {
@@ -2362,7 +2364,7 @@ service AdminService {
};
}
- //Returns the IAM roles visible for the requested user
+ //Returns the IAM roles visible for the requested user
rpc ListIAMMemberRoles(ListIAMMemberRolesRequest) returns (ListIAMMemberRolesResponse) {
option (google.api.http) = {
post: "/members/roles/_search";
@@ -2564,7 +2566,7 @@ service AdminService {
}
//Returns event descriptions which cannot be processed.
- // It's possible that some events need some retries.
+ // It's possible that some events need some retries.
// For example if the SMTP-API wasn't able to send an email at the first time
rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) {
option (google.api.http) = {
@@ -2629,6 +2631,29 @@ service AdminService {
};
};
}
+
+ // Imports data into instance and creates different objects
+ rpc ImportData(ImportDataRequest) returns (ImportDataResponse) {
+ option (google.api.http) = {
+ post: "/import";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.write";
+ };
+ }
+ // Exports data from instance
+ rpc ExportData(ExportDataRequest) returns (ExportDataResponse) {
+ option (google.api.http) = {
+ post: "/export";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.read";
+ };
+ }
}
@@ -3070,7 +3095,7 @@ message SetUpOrgRequest {
];
bool is_phone_verified = 2;
}
-
+
string user_name = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
@@ -3079,7 +3104,7 @@ message SetUpOrgRequest {
example: "\"mr_long_neck\"";
}
];
-
+
Profile profile = 2 [(validate.rules).message.required = true];
Email email = 3 [(validate.rules).message.required = true];
Phone phone = 4;
@@ -3905,7 +3930,7 @@ message RemoveIDPFromLoginPolicyRequest {
required: ["idp_id"]
};
};
-
+
string idp_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
@@ -3934,7 +3959,7 @@ message AddSecondFactorToLoginPolicyRequest {
required: ["type"]
};
};
-
+
zitadel.policy.v1.SecondFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}];
}
@@ -3984,7 +4009,7 @@ message RemoveMultiFactorFromLoginPolicyRequest {
required: ["type"]
};
};
-
+
zitadel.policy.v1.MultiFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}];
}
@@ -4621,3 +4646,187 @@ message FailedEvent {
}
];
}
+
+message ImportDataRequest {
+ message LocalInput{
+ string path = 1;
+ }
+ message S3Input{
+ string path = 1;
+ string endpoint = 2;
+ string access_key_id =3;
+ string secret_access_key = 4;
+ bool ssl = 5;
+ string bucket = 6;
+ }
+ message GCSInput{
+ string bucket = 1;
+ string serviceaccount_json = 2;
+ string path = 3;
+ }
+
+ oneof data {
+ ImportDataOrg data_orgs = 1;
+ zitadel.v1.v1.ImportDataOrg data_orgsv1 = 2;
+ LocalInput data_orgs_local = 3;
+ LocalInput data_orgsv1_local = 4;
+ S3Input data_orgs_s3 = 5;
+ S3Input data_orgsv1_s3 = 6;
+ GCSInput data_orgs_gcs = 7;
+ GCSInput data_orgsv1_gcs = 8;
+ }
+ string timeout = 9;
+}
+
+message ImportDataOrg {
+ repeated DataOrg orgs = 1;
+}
+
+message DataOrg {
+ string org_id = 1;
+ zitadel.management.v1.AddOrgRequest org = 3;
+ AddCustomDomainPolicyRequest domain_policy = 4;
+ zitadel.management.v1.AddCustomLabelPolicyRequest label_policy = 5;
+ zitadel.management.v1.AddCustomLockoutPolicyRequest lockout_policy = 6;
+ zitadel.management.v1.AddCustomLoginPolicyRequest login_policy = 7;
+ zitadel.management.v1.AddCustomPasswordComplexityPolicyRequest password_complexity_policy = 8;
+ zitadel.management.v1.AddCustomPrivacyPolicyRequest privacy_policy = 9;
+
+ repeated zitadel.v1.v1.DataProject projects = 10;
+ repeated zitadel.management.v1.AddProjectRoleRequest project_roles = 11;
+ repeated zitadel.v1.v1.DataAPIApplication api_apps = 12;
+ repeated zitadel.v1.v1.DataOIDCApplication oidc_apps = 13;
+ repeated zitadel.v1.v1.DataHumanUser human_users = 14;
+ repeated zitadel.v1.v1.DataMachineUser machine_users = 15;
+ repeated zitadel.management.v1.SetTriggerActionsRequest trigger_actions = 16;
+ repeated zitadel.v1.v1.DataAction actions = 17;
+
+ repeated zitadel.v1.v1.DataProjectGrant project_grants = 18;
+ repeated zitadel.management.v1.AddUserGrantRequest user_grants = 19;
+
+ repeated zitadel.management.v1.AddOrgMemberRequest org_members = 20;
+ repeated zitadel.management.v1.AddProjectMemberRequest project_members = 21;
+ repeated zitadel.management.v1.AddProjectGrantMemberRequest project_grant_members = 22;
+
+ repeated zitadel.management.v1.SetUserMetadataRequest user_metadata = 23;
+
+ repeated zitadel.management.v1.SetCustomLoginTextsRequest login_texts = 24;
+
+ repeated zitadel.management.v1.SetCustomInitMessageTextRequest init_messages = 25;
+ repeated zitadel.management.v1.SetCustomPasswordResetMessageTextRequest password_reset_messages = 26;
+ repeated zitadel.management.v1.SetCustomVerifyEmailMessageTextRequest verify_email_messages = 27;
+ repeated zitadel.management.v1.SetCustomVerifyPhoneMessageTextRequest verify_phone_messages = 28;
+ repeated zitadel.management.v1.SetCustomDomainClaimedMessageTextRequest domain_claimed_messages = 29;
+ repeated zitadel.management.v1.SetCustomPasswordlessRegistrationMessageTextRequest passwordless_registration_messages = 30;
+
+ repeated zitadel.v1.v1.DataOIDCIDP oidc_idps = 31;
+ repeated zitadel.v1.v1.DataJWTIDP jwt_idps = 32;
+
+ repeated zitadel.idp.v1.IDPUserLink user_links = 33;
+ repeated zitadel.org.v1.Domain domains = 34;
+}
+
+message ImportDataResponse{
+ repeated ImportDataError errors = 1;
+ ImportDataSuccess success = 2;
+}
+
+message ImportDataError{
+ string type = 1;
+ string id = 2;
+ string message = 3;
+}
+
+message ImportDataSuccess {
+ repeated ImportDataSuccessOrg orgs = 1;
+}
+
+message ImportDataSuccessOrg{
+ string org_id = 1;
+ repeated string project_ids = 2;
+ repeated string project_roles = 3;
+ repeated string oidc_app_ids = 4;
+ repeated string api_app_ids = 5;
+ repeated string human_user_ids = 6;
+ repeated string machine_user_ids = 7;
+ repeated string action_ids = 8;
+ repeated zitadel.management.v1.SetTriggerActionsRequest trigger_actions = 9;
+ repeated ImportDataSuccessProjectGrant project_grants = 10;
+ repeated ImportDataSuccessUserGrant user_grants = 11;
+ repeated string org_members = 12;
+ repeated ImportDataSuccessProjectMember project_members = 13;
+ repeated ImportDataSuccessProjectGrantMember project_grant_members = 14;
+ repeated string oidc_ipds = 15;
+ repeated string jwt_idps = 16;
+ repeated string idp_links = 17;
+ repeated ImportDataSuccessUserLinks user_links = 18;
+ repeated ImportDataSuccessUserMetadata user_metadata = 19;
+ repeated string domains = 20;
+}
+
+message ImportDataSuccessProjectGrant{
+ string grant_id = 1;
+ string project_id = 2;
+ string org_id = 3;
+}
+
+message ImportDataSuccessUserGrant{
+ string project_id = 1;
+ string user_id = 2;
+}
+
+message ImportDataSuccessProjectMember{
+ string project_id = 1;
+ string user_id = 2;
+}
+
+message ImportDataSuccessProjectGrantMember{
+ string project_id = 1;
+ string grant_id = 2;
+ string user_id = 3;
+}
+
+message ImportDataSuccessUserLinks {
+ string user_id = 1;
+ string external_user_id = 2;
+ string display_name = 3;
+ string idp_id = 4;
+}
+
+message ImportDataSuccessUserMetadata {
+ string user_id = 1;
+ string key = 2;
+}
+
+message ExportDataRequest {
+ message LocalOutput{
+ string path = 1;
+ }
+ message S3Output{
+ string path = 1;
+ string endpoint = 2;
+ string access_key_id =3;
+ string secret_access_key = 4;
+ bool ssl = 5;
+ string bucket = 6;
+ }
+ message GCSOutput{
+ string bucket = 1;
+ string serviceaccount_json = 2;
+ string path = 3;
+ }
+
+ repeated string org_ids = 1;
+ repeated string excluded_org_ids = 2;
+ bool with_passwords = 3;
+ bool with_otp = 4;
+ bool response_output = 5;
+ LocalOutput local_output = 6;
+ S3Output s3_output = 7;
+ GCSOutput gcs_output = 8;
+ string timeout = 9;
+}
+
+message ExportDataResponse {
+ repeated DataOrg orgs = 1;
+}
\ No newline at end of file
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index 11da17b0d5..c49651b309 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -3007,6 +3007,10 @@ message ImportHumanUserRequest {
string phone = 1 [(validate.rules).string = {min_len: 1, max_len: 50, prefix: "+"}];
bool is_phone_verified = 2;
}
+ message HashedPassword{
+ string value = 1;
+ string algorithm = 2;
+ }
string user_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
@@ -3014,8 +3018,11 @@ message ImportHumanUserRequest {
Email email = 3 [(validate.rules).message.required = true];
Phone phone = 4;
string password = 5;
- bool password_change_required = 6;
- bool request_passwordless_registration = 7;
+ HashedPassword hashed_password = 6;
+ bool password_change_required = 7;
+ bool request_passwordless_registration = 8;
+
+ string otp_code = 9;
}
message ImportHumanUserResponse {
diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto
index 6123249abb..2d9e47a279 100644
--- a/proto/zitadel/system.proto
+++ b/proto/zitadel/system.proto
@@ -11,324 +11,323 @@ import "validate/validate.proto";
package zitadel.system.v1;
-option go_package ="github.com/zitadel/zitadel/pkg/grpc/system";
+option go_package = "github.com/zitadel/zitadel/pkg/grpc/system";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
- info: {
- title: "System API";
- version: "1.0";
- description: "This API is intended to configure and manage the different tenants whithin ZITADEL.";
- contact:{
- name: "ZITADEL"
- url: "https://zitadel.com"
- email: "hi@zitadel.com"
- }
- license: {
- name: "Apache 2.0",
- url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
- };
+ info: {
+ title: "System API";
+ version: "1.0";
+ description: "This API is intended to configure and manage the different tenants whithin ZITADEL.";
+ contact:{
+ name: "ZITADEL"
+ url: "https://zitadel.com"
+ email: "hi@zitadel.com"
+ }
+ license: {
+ name: "Apache 2.0",
+ url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
};
+ };
- schemes: HTTPS;
- schemes: HTTP;
+ schemes: HTTPS;
+ schemes: HTTP;
- consumes: "application/json";
- consumes: "application/grpc";
+ consumes: "application/json";
+ consumes: "application/grpc";
- produces: "application/json";
- produces: "application/grpc";
+ produces: "application/json";
+ produces: "application/grpc";
- consumes: "application/grpc-web+proto";
- produces: "application/grpc-web+proto";
+ consumes: "application/grpc-web+proto";
+ produces: "application/grpc-web+proto";
- host: "api.zitadel.ch";
- base_path: "/system/v1";
+ host: "api.zitadel.ch";
+ base_path: "/system/v1";
- external_docs: {
- description: "Detailed information about ZITADEL",
- url: "https://docs.zitadel.com"
- }
+ external_docs: {
+ description: "Detailed information about ZITADEL",
+ url: "https://docs.zitadel.com"
+ }
- responses: {
- key: "403";
- value: {
- description: "Returned when the user does not have permission to access the resource.";
- schema: {
- json_schema: {
- ref: "#/definitions/rpcStatus";
- }
- }
+ responses: {
+ key: "403";
+ value: {
+ description: "Returned when the user does not have permission to access the resource.";
+ schema: {
+ json_schema: {
+ ref: "#/definitions/rpcStatus";
}
+ }
}
- responses: {
- key: "404";
- value: {
- description: "Returned when the resource does not exist.";
- schema: {
- json_schema: {
- ref: "#/definitions/rpcStatus";
- }
- }
+ }
+ responses: {
+ key: "404";
+ value: {
+ description: "Returned when the resource does not exist.";
+ schema: {
+ json_schema: {
+ ref: "#/definitions/rpcStatus";
}
+ }
}
+ }
};
service SystemService {
- //Indicates if ZITADEL is running.
- // It respondes as soon as ZITADEL started
- rpc Healthz(HealthzRequest) returns (HealthzResponse) {
- option (google.api.http) = {
- get: "/healthz";
+ //Indicates if ZITADEL is running.
+ // It respondes as soon as ZITADEL started
+ rpc Healthz(HealthzRequest) returns (HealthzResponse) {
+ option (google.api.http) = {
+ get: "/healthz";
+ };
+
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "probes";
+ responses: {
+ key: "200";
+ value: {
+ description: "ZITADEL started";
};
-
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "probes";
- responses: {
- key: "200";
- value: {
- description: "ZITADEL started";
- };
- }
- responses: {
- key: "default";
- value: {
- description: "ZITADEL NOT started yet";
- };
- }
+ }
+ responses: {
+ key: "default";
+ value: {
+ description: "ZITADEL NOT started yet";
};
- }
+ }
+ };
+ }
- // Returns a list of ZITADEL instances
- rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
- option (google.api.http) = {
- post: "/instances/_search"
- body: "*"
+ // Returns a list of ZITADEL instances
+ rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
+ option (google.api.http) = {
+ post: "/instances/_search"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Returns the detail of an instance
+ rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
+ option (google.api.http) = {
+ get: "/instances/{instance_id}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Creates a new instance with all needed setup data
+ // This might take some time
+ rpc AddInstance(AddInstanceRequest) returns (AddInstanceResponse) {
+ option (google.api.http) = {
+ post: "/instances"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Removes a instances
+ // This might take some time
+ rpc RemoveInstance(RemoveInstanceRequest) returns (RemoveInstanceResponse) {
+ option (google.api.http) = {
+ delete: "/instances/{instance_id}"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Checks if a domain exists
+ rpc ExistsDomain(ExistsDomainRequest) returns (ExistsDomainResponse) {
+ option (google.api.http) = {
+ post: "/domains/{domain}/_exists";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Returns the custom domains of an instance
+ rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
+ option (google.api.http) = {
+ post: "/instances/{instance_id}/domains/_search";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Returns the domain of an instance
+ rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
+ option (google.api.http) = {
+ post: "/instances/{instance_id}/domains";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Returns the domain of an instance
+ rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
+ option (google.api.http) = {
+ delete: "/instances/{instance_id}/domains/{domain}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ // Returns the domain of an instance
+ rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
+ option (google.api.http) = {
+ post: "/instances/{instance_id}/domains/_set_primary";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+ }
+
+ //Returns all stored read models of ZITADEL
+ // views are used for search optimisation and optimise request latencies
+ // they represent the delta of the event happend on the objects
+ rpc ListViews(ListViewsRequest) returns (ListViewsResponse) {
+ option (google.api.http) = {
+ post: "/views/_search";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "views";
+ external_docs: {
+ url: "https://docs.zitadel.com/concepts#Software_Architecture";
+ description: "details of ZITADEL's event driven software concepts";
+ };
+ responses: {
+ key: "200";
+ value: {
+ description: "Views for query operations";
};
+ };
+ };
+ }
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
+ //Truncates the delta of the change stream
+ // be carefull with this function because ZITADEL has to
+ // recompute the deltas after they got cleared.
+ // Search requests will return wrong results until all deltas are recomputed
+ rpc ClearView(ClearViewRequest) returns (ClearViewResponse) {
+ option (google.api.http) = {
+ post: "/views/{database}/{view_name}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "views";
+ external_docs: {
+ url: "https://docs.zitadel.com/concepts#Software_Architecture";
+ description: "details of ZITADEL's event driven software concepts";
+ };
+ responses: {
+ key: "200";
+ value: {
+ description: "View cleared";
};
- }
+ };
+ };
+ }
- // Returns the detail of an instance
- rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
- option (google.api.http) = {
- get: "/instances/{instance_id}";
+ //Returns event descriptions which cannot be processed.
+ // It's possible that some events need some retries.
+ // For example if the SMTP-API wasn't able to send an email at the first time
+ rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) {
+ option (google.api.http) = {
+ post: "/failedevents/_search";
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "failed events";
+ external_docs: {
+ url: "https://docs.zitadel.com/concepts#Software_Architecture";
+ description: "details of ZITADEL's event driven software concepts";
+ };
+ responses: {
+ key: "200";
+ value: {
+ description: "Events which were not processed by the views";
};
+ };
+ };
+ }
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
+ //Deletes the event from failed events view.
+ // the event is not removed from the change stream
+ // This call is usefull if the system was able to process the event later.
+ // e.g. if the second try of sending an email was successful. the first try produced a
+ // failed event. You can find out if it worked on the `failure_count`
+ rpc RemoveFailedEvent(RemoveFailedEventRequest) returns (RemoveFailedEventResponse) {
+ option (google.api.http) = {
+ delete: "/failedevents/{database}/{view_name}/{failed_sequence}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "authenticated";
+ };
+
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "failed events";
+ external_docs: {
+ url: "https://docs.zitadel.com/concepts#Software_Architecture";
+ description: "details of ZITADEL's event driven software concepts";
+ };
+ responses: {
+ key: "200";
+ value: {
+ description: "Events removed from the list";
};
- }
-
- // Creates a new instance with all needed setup data
- // This might take some time
- rpc AddInstance(AddInstanceRequest) returns (AddInstanceResponse) {
- option (google.api.http) = {
- post: "/instances"
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
- // Removes a instances
- // This might take some time
- rpc RemoveInstance(RemoveInstanceRequest) returns (RemoveInstanceResponse) {
- option (google.api.http) = {
- delete: "/instances/{instance_id}"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
- // Checks if a domain exists
- rpc ExistsDomain(ExistsDomainRequest) returns (ExistsDomainResponse) {
- option (google.api.http) = {
- post: "/domains/{domain}/_exists";
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
- // Returns the custom domains of an instance
- rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
- option (google.api.http) = {
- post: "/instances/{instance_id}/domains/_search";
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
- // Returns the domain of an instance
- rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
- option (google.api.http) = {
- post: "/instances/{instance_id}/domains";
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
- // Returns the domain of an instance
- rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
- option (google.api.http) = {
- delete: "/instances/{instance_id}/domains/{domain}";
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
- // Returns the domain of an instance
- rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
- option (google.api.http) = {
- post: "/instances/{instance_id}/domains/_set_primary";
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
- }
-
-
- //Returns all stored read models of ZITADEL
- // views are used for search optimisation and optimise request latencies
- // they represent the delta of the event happend on the objects
- rpc ListViews(ListViewsRequest) returns (ListViewsResponse) {
- option (google.api.http) = {
- post: "/views/_search";
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
-
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "views";
- external_docs: {
- url: "https://docs.zitadel.com/concepts#Software_Architecture";
- description: "details of ZITADEL's event driven software concepts";
- };
- responses: {
- key: "200";
- value: {
- description: "Views for query operations";
- };
+ };
+ responses: {
+ key: "400";
+ value: {
+ description: "failed event not found";
+ schema: {
+ json_schema: {
+ ref: "#/definitions/rpcStatus";
};
+ };
};
- }
-
- //Truncates the delta of the change stream
- // be carefull with this function because ZITADEL has to
- // recompute the deltas after they got cleared.
- // Search requests will return wrong results until all deltas are recomputed
- rpc ClearView(ClearViewRequest) returns (ClearViewResponse) {
- option (google.api.http) = {
- post: "/views/{database}/{view_name}";
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
-
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "views";
- external_docs: {
- url: "https://docs.zitadel.com/concepts#Software_Architecture";
- description: "details of ZITADEL's event driven software concepts";
- };
- responses: {
- key: "200";
- value: {
- description: "View cleared";
- };
- };
- };
- }
-
- //Returns event descriptions which cannot be processed.
- // It's possible that some events need some retries.
- // For example if the SMTP-API wasn't able to send an email at the first time
- rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) {
- option (google.api.http) = {
- post: "/failedevents/_search";
- body: "*"
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
-
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "failed events";
- external_docs: {
- url: "https://docs.zitadel.com/concepts#Software_Architecture";
- description: "details of ZITADEL's event driven software concepts";
- };
- responses: {
- key: "200";
- value: {
- description: "Events which were not processed by the views";
- };
- };
- };
- }
-
- //Deletes the event from failed events view.
- // the event is not removed from the change stream
- // This call is usefull if the system was able to process the event later.
- // e.g. if the second try of sending an email was successful. the first try produced a
- // failed event. You can find out if it worked on the `failure_count`
- rpc RemoveFailedEvent(RemoveFailedEventRequest) returns (RemoveFailedEventResponse) {
- option (google.api.http) = {
- delete: "/failedevents/{database}/{view_name}/{failed_sequence}";
- };
-
- option (zitadel.v1.auth_option) = {
- permission: "authenticated";
- };
-
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "failed events";
- external_docs: {
- url: "https://docs.zitadel.com/concepts#Software_Architecture";
- description: "details of ZITADEL's event driven software concepts";
- };
- responses: {
- key: "200";
- value: {
- description: "Events removed from the list";
- };
- };
- responses: {
- key: "400";
- value: {
- description: "failed event not found";
- schema: {
- json_schema: {
- ref: "#/definitions/rpcStatus";
- };
- };
- };
- };
- };
- }
+ };
+ };
+ }
}
@@ -339,175 +338,175 @@ message HealthzRequest {}
message HealthzResponse {}
message ListInstancesRequest {
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
- json_schema: {
- description: "Search query for lists";
- required: ["query"]
- };
- };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+ json_schema: {
+ description: "Search query for lists";
+ required: ["query"]
+ };
+ };
- //list limitations and ordering
- zitadel.v1.ListQuery query = 1;
- // the field the result is sorted
- zitadel.instance.v1.FieldName sorting_column = 2;
- //criterias the client is looking for
- repeated zitadel.instance.v1.Query queries = 3;
+ //list limitations and ordering
+ zitadel.v1.ListQuery query = 1;
+ // the field the result is sorted
+ zitadel.instance.v1.FieldName sorting_column = 2;
+ //criterias the client is looking for
+ repeated zitadel.instance.v1.Query queries = 3;
}
message ListInstancesResponse {
- zitadel.v1.ListDetails details = 1;
- zitadel.instance.v1.FieldName sorting_column = 2;
- repeated zitadel.instance.v1.Instance result = 3;
+ zitadel.v1.ListDetails details = 1;
+ zitadel.instance.v1.FieldName sorting_column = 2;
+ repeated zitadel.instance.v1.Instance result = 3;
}
message GetInstanceRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetInstanceResponse {
- zitadel.instance.v1.InstanceDetail instance = 1;
+ zitadel.instance.v1.InstanceDetail instance = 1;
}
message AddInstanceRequest {
- message Profile {
- string first_name = 1 [(validate.rules).string = {max_len: 200}];
- string last_name = 2 [(validate.rules).string = {max_len: 200}];
- string preferred_language = 5 [(validate.rules).string = {max_len: 10}];
- }
- message Email {
- string email = 1[(validate.rules).string = {min_len: 1, max_len: 200}];
- bool is_email_verified = 2;
- }
- message Password {
- string password = 1 [(validate.rules).string = {max_len: 200}];
- bool password_change_required = 2;
- }
+ message Profile {
+ string first_name = 1 [(validate.rules).string = {max_len: 200}];
+ string last_name = 2 [(validate.rules).string = {max_len: 200}];
+ string preferred_language = 5 [(validate.rules).string = {max_len: 10}];
+ }
+ message Email {
+ string email = 1[(validate.rules).string = {min_len: 1, max_len: 200}];
+ bool is_email_verified = 2;
+ }
+ message Password {
+ string password = 1 [(validate.rules).string = {max_len: 200}];
+ bool password_change_required = 2;
+ }
- string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
- string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
- string owner_user_name = 4 [(validate.rules).string = {max_len: 200}];
- Email owner_email = 5 [(validate.rules).message.required = true];
- Profile owner_profile = 6 [(validate.rules).message.required = false];
- Password owner_password = 7 [(validate.rules).message.required = false];
- string default_language = 8 [(validate.rules).string = {max_len: 10}];
+ string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
+ string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
+ string owner_user_name = 4 [(validate.rules).string = {max_len: 200}];
+ Email owner_email = 5 [(validate.rules).message.required = true];
+ Profile owner_profile = 6 [(validate.rules).message.required = false];
+ Password owner_password = 7 [(validate.rules).message.required = false];
+ string default_language = 8 [(validate.rules).string = {max_len: 10}];
}
message AddInstanceResponse {
- string instance_id = 1;
- zitadel.v1.ObjectDetails details = 2;
+ string instance_id = 1;
+ zitadel.v1.ObjectDetails details = 2;
}
message RemoveInstanceRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message RemoveInstanceResponse {
- zitadel.v1.ObjectDetails details = 1;
+ zitadel.v1.ObjectDetails details = 1;
}
message GetUsageRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetUsageResponse {
- zitadel.v1.ObjectDetails details = 1;
- uint64 executed_requests = 2;
- uint64 executed_action_mins = 3;
+ zitadel.v1.ObjectDetails details = 1;
+ uint64 executed_requests = 2;
+ uint64 executed_action_mins = 3;
}
message ExistsDomainRequest {
- string domain = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message ExistsDomainResponse {
- bool exists = 1;
+ bool exists = 1;
}
message ListDomainsRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];//list limitations and ordering
- zitadel.v1.ListQuery query = 2;
- // the field the result is sorted
- zitadel.instance.v1.DomainFieldName sorting_column = 3;
- //criterias the client is looking for
- repeated zitadel.instance.v1.DomainSearchQuery queries = 4;
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];//list limitations and ordering
+ zitadel.v1.ListQuery query = 2;
+ // the field the result is sorted
+ zitadel.instance.v1.DomainFieldName sorting_column = 3;
+ //criterias the client is looking for
+ repeated zitadel.instance.v1.DomainSearchQuery queries = 4;
}
message ListDomainsResponse {
- zitadel.v1.ListDetails details = 1;
- zitadel.instance.v1.DomainFieldName sorting_column = 2;
- repeated zitadel.instance.v1.Domain result = 3;
+ zitadel.v1.ListDetails details = 1;
+ zitadel.instance.v1.DomainFieldName sorting_column = 2;
+ repeated zitadel.instance.v1.Domain result = 3;
}
message AddDomainRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message AddDomainResponse {
- zitadel.v1.ObjectDetails details = 1;
+ zitadel.v1.ObjectDetails details = 1;
}
message RemoveDomainRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message RemoveDomainResponse {
- zitadel.v1.ObjectDetails details = 1;
+ zitadel.v1.ObjectDetails details = 1;
}
message SetPrimaryDomainRequest {
- string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message SetPrimaryDomainResponse {
- zitadel.v1.ObjectDetails details = 1;
+ zitadel.v1.ObjectDetails details = 1;
}
message ChangeSubscriptionRequest {
- string domain = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string subscription_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
- uint64 request_limit = 3;
- uint64 action_mins_limit = 4;
+ string domain = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string subscription_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ uint64 request_limit = 3;
+ uint64 action_mins_limit = 4;
}
message ChangeSubscriptionResponse {
- zitadel.v1.ObjectDetails details = 1;
+ zitadel.v1.ObjectDetails details = 1;
}
//This is an empty request
message ListViewsRequest {}
message ListViewsResponse {
- //TODO: list details
- repeated View result = 1;
+ //TODO: list details
+ repeated View result = 1;
}
message ClearViewRequest {
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
- json_schema: {
- required: ["database", "view_name"]
- };
- };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+ json_schema: {
+ required: ["database", "view_name"]
+ };
+ };
- string database = 1 [
- (validate.rules).string = {min_len: 1, max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"adminapi\"";
- min_length: 1;
- max_length: 200;
- }
- ];
- string view_name = 2 [
- (validate.rules).string = {min_len: 1, max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"iam_members\"";
- min_length: 1;
- max_length: 200;
- }
- ];
+ string database = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"adminapi\"";
+ min_length: 1;
+ max_length: 200;
+ }
+ ];
+ string view_name = 2 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"iam_members\"";
+ min_length: 1;
+ max_length: 200;
+ }
+ ];
}
//This is an empty response
@@ -517,101 +516,101 @@ message ClearViewResponse {}
message ListFailedEventsRequest {}
message ListFailedEventsResponse {
- //TODO: list details
- repeated FailedEvent result = 1;
+ //TODO: list details
+ repeated FailedEvent result = 1;
}
message RemoveFailedEventRequest {
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
- json_schema: {
- required: ["database", "view_name", "failed_sequence"]
- };
- };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+ json_schema: {
+ required: ["database", "view_name", "failed_sequence"]
+ };
+ };
- string database = 1 [
- (validate.rules).string = {min_len: 1, max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"adminapi\"";
- min_length: 1;
- max_length: 200;
- }
- ];
- string view_name = 2 [
- (validate.rules).string = {min_len: 1, max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"iam_members\"";
- min_length: 1;
- max_length: 200;
- }
- ];
- uint64 failed_sequence = 3 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"9823758\"";
- }
- ];
+ string database = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"adminapi\"";
+ min_length: 1;
+ max_length: 200;
+ }
+ ];
+ string view_name = 2 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"iam_members\"";
+ min_length: 1;
+ max_length: 200;
+ }
+ ];
+ uint64 failed_sequence = 3 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"9823758\"";
+ }
+ ];
}
//This is an empty response
message RemoveFailedEventResponse {}
message View {
- string database = 1 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"adminapi\"";
- }
- ];
- string view_name = 2 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"iam_members\"";
- }
- ];
- uint64 processed_sequence = 3 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"9823758\"";
- }
- ];
- google.protobuf.Timestamp event_timestamp = 4 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"2019-04-01T08:45:00.000000Z\"";
- description: "The timestamp the event occured";
- }
- ]; // The timestamp the event occured
- google.protobuf.Timestamp last_successful_spooler_run = 5 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- description: "The timestamp the event occured";
- }
- ];
- string instance = 6 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"840498034930840\"";
- }
- ];
+ string database = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"adminapi\"";
+ }
+ ];
+ string view_name = 2 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"iam_members\"";
+ }
+ ];
+ uint64 processed_sequence = 3 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"9823758\"";
+ }
+ ];
+ google.protobuf.Timestamp event_timestamp = 4 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"2019-04-01T08:45:00.000000Z\"";
+ description: "The timestamp the event occured";
+ }
+ ]; // The timestamp the event occured
+ google.protobuf.Timestamp last_successful_spooler_run = 5 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "The timestamp the event occured";
+ }
+ ];
+ string instance = 6 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"840498034930840\"";
+ }
+ ];
}
message FailedEvent {
- string database = 1 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"adminapi\"";
- }
- ];
- string view_name = 2 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"iam_members\"";
- }
- ];
- uint64 failed_sequence = 3 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"9823759\"";
- }
- ];
- uint64 failure_count = 4 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"5\"";
- }
- ];
- string error_message = 5 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"ID=EXAMP-ID3ER Message=Example message\"";
- }
- ];
+ string database = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"adminapi\"";
+ }
+ ];
+ string view_name = 2 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"iam_members\"";
+ }
+ ];
+ uint64 failed_sequence = 3 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"9823759\"";
+ }
+ ];
+ uint64 failure_count = 4 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"5\"";
+ }
+ ];
+ string error_message = 5 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"ID=EXAMP-ID3ER Message=Example message\"";
+ }
+ ];
}
diff --git a/proto/zitadel/v1.proto b/proto/zitadel/v1.proto
new file mode 100644
index 0000000000..2887082679
--- /dev/null
+++ b/proto/zitadel/v1.proto
@@ -0,0 +1,161 @@
+syntax = "proto3";
+
+import "zitadel/user.proto";
+import "zitadel/idp.proto";
+import "zitadel/org.proto";
+import "zitadel/management.proto";
+
+import "protoc-gen-openapiv2/options/annotations.proto";
+
+import "validate/validate.proto";
+
+package zitadel.v1.v1;
+
+option go_package ="github.com/zitadel/zitadel/pkg/grpc/v1";
+
+message AddCustomOrgIAMPolicyRequest {
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+ json_schema: {
+ required: ["org_id"]
+ };
+ };
+
+ string org_id = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"#69629023906488334\"";
+ min_length: 1;
+ max_length: 200;
+ }
+ ];
+ bool user_login_must_be_domain = 2 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "the username has to end with the domain of it's organisation"
+ }
+ ]; // the username has to end with the domain of it's organisation (uniqueness is organisation based)
+}
+message ImportDataOrg {
+ repeated DataOrg orgs = 1;
+}
+
+message DataOrg {
+ string org_id = 1;
+ zitadel.management.v1.AddOrgRequest org = 3;
+ AddCustomOrgIAMPolicyRequest iam_policy = 4;
+ zitadel.management.v1.AddCustomLabelPolicyRequest label_policy = 5;
+ zitadel.management.v1.AddCustomLockoutPolicyRequest lockout_policy = 6;
+ zitadel.management.v1.AddCustomLoginPolicyRequest login_policy = 7;
+ zitadel.management.v1.AddCustomPasswordComplexityPolicyRequest password_complexity_policy = 8;
+ zitadel.management.v1.AddCustomPrivacyPolicyRequest privacy_policy = 9;
+
+ repeated DataProject projects = 10;
+ repeated zitadel.management.v1.AddProjectRoleRequest project_roles = 11;
+ repeated DataAPIApplication api_apps = 12;
+ repeated DataOIDCApplication oidc_apps = 13;
+ repeated DataHumanUser human_users = 14;
+ repeated DataMachineUser machine_users = 15;
+ repeated zitadel.management.v1.SetTriggerActionsRequest trigger_actions = 16;
+ repeated DataAction actions = 17;
+
+ repeated DataProjectGrant project_grants = 18;
+ repeated zitadel.management.v1.AddUserGrantRequest user_grants = 19;
+
+ repeated zitadel.management.v1.AddOrgMemberRequest org_members = 20;
+ repeated zitadel.management.v1.AddProjectMemberRequest project_members = 21;
+ repeated zitadel.management.v1.AddProjectGrantMemberRequest project_grant_members = 22;
+
+ repeated zitadel.management.v1.SetUserMetadataRequest user_metadata = 23;
+
+ repeated zitadel.management.v1.SetCustomLoginTextsRequest login_texts = 24;
+
+ repeated zitadel.management.v1.SetCustomInitMessageTextRequest init_messages = 25;
+ repeated zitadel.management.v1.SetCustomPasswordResetMessageTextRequest password_reset_messages = 26;
+ repeated zitadel.management.v1.SetCustomVerifyEmailMessageTextRequest verify_email_messages = 27;
+ repeated zitadel.management.v1.SetCustomVerifyPhoneMessageTextRequest verify_phone_messages = 28;
+ repeated zitadel.management.v1.SetCustomDomainClaimedMessageTextRequest domain_claimed_messages = 29;
+ repeated zitadel.management.v1.SetCustomPasswordlessRegistrationMessageTextRequest passwordless_registration_messages = 30;
+
+ repeated DataOIDCIDP oidc_idps = 31;
+ repeated DataJWTIDP jwt_idps = 32;
+
+ repeated zitadel.management.v1.AddSecondFactorToLoginPolicyRequest second_factors = 33;
+ repeated zitadel.management.v1.AddMultiFactorToLoginPolicyRequest multi_factors = 34;
+ repeated zitadel.management.v1.AddIDPToLoginPolicyRequest idps = 35;
+
+ repeated zitadel.idp.v1.IDPUserLink user_links = 36;
+ repeated zitadel.org.v1.Domain domains = 37;
+}
+message DataOIDCIDP{
+ string idp_id = 1;
+ zitadel.management.v1.AddOrgOIDCIDPRequest idp = 2;
+}
+message DataJWTIDP{
+ string idp_id = 1;
+ zitadel.management.v1.AddOrgJWTIDPRequest idp = 32;
+}
+
+message ExportHumanUser {
+ message Profile {
+ string first_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string last_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string nick_name = 3 [(validate.rules).string = {max_len: 200}];
+ string display_name = 4 [(validate.rules).string = {max_len: 200}];
+ string preferred_language = 5 [(validate.rules).string = {max_len: 10}];
+ zitadel.user.v1.Gender gender = 6;
+ }
+ message Email {
+ string email = 1 [(validate.rules).string.email = true]; //TODO: check if no value is allowed
+ bool is_email_verified = 2;
+ }
+ message Phone {
+ // has to be a global number
+ string phone = 1 [(validate.rules).string = {min_len: 1, max_len: 50, prefix: "+"}];
+ bool is_phone_verified = 2;
+ }
+ message HashedPassword{
+ string value = 1;
+ string algorithm = 2;
+ }
+
+ string user_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+
+ Profile profile = 2 [(validate.rules).message.required = true];
+ Email email = 3 [(validate.rules).message.required = true];
+ Phone phone = 4;
+ string password = 5;
+ HashedPassword hashed_password = 6;
+ bool password_change_required = 7;
+ bool request_passwordless_registration = 8;
+
+ string otp_code = 9;
+}
+
+
+message DataProject {
+ string project_id = 1;
+ zitadel.management.v1.AddProjectRequest project = 2;
+}
+message DataAPIApplication {
+ string app_id = 1;
+ zitadel.management.v1.AddAPIAppRequest app = 2;
+}
+message DataOIDCApplication {
+ string app_id = 1;
+ zitadel.management.v1.AddOIDCAppRequest app = 2;
+}
+message DataHumanUser {
+ string user_id = 1;
+ zitadel.management.v1.ImportHumanUserRequest user = 2;
+}
+message DataMachineUser {
+ string user_id = 1;
+ zitadel.management.v1.AddMachineUserRequest user = 2;
+}
+message DataAction {
+ string action_id = 1;
+ zitadel.management.v1.CreateActionRequest action = 2;
+}
+message DataProjectGrant {
+ string grant_id = 1;
+ zitadel.management.v1.AddProjectGrantRequest project_grant = 2;
+}
\ No newline at end of file