feat: V2 alpha import and export of organizations (#3798)

* feat(import): add functionality to import data into an instance

* feat(import): move import to admin api and additional checks for nil pointer

* fix(export): export implementation with filtered members and grants

* fix: export and import implementation

* fix: add possibility to export hashed passwords with the user

* fix(import): import with structure of v1 and v2

* docs: add v1 proto

* fix(import): check im imported user is already existing

* fix(import): add otp import function

* fix(import): add external idps, domains, custom text and messages

* fix(import): correct usage of default values from login policy

* fix(export): fix renaming of add project function

* fix(import): move checks for unit tests

* expect filter

* fix(import): move checks for unit tests

* fix(import): move checks for unit tests

* fix(import): produce prerelease from branch

* fix(import): correctly use provided user id for machine user imports

* fix(import): corrected otp import and added guide for export and import

* fix: import verified and primary domains

* fix(import): add reading from gcs, s3 and localfile with tracing

* fix(import): gcs and s3, file size correction and error logging

* Delete docker-compose.yml

* fix(import): progress logging and count of resources

* fix(import): progress logging and count of resources

* log subscription

* fix(import): incorporate review

* fix(import): incorporate review

* docs: add suggestion for import

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* fix(import): add verification otp event and handling of deleted but existing users

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabienne <fabienne.gerschwiler@gmail.com>
Co-authored-by: Silvan <silvan.reusser@gmail.com>
Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
This commit is contained in:
Stefan Benz 2022-07-28 15:42:35 +02:00 committed by GitHub
parent d620126aab
commit bc9a85daf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 4430 additions and 648 deletions

View File

@ -3,7 +3,7 @@ module.exports = {
{name: 'main'}, {name: 'main'},
{name: '1.x.x', range: '1.x.x', channel: '1.x.x'}, {name: '1.x.x', range: '1.x.x', channel: '1.x.x'},
{name: 'v2-alpha', prerelease: true}, {name: 'v2-alpha', prerelease: true},
{name: 'scheduler', prerelease: true}, {name: 'v2-alpha-import', prerelease: true},
], ],
plugins: [ plugins: [
"@semantic-release/commit-analyzer" "@semantic-release/commit-analyzer"

View File

@ -182,6 +182,9 @@ protoc \
-I=/proto/include \ -I=/proto/include \
--doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,settings.md \ --doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,settings.md \
${PROTO_PATH}/settings.proto ${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" echo "done generating grpc"

View File

@ -100,7 +100,7 @@ func startZitadel(config *Config, masterKey string) error {
return fmt.Errorf("cannot start eventstore for queries: %w", err) 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 { if err != nil {
return fmt.Errorf("cannot start queries: %w", err) 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 { if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.Database.Database, config.DefaultInstance)); err != nil {
return err 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 return err
} }
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil { if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil {

View File

@ -386,7 +386,7 @@ all queries need to match (AND)
> **rpc** SetUpOrg([SetUpOrgRequest](#setuporgrequest)) > **rpc** SetUpOrg([SetUpOrgRequest](#setuporgrequest))
[SetUpOrgResponse](#setuporgresponse) [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 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) [ListFailedEventsResponse](#listfailedeventsresponse)
Returns event descriptions which cannot be processed. 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 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} 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 ### 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 ### 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 ### IsOrgUniqueRequest
if name or domain is already in use, org is not unique if name or domain is already in use, org is not unique
at least one argument has to be provided at least one argument has to be provided

View File

@ -5176,8 +5176,10 @@ This is an empty response
| email | ImportHumanUserRequest.Email | - | message.required: true<br /> | | email | ImportHumanUserRequest.Email | - | message.required: true<br /> |
| phone | ImportHumanUserRequest.Phone | - | | | phone | ImportHumanUserRequest.Phone | - | |
| password | string | - | | | password | string | - | |
| hashed_password | ImportHumanUserRequest.HashedPassword | - | |
| password_change_required | bool | - | | | password_change_required | bool | - | |
| request_passwordless_registration | 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 ### ImportHumanUserRequest.Phone

View File

@ -150,8 +150,8 @@ they represent the delta of the event happend on the objects
[ClearViewResponse](#clearviewresponse) [ClearViewResponse](#clearviewresponse)
Truncates the delta of the change stream Truncates the delta of the change stream
be carefull with this function because ZITADEL has to be carefull with this function because ZITADEL has to
recompute the deltas after they got cleared. recompute the deltas after they got cleared.
Search requests will return wrong results until all deltas are recomputed 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) [ListFailedEventsResponse](#listfailedeventsresponse)
Returns event descriptions which cannot be processed. 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 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. Deletes the event from failed events view.
the event is not removed from the change stream 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 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` failed event. You can find out if it worked on the `failure_count`

261
docs/docs/apis/proto/v1.md Normal file
View File

@ -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<br /> string.max_len: 200<br /> |
| 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<br /> string.max_len: 200<br /> |
| profile | ExportHumanUser.Profile | - | message.required: true<br /> |
| email | ExportHumanUser.Email | - | message.required: true<br /> |
| 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<br /> |
| 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<br /> string.max_len: 50<br /> string.prefix: +<br /> |
| is_phone_verified | bool | - | |
### ExportHumanUser.Profile
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| first_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| last_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| nick_name | string | - | string.max_len: 200<br /> |
| display_name | string | - | string.max_len: 200<br /> |
| preferred_language | string | - | string.max_len: 10<br /> |
| gender | zitadel.user.v1.Gender | - | |
### ImportDataOrg
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| orgs | repeated DataOrg | - | |

View File

@ -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

View File

@ -100,7 +100,10 @@ module.exports = {
type: "category", type: "category",
label: "API", label: "API",
collapsed: false, collapsed: false,
items: ["guides/api/access-zitadel-apis"], items: [
"guides/api/access-zitadel-apis",
"guides/api/export-and-import"
],
}, },
{ {
type: "category", type: "category",

3
go.mod
View File

@ -3,6 +3,7 @@ module github.com/zitadel/zitadel
go 1.17 go 1.17
require ( require (
cloud.google.com/go/storage v1.14.0
github.com/BurntSushi/toml v0.4.1 github.com/BurntSushi/toml v0.4.1
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.0.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/mod v0.5.1 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // 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 google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

3
go.sum
View File

@ -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.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.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.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/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 h1:laKx2y7IWMjguCe5zZx6n7qLtREk4kyE69SXVC0VSN8=
cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= 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-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/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/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 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.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.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/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-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=

View File

@ -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) { 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 { if err != nil {
return nil, err 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 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{ return &domain.DomainPolicy{
UserLoginMustBeDomain: userLoginMustBeDomain, UserLoginMustBeDomain: userLoginMustBeDomain,
ValidateOrgDomains: validateOrgDomains, 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) { 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 { if err != nil {
return nil, err return nil, err
} }

File diff suppressed because it is too large Load Diff

View File

@ -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
}
}

View File

@ -2,7 +2,6 @@ package admin
import ( import (
"context" "context"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/admin/repository" "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/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server" "github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/admin" "github.com/zitadel/zitadel/pkg/grpc/admin"
@ -30,6 +30,7 @@ type Server struct {
administrator repository.AdministratorRepository administrator repository.AdministratorRepository
assetsAPIDomain func(context.Context) string assetsAPIDomain func(context.Context) string
userCodeAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm
passwordHashAlg crypto.HashAlgorithm
} }
type Config struct { type Config struct {
@ -40,6 +41,7 @@ func CreateServer(
database string, database string,
command *command.Commands, command *command.Commands,
query *query.Queries, query *query.Queries,
sd systemdefaults.SystemDefaults,
repo repository.Repository, repo repository.Repository,
externalSecure bool, externalSecure bool,
userCodeAlg crypto.EncryptionAlgorithm, userCodeAlg crypto.EncryptionAlgorithm,
@ -51,6 +53,7 @@ func CreateServer(
administrator: repo, administrator: repo,
assetsAPIDomain: assets.AssetAPI(externalSecure), assetsAPIDomain: assets.AssetAPI(externalSecure),
userCodeAlg: userCodeAlg, userCodeAlg: userCodeAlg,
passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost),
} }
} }

View File

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,7 +9,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" 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{ return &domain.Action{
Name: req.Name, Name: req.Name,
Script: req.Script, Script: req.Script,

View File

@ -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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -14,7 +14,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" 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{ return &domain.IDPConfig{
Name: req.Name, Name: req.Name,
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req), 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{ return &domain.IDPConfig{
Name: req.Name, Name: req.Name,
JWTConfig: addJWTIDPRequestToDomainJWTIDPConfig(req), JWTConfig: addJWTIDPRequestToDomainJWTIDPConfig(req),

View File

@ -35,7 +35,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got := addOIDCIDPRequestToDomain(tt.args.req) got := AddOIDCIDPRequestToDomain(tt.args.req)
test.AssertFieldsMapped(t, got, test.AssertFieldsMapped(t, got,
"ObjectRoot", "ObjectRoot",
"OIDCConfig.ClientSecret", "OIDCConfig.ClientSecret",

View File

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,7 +5,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" 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{ return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor, PrimaryColor: p.PrimaryColor,
BackgroundColor: p.BackgroundColor, BackgroundColor: p.BackgroundColor,

View File

@ -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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,7 +9,7 @@ import (
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" 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{ return &domain.LoginPolicy{
AllowUsernamePassword: p.AllowUsernamePassword, AllowUsernamePassword: p.AllowUsernamePassword,
AllowRegister: p.AllowRegister, AllowRegister: p.AllowRegister,

View File

@ -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) { 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) lang, err := language.Parse(req.Profile.PreferredLanguage)
logging.OnError(err).Debug("unable to parse language") 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, Verified: req.Phone.IsPhoneVerified,
} }
} }
details, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, human) return 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
} }
func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) { func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) {

View File

@ -142,11 +142,16 @@ func ImportHumanUserRequestToDomain(req *mgmt_pb.ImportHumanUserRequest) (human
IsPhoneVerified: req.Phone.IsPhoneVerified, IsPhoneVerified: req.Phone.IsPhoneVerified,
} }
} }
if req.Password != "" { if req.Password != "" {
human.Password = &domain.Password{SecretString: req.Password} human.Password = domain.NewPassword(req.Password)
human.Password.ChangeRequired = req.PasswordChangeRequired 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 return human, req.RequestPasswordlessRegistration
} }

View File

@ -21,17 +21,19 @@ type OrgSetup struct {
Roles []string Roles []string
} }
func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string) (string, *domain.ObjectDetails, error) { func (c *Commands) SetUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, *domain.ObjectDetails, error) {
orgID, err := c.idGenerator.Next() existingOrg, err := c.getOrgWriteModelByID(ctx, orgID)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
if existingOrg != nil {
userID, err := c.idGenerator.Next() return "", nil, errors.ThrowPreconditionFailed(nil, "COMMAND-poaj2", "Errors.Org.AlreadyExisting")
if err != nil {
return "", nil, err
} }
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) orgAgg := org.NewAggregate(orgID)
userAgg := user_repo.NewAggregate(userID, orgID) userAgg := user_repo.NewAggregate(userID, orgID)
@ -65,6 +67,20 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string)
}, nil }, 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, //AddOrgCommand defines the commands to create a new org,
// this includes the verified default domain // this includes the verified default domain
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string, userIDs ...string) preparation.Validation { 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 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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -136,6 +177,7 @@ func (c *Commands) ChangeOrg(ctx context.Context, orgID, name string) (*domain.O
if orgID == "" || name == "" { if orgID == "" || name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Mf9sd", "Errors.Org.Invalid")
} }
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID) orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -242,15 +284,12 @@ func ExistsOrg(ctx context.Context, filter preparation.FilterToQueryReducer, id
return exists, nil 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() { if !organisation.IsValid() {
return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid") return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid")
} }
organisation.AggregateID, err = c.idGenerator.Next() organisation.AggregateID = orgID
if err != nil {
return nil, nil, nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal")
}
organisation.AddIAMDomain(authz.GetInstance(ctx).RequestedDomain()) organisation.AddIAMDomain(authz.GetInstance(ctx).RequestedDomain())
addedOrg := NewOrgWriteModel(organisation.AggregateID) addedOrg := NewOrgWriteModel(organisation.AggregateID)

View File

@ -11,14 +11,33 @@ import (
"github.com/zitadel/zitadel/internal/repository/org" "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) { func (c *Commands) AddAction(ctx context.Context, addAction *domain.Action, resourceOwner string) (_ string, _ *domain.ObjectDetails, err error) {
if !addAction.IsValid() { if !addAction.IsValid() {
return "", nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-eg2gf", "Errors.Action.Invalid") 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 { if err != nil {
return "", nil, err 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) actionModel := NewActionWriteModel(addAction.AggregateID, resourceOwner)
actionAgg := ActionAggregateFromWriteModel(&actionModel.WriteModel) actionAgg := ActionAggregateFromWriteModel(&actionModel.WriteModel)

View File

@ -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) { return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" { if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument") 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) { return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" { if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument") 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 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) { func (c *Commands) AddOrgDomain(ctx context.Context, orgID, domain string, claimedUserIDs []string) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(orgID) orgAgg := org.NewAggregate(orgID)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgDomain(orgAgg, domain, claimedUserIDs)) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgDomain(orgAgg, domain, claimedUserIDs))

View File

@ -178,7 +178,7 @@ func TestVerifyDomain(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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)
}) })
} }
} }

View File

@ -11,6 +11,17 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "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) { func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, resourceOwner string) (*domain.IDPConfig, error) {
if resourceOwner == "" { if resourceOwner == "" {
return nil, errors.ThrowInvalidArgument(nil, "Org-0j8gs", "Errors.ResourceOwnerMissing") 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 { if config.OIDCConfig == nil && config.JWTConfig == nil {
return nil, errors.ThrowInvalidArgument(nil, "Org-eUpQU", "Errors.idp.config.notset") return nil, errors.ThrowInvalidArgument(nil, "Org-eUpQU", "Errors.idp.config.notset")
} }
idpConfigID, err := c.idGenerator.Next() idpConfigID, err := c.idGenerator.Next()
if err != nil { if err != nil {
return nil, err 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) addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, resourceOwner)
orgAgg := OrgAggregateFromWriteModel(&addedConfig.WriteModel) orgAgg := OrgAggregateFromWriteModel(&addedConfig.WriteModel)

View File

@ -13,11 +13,46 @@ import (
"github.com/zitadel/zitadel/internal/repository/project" "github.com/zitadel/zitadel/internal/repository/project"
) )
func (c *Commands) AddProject(ctx context.Context, project *domain.Project, resourceOwner, ownerUserID string) (_ *domain.Project, err error) { func (c *Commands) AddProjectWithID(ctx context.Context, project *domain.Project, resourceOwner, projectID string) (_ *domain.Project, err error) {
events, addedProject, err := c.addProject(ctx, project, resourceOwner, ownerUserID) existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil { if err != nil {
return nil, err 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...) pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -29,14 +64,11 @@ func (c *Commands) AddProject(ctx context.Context, project *domain.Project, reso
return projectWriteModelToProject(addedProject), nil 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() { if !projectAdd.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid") return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
projectAdd.AggregateID, err = c.idGenerator.Next()
if err != nil {
return nil, nil, err
} }
projectAdd.AggregateID = projectID
addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner) addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel)
@ -52,7 +84,16 @@ func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, r
projectAdd.PrivateLabelingSetting), projectAdd.PrivateLabelingSetting),
project.NewProjectMemberAddedEvent(ctx, projectAgg, ownerUserID, projectRole), 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( func AddProjectCommand(

View File

@ -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) { func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
if application == nil || application.AggregateID == "" { existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, appID, resourceOwner)
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)
if err != nil { if err != nil {
return nil, err 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...) pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -98,38 +147,6 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
return result, nil 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) { func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
if apiApp.AppID == "" || apiApp.AggregateID == "" { if apiApp.AppID == "" || apiApp.AggregateID == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid") return nil, errors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")

View File

@ -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) { func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
if application == nil || application.AggregateID == "" { 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") 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 { if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-3m9ss", "Errors.Project.NotFound") return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-3m9ss", "Errors.Project.NotFound")
} }
addedApplication := NewOIDCApplicationWriteModel(application.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel) if oidcApp.AppName == "" || !oidcApp.IsValid() {
events, stringPw, err := c.addOIDCApplication(ctx, projectAgg, project, application, resourceOwner, appSecretGenerator) return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Application.Invalid")
}
appID, err := c.idGenerator.Next()
if err != nil { if err != nil {
return nil, err return nil, err
} }
addedApplication.AppID = application.AppID
pushedEvents, err := c.eventstore.Push(ctx, events...) return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator)
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) 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) { func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, 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
}
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), project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
} }
var stringPw string var stringPw string
err = domain.SetNewClientID(oidcApp, c.idGenerator, proj) err = domain.SetNewClientID(oidcApp, c.idGenerator, project)
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator) stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator)
if err != nil { if err != nil {
return nil, "", err return nil, err
} }
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx, events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
projectAgg, projectAgg,
@ -187,7 +194,19 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
oidcApp.ClockSkew, oidcApp.ClockSkew,
oidcApp.AdditionalOrigins)) 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) { func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {

View File

@ -12,6 +12,18 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "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) { func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string) (_ *domain.ProjectGrant, err error) {
if !grant.IsValid() { if !grant.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-3b8fs", "Errors.Project.Grant.Invalid") 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 { if err != nil {
return nil, err return nil, err
} }
grant.GrantID, err = c.idGenerator.Next()
grantID, err := c.idGenerator.Next()
if err != nil { if err != nil {
return nil, err 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) addedGrant := NewProjectGrantWriteModel(grant.GrantID, grant.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedGrant.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&addedGrant.WriteModel)
pushedEvents, err := c.eventstore.Push( pushedEvents, err := c.eventstore.Push(

View File

@ -47,6 +47,8 @@ type AddHuman struct {
Phone Phone Phone Phone
//Password is optional //Password is optional
Password string Password string
//BcryptedPassword is optional
BcryptedPassword string
//PasswordChangeRequired is used if the `Password`-field is set //PasswordChangeRequired is used if the `Password`-field is set
PasswordChangeRequired bool PasswordChangeRequired bool
Passwordless bool Passwordless bool
@ -54,14 +56,19 @@ type AddHuman struct {
Register bool Register bool
} }
func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) { func (c *Commands) AddHumanWithID(ctx context.Context, resourceOwner string, userID string, human *AddHuman) (*domain.HumanDetails, error) {
if resourceOwner == "" { existingHuman, err := c.getHumanWriteModelByID(ctx, userID, resourceOwner)
return nil, errors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
}
userID, err := c.idGenerator.Next()
if err != nil { if err != nil {
return nil, err 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) agg := user.NewAggregate(userID, resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, AddHumanCommand(agg, human, c.userPasswordAlg, c.userEncryption)) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, AddHumanCommand(agg, human, c.userPasswordAlg, c.userEncryption))
if err != nil { if err != nil {
@ -83,6 +90,18 @@ func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *Ad
}, nil }, 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 { type humanCreationCommand interface {
eventstore.Command eventstore.Command
AddPhoneData(phoneNumber string) AddPhoneData(phoneNumber string)
@ -167,6 +186,10 @@ func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.Hash
createCmd.AddPasswordData(secret, human.PasswordChangeRequired) 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 := make([]eventstore.Command, 0, 3)
cmds = append(cmds, createCmd) cmds = append(cmds, createCmd)
@ -274,6 +297,18 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
if err != nil { if err != nil {
return nil, nil, errors.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound") 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) events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -355,8 +390,8 @@ func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Hum
if orgID == "" || !human.IsValid() { if orgID == "" || !human.IsValid() {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid") return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
} }
if human.Password != nil && human.SecretString != "" { if human.Password != nil && human.Password.SecretString != "" {
human.ChangeRequired = true human.Password.ChangeRequired = true
} }
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) 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 == "" { if human != nil && human.Username == "" {
human.Username = human.EmailAddress 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") return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
} }
if human.Password != nil && human.SecretString != "" { if human.Password != nil && human.Password.SecretString != "" {
human.ChangeRequired = false human.Password.ChangeRequired = false
} }
return c.createHuman(ctx, orgID, human, link, true, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator) 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") return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
} }
} }
userID, err := c.idGenerator.Next()
if err != nil { if human.AggregateID == "" {
return nil, nil, err userID, err := c.idGenerator.Next()
if err != nil {
return nil, nil, err
}
human.AggregateID = userID
} }
human.AggregateID = userID
human.SetNamesAsDisplayname() human.SetNamesAsDisplayname()
if human.Password != nil { 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 return nil, nil, err
} }
} }
@ -510,7 +549,10 @@ func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, h
human.StreetAddress) human.StreetAddress)
} }
if human.Password != nil { 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 return addEvent
} }
@ -541,7 +583,10 @@ func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggrega
human.StreetAddress) human.StreetAddress)
} }
if human.Password != nil { 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 return addEvent
} }

View File

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -11,6 +12,28 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "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) { func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string) (*domain.OTP, error) {
if userID == "" { if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing") 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") 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") return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
} }
otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner) otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner)
if err != nil { if err != nil {
return nil, err 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") return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-do9se", "Errors.User.MFA.OTP.AlreadyReady")
} }
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel) userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain) accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" { if accountName == "" {
accountName = human.EmailAddress accountName = human.EmailAddress
@ -46,8 +71,8 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -11,6 +11,23 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "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) { func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOwner string, links []*domain.UserIDPLink) (err error) {
if userID == "" { if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing") return caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing")

View File

@ -2,7 +2,6 @@ package command
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors" caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/repository/user" "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 { if err != nil {
return nil, err 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 machine.AggregateID = userID
addedMachine := NewMachineWriteModel(machine.AggregateID, orgID) addedMachine := NewMachineWriteModel(machine.AggregateID, orgID)
userAgg := UserAggregateFromWriteModel(&addedMachine.WriteModel) userAgg := UserAggregateFromWriteModel(&addedMachine.WriteModel)

View File

@ -124,3 +124,11 @@ func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error {
} }
return alg.CompareHash(value.Crypted, comparer) return alg.CompareHash(value.Crypted, comparer)
} }
func FillHash(value []byte, alg HashAlgorithm) *CryptoValue {
return &CryptoValue{
CryptoType: TypeHash,
Algorithm: alg.Algorithm(),
Crypted: value,
}
}

View File

@ -19,6 +19,7 @@ type Human struct {
Username string Username string
State UserState State UserState
*Password *Password
*HashedPassword
*Profile *Profile
*Email *Email
*Phone *Phone
@ -88,7 +89,7 @@ func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwor
} }
func (u *Human) IsInitialState(passwordless, externalIDPs bool) bool { 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) { func NewInitUserCode(generator crypto.Generator) (*InitUserCode, error) {

View File

@ -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),
},
}
}

View File

@ -168,7 +168,7 @@ func (h *ProjectionHandler) subscribe(ctx context.Context) {
index, err := h.Process(ctx, events...) index, err := h.Process(ctx, events...)
if err != nil || index < len(events)-1 { 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")
} }
} }
} }

View File

@ -502,3 +502,15 @@ func prepareIDPsQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPs, error)) {
}, nil }, 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")
}

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"net/http" "net/http"
"sync" "sync"
@ -27,6 +29,8 @@ type Queries struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
client *sql.DB client *sql.DB
idpConfigEncryption crypto.EncryptionAlgorithm
DefaultLanguage language.Tag DefaultLanguage language.Tag
LoginDir http.FileSystem LoginDir http.FileSystem
NotificationDir http.FileSystem NotificationDir http.FileSystem
@ -35,9 +39,10 @@ type Queries struct {
NotificationTranslationFileContents map[string][]byte NotificationTranslationFileContents map[string][]byte
supportedLangs []language.Tag supportedLangs []language.Tag
zitadelRoles []authz.RoleMapping 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") statikLoginFS, err := fs.NewWithNamespace("login")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to start login statik dir") 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) keypair.RegisterEventMappers(repo.eventstore)
usergrant.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) err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -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
}

View File

@ -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
}

View File

@ -10,6 +10,8 @@ import "zitadel/policy.proto";
import "zitadel/settings.proto"; import "zitadel/settings.proto";
import "zitadel/text.proto"; import "zitadel/text.proto";
import "zitadel/member.proto"; import "zitadel/member.proto";
import "zitadel/management.proto";
import "zitadel/v1.proto";
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/protobuf/timestamp.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) { rpc IsOrgUnique(IsOrgUniqueRequest) returns (IsOrgUniqueResponse) {
option (google.api.http) = { option (google.api.http) = {
get: "/orgs/_is_unique"; 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 // and adds the user to the orgs members as ORG_OWNER
rpc SetUpOrg(SetUpOrgRequest) returns (SetUpOrgResponse) { rpc SetUpOrg(SetUpOrgRequest) returns (SetUpOrgResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -1471,7 +1473,7 @@ service AdminService {
}; };
}; };
} }
//Updates the default login policy of ZITADEL //Updates the default login policy of ZITADEL
// it impacts all organisations without a customised policy // it impacts all organisations without a customised policy
rpc UpdateLoginPolicy(UpdateLoginPolicyRequest) returns (UpdateLoginPolicyResponse) { rpc UpdateLoginPolicy(UpdateLoginPolicyRequest) returns (UpdateLoginPolicyResponse) {
@ -1536,7 +1538,7 @@ service AdminService {
post: "/policies/login/idps"; post: "/policies/login/idps";
body: "*"; body: "*";
}; };
option (zitadel.v1.auth_option) = { option (zitadel.v1.auth_option) = {
permission: "iam.policy.write"; permission: "iam.policy.write";
}; };
@ -1887,7 +1889,7 @@ service AdminService {
}; };
}; };
} }
//Updates the default password age policy of ZITADEL //Updates the default password age policy of ZITADEL
// it impacts all organisations without a customised policy // it impacts all organisations without a customised policy
rpc UpdatePasswordAgePolicy(UpdatePasswordAgePolicyRequest) returns (UpdatePasswordAgePolicyResponse) { rpc UpdatePasswordAgePolicy(UpdatePasswordAgePolicyRequest) returns (UpdatePasswordAgePolicyResponse) {
@ -1945,7 +1947,7 @@ service AdminService {
}; };
}; };
} }
//Updates the default lockout policy of ZITADEL //Updates the default lockout policy of ZITADEL
// it impacts all organisations without a customised policy // it impacts all organisations without a customised policy
rpc UpdateLockoutPolicy(UpdateLockoutPolicyRequest) returns (UpdateLockoutPolicyResponse) { 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) { rpc ListIAMMemberRoles(ListIAMMemberRolesRequest) returns (ListIAMMemberRolesResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/members/roles/_search"; post: "/members/roles/_search";
@ -2564,7 +2566,7 @@ service AdminService {
} }
//Returns event descriptions which cannot be processed. //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 // For example if the SMTP-API wasn't able to send an email at the first time
rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) { rpc ListFailedEvents(ListFailedEventsRequest) returns (ListFailedEventsResponse) {
option (google.api.http) = { 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; bool is_phone_verified = 2;
} }
string user_name = 1 [ string user_name = 1 [
(validate.rules).string = {min_len: 1, max_len: 200}, (validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
@ -3079,7 +3104,7 @@ message SetUpOrgRequest {
example: "\"mr_long_neck\""; example: "\"mr_long_neck\"";
} }
]; ];
Profile profile = 2 [(validate.rules).message.required = true]; Profile profile = 2 [(validate.rules).message.required = true];
Email email = 3 [(validate.rules).message.required = true]; Email email = 3 [(validate.rules).message.required = true];
Phone phone = 4; Phone phone = 4;
@ -3905,7 +3930,7 @@ message RemoveIDPFromLoginPolicyRequest {
required: ["idp_id"] required: ["idp_id"]
}; };
}; };
string idp_id = 1 [ string idp_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200}, (validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
@ -3934,7 +3959,7 @@ message AddSecondFactorToLoginPolicyRequest {
required: ["type"] required: ["type"]
}; };
}; };
zitadel.policy.v1.SecondFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}]; zitadel.policy.v1.SecondFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}];
} }
@ -3984,7 +4009,7 @@ message RemoveMultiFactorFromLoginPolicyRequest {
required: ["type"] required: ["type"]
}; };
}; };
zitadel.policy.v1.MultiFactorType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}]; 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;
}

View File

@ -3007,6 +3007,10 @@ message ImportHumanUserRequest {
string phone = 1 [(validate.rules).string = {min_len: 1, max_len: 50, prefix: "+"}]; string phone = 1 [(validate.rules).string = {min_len: 1, max_len: 50, prefix: "+"}];
bool is_phone_verified = 2; 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}]; 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]; Email email = 3 [(validate.rules).message.required = true];
Phone phone = 4; Phone phone = 4;
string password = 5; string password = 5;
bool password_change_required = 6; HashedPassword hashed_password = 6;
bool request_passwordless_registration = 7; bool password_change_required = 7;
bool request_passwordless_registration = 8;
string otp_code = 9;
} }
message ImportHumanUserResponse { message ImportHumanUserResponse {

File diff suppressed because it is too large Load Diff

161
proto/zitadel/v1.proto Normal file
View File

@ -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;
}