From 556f381a5a6ad2949e91a58503aa01a0187b2224 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 18 Oct 2022 16:07:30 +0100 Subject: [PATCH] fix(import): add import for app and machine keys (#4536) * fix(import): add import for app and machine keys * fix(export): add review changes * fix(import): Apply suggestions from code review Co-authored-by: Livio Spring * fix(import): add review changes Co-authored-by: Livio Spring --- docs/docs/apis/proto/admin.md | 4 + docs/docs/apis/proto/v1.md | 34 +++ internal/api/grpc/admin/export.go | 245 +++++++++++-------- internal/api/grpc/admin/import.go | 59 +++++ internal/command/project_application_api.go | 6 +- internal/command/project_application_key.go | 59 ++++- internal/command/project_application_oidc.go | 8 +- internal/command/project_converter.go | 3 +- internal/command/user_machine_key.go | 37 ++- internal/query/authn_key.go | 88 +++++++ internal/query/authn_key_test.go | 183 ++++++++++++++ internal/static/i18n/de.yaml | 6 +- internal/static/i18n/en.yaml | 4 + internal/static/i18n/fr.yaml | 4 + internal/static/i18n/it.yaml | 6 +- internal/static/i18n/zh.yaml | 4 + proto/zitadel/admin.proto | 5 + proto/zitadel/v1.proto | 24 ++ 18 files changed, 648 insertions(+), 131 deletions(-) diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 72c0ab5f8f..8b5c477c82 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -1889,6 +1889,8 @@ This is an empty request | jwt_idps | repeated zitadel.v1.v1.DataJWTIDP | - | | | user_links | repeated zitadel.idp.v1.IDPUserLink | - | | | domains | repeated zitadel.org.v1.Domain | - | | +| app_keys | repeated zitadel.v1.v1.DataAppKey | - | | +| machine_keys | repeated zitadel.v1.v1.DataMachineKey | - | | @@ -2909,6 +2911,8 @@ This is an empty response | user_links | repeated ImportDataSuccessUserLinks | - | | | user_metadata | repeated ImportDataSuccessUserMetadata | - | | | domains | repeated string | - | | +| app_keys | repeated string | - | | +| machine_keys | repeated string | - | | diff --git a/docs/docs/apis/proto/v1.md b/docs/docs/apis/proto/v1.md index b8f4638bef..e9172cca00 100644 --- a/docs/docs/apis/proto/v1.md +++ b/docs/docs/apis/proto/v1.md @@ -45,6 +45,23 @@ title: zitadel/v1.proto +### DataAppKey + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| id | string | - | | +| project_id | string | - | | +| app_id | string | - | | +| client_id | string | - | | +| type | zitadel.authn.v1.KeyType | - | | +| expiration_date | google.protobuf.Timestamp | - | | +| public_key | bytes | - | | + + + + ### DataHumanUser @@ -69,6 +86,21 @@ title: zitadel/v1.proto +### DataMachineKey + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| key_id | string | - | | +| user_id | string | - | | +| type | zitadel.authn.v1.KeyType | - | | +| expiration_date | google.protobuf.Timestamp | - | | +| public_key | bytes | - | | + + + + ### DataMachineUser @@ -147,6 +179,8 @@ title: zitadel/v1.proto | idps | repeated zitadel.management.v1.AddIDPToLoginPolicyRequest | - | | | user_links | repeated zitadel.idp.v1.IDPUserLink | - | | | domains | repeated zitadel.org.v1.Domain | - | | +| app_keys | repeated DataAppKey | - | | +| machine_keys | repeated DataMachineKey | - | | diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go index 164203cfea..63be01a611 100644 --- a/internal/api/grpc/admin/export.go +++ b/internal/api/grpc/admin/export.go @@ -4,7 +4,9 @@ import ( "context" "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + authn_grpc "github.com/zitadel/zitadel/internal/api/grpc/authn" text_grpc "github.com/zitadel/zitadel/internal/api/grpc/text" "github.com/zitadel/zitadel/internal/domain" caos_errors "github.com/zitadel/zitadel/internal/errors" @@ -156,7 +158,7 @@ func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest /****************************************************************************************************************** Users ******************************************************************************************************************/ - org.HumanUsers, org.MachineUsers, org.UserMetadata, err = s.getUsers(ctx, org.GetOrgId(), req.WithPasswords, req.WithOtp) + org.HumanUsers, org.MachineUsers, org.UserMetadata, org.MachineKeys, err = s.getUsers(ctx, org.GetOrgId(), req.WithPasswords, req.WithOtp) if err != nil { return nil, err } @@ -170,7 +172,7 @@ func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest /****************************************************************************************************************** Project and Applications ******************************************************************************************************************/ - org.Projects, org.ProjectRoles, org.OidcApps, org.ApiApps, err = s.getProjectsAndApps(ctx, org.GetOrgId()) + org.Projects, org.ProjectRoles, org.OidcApps, org.ApiApps, org.AppKeys, err = s.getProjectsAndApps(ctx, org.GetOrgId()) if err != nil { return nil, err } @@ -392,17 +394,15 @@ func (s *Server) getLoginPolicy(ctx context.Context, orgID string) (_ *managemen } idpLinksQuery, err := s.query.IDPLoginPolicyLinks(ctx, orgID, &query.IDPLoginPolicyLinksSearchQuery{}) - if err != nil && !caos_errors.IsNotFound(err) { + if err != nil { return nil, err } idpLinks := make([]*management_pb.AddCustomLoginPolicyRequest_IDP, 0) - if !caos_errors.IsNotFound(err) && idpLinksQuery != nil { - for _, idpLink := range idpLinksQuery.Links { - idpLinks = append(idpLinks, &management_pb.AddCustomLoginPolicyRequest_IDP{ - IdpId: idpLink.IDPID, - OwnerType: idp_pb.IDPOwnerType(idpLink.IDPType), - }) - } + for _, idpLink := range idpLinksQuery.Links { + idpLinks = append(idpLinks, &management_pb.AddCustomLoginPolicyRequest_IDP{ + IdpId: idpLink.IDPID, + OwnerType: idp_pb.IDPOwnerType(idpLink.IDPType), + }) } return &management_pb.AddCustomLoginPolicyRequest{ @@ -437,21 +437,19 @@ func (s *Server) getUserLinks(ctx context.Context, orgID string) (_ []*idp_pb.ID return nil, err } idpUserLinks, err := s.query.IDPUserLinks(ctx, &query.IDPUserLinksSearchQuery{Queries: []query.SearchQuery{userLinksResourceOwner}}) - if err != nil && !caos_errors.IsNotFound(err) { + if err != nil { return nil, err } userLinks := make([]*idp_pb.IDPUserLink, 0) - if !caos_errors.IsNotFound(err) && idpUserLinks != nil { - for _, idpUserLink := range idpUserLinks.Links { - userLinks = append(userLinks, &idp_pb.IDPUserLink{ - UserId: idpUserLink.UserID, - IdpId: idpUserLink.IDPID, - IdpName: idpUserLink.IDPName, - ProvidedUserId: idpUserLink.ProvidedUserID, - ProvidedUserName: idpUserLink.ProvidedUsername, - IdpType: idp_pb.IDPType(idpUserLink.IDPType), - }) - } + for _, idpUserLink := range idpUserLinks.Links { + userLinks = append(userLinks, &idp_pb.IDPUserLink{ + UserId: idpUserLink.UserID, + IdpId: idpUserLink.IDPID, + IdpName: idpUserLink.IDPName, + ProvidedUserId: idpUserLink.ProvidedUserID, + ProvidedUserName: idpUserLink.ProvidedUsername, + IdpType: idp_pb.IDPType(idpUserLink.IDPType), + }) } return userLinks, nil @@ -511,24 +509,22 @@ func (s *Server) getPrivacyPolicy(ctx context.Context, orgID string) (_ *managem return nil, nil } -func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, withOTP bool) (_ []*v1_pb.DataHumanUser, _ []*v1_pb.DataMachineUser, _ []*management_pb.SetUserMetadataRequest, err error) { +func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, withOTP bool) (_ []*v1_pb.DataHumanUser, _ []*v1_pb.DataMachineUser, _ []*management_pb.SetUserMetadataRequest, _ []*v1_pb.DataMachineKey, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() orgSearch, err := query.NewUserResourceOwnerSearchQuery(org, query.TextEquals) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{orgSearch}}) - if err != nil && !caos_errors.IsNotFound(err) { - return nil, nil, nil, err + if err != nil { + return nil, nil, nil, nil, err } humanUsers := make([]*v1_pb.DataHumanUser, 0) machineUsers := make([]*v1_pb.DataMachineUser, 0) userMetadata := make([]*management_pb.SetUserMetadataRequest, 0) - if err != nil && caos_errors.IsNotFound(err) { - return humanUsers, machineUsers, userMetadata, nil - } + machineKeys := make([]*v1_pb.DataMachineKey, 0) for _, user := range users.Users { switch user.Type { case domain.UserTypeHuman: @@ -563,7 +559,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w hashedPassword, hashAlgorithm, err := s.query.GetHumanPassword(ctx, org, user.ID) pwspan.EndWithError(err) if err != nil && !caos_errors.IsNotFound(err) { - return nil, nil, nil, err + return nil, nil, nil, nil, err } if err == nil && hashedPassword != nil { dataUser.User.HashedPassword = &management_pb.ImportHumanUserRequest_HashedPassword{ @@ -577,7 +573,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w code, err := s.query.GetHumanOTPSecret(ctx, user.ID, org) otpspan.EndWithError(err) if err != nil && !caos_errors.IsNotFound(err) { - return nil, nil, nil, err + return nil, nil, nil, nil, err } if err == nil && code != "" { dataUser.User.OtpCode = code @@ -594,17 +590,40 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w Description: user.Machine.Description, }, }) + userIDQuery, err := query.NewAuthNKeyAggregateIDQuery(user.ID) + if err != nil { + return nil, nil, nil, nil, err + } + orgIDQuery, err := query.NewAuthNKeyResourceOwnerQuery(org) + if err != nil { + return nil, nil, nil, nil, err + } + + keys, err := s.query.SearchAuthNKeysData(ctx, &query.AuthNKeySearchQueries{Queries: []query.SearchQuery{userIDQuery, orgIDQuery}}) + if err != nil { + return nil, nil, nil, nil, err + } + for _, key := range keys.AuthNKeysData { + machineKeys = append(machineKeys, &v1_pb.DataMachineKey{ + KeyId: key.ID, + UserId: user.ID, + Type: authn_grpc.KeyTypeToPb(key.Type), + ExpirationDate: timestamppb.New(key.Expiration), + PublicKey: key.PublicKey, + }) + + } } ctx, metaspan := tracing.NewSpan(ctx) metadataOrgSearch, err := query.NewUserMetadataResourceOwnerSearchQuery(org) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } metadataList, err := s.query.SearchUserMetadata(ctx, false, user.ID, &query.UserMetadataSearchQueries{Queries: []query.SearchQuery{metadataOrgSearch}}) metaspan.EndWithError(err) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } for _, metadata := range metadataList.Metadata { userMetadata = append(userMetadata, &management_pb.SetUserMetadataRequest{ @@ -614,7 +633,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w }) } } - return humanUsers, machineUsers, userMetadata, nil + return humanUsers, machineUsers, userMetadata, machineKeys, nil } func (s *Server) getTriggerActions(ctx context.Context, org string, processedActions []string) (_ []*management_pb.SetTriggerActionsRequest, err error) { @@ -655,13 +674,10 @@ func (s *Server) getActions(ctx context.Context, org string) ([]*v1_pb.DataActio return nil, err } queriedActions, err := s.query.SearchActions(ctx, &query.ActionSearchQueries{Queries: []query.SearchQuery{actionSearch}}) - if err != nil && !caos_errors.IsNotFound(err) { + if err != nil { return nil, err } actions := make([]*v1_pb.DataAction, len(queriedActions.Actions)) - if err != nil && caos_errors.IsNotFound(err) { - return actions, nil - } for i, action := range queriedActions.Actions { timeout := durationpb.New(action.Timeout()) @@ -679,23 +695,21 @@ func (s *Server) getActions(ctx context.Context, org string) ([]*v1_pb.DataActio return actions, nil } -func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.DataProject, []*management_pb.AddProjectRoleRequest, []*v1_pb.DataOIDCApplication, []*v1_pb.DataAPIApplication, error) { +func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.DataProject, []*management_pb.AddProjectRoleRequest, []*v1_pb.DataOIDCApplication, []*v1_pb.DataAPIApplication, []*v1_pb.DataAppKey, error) { projectSearch, err := query.NewProjectResourceOwnerSearchQuery(org) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } queriedProjects, err := s.query.SearchProjects(ctx, &query.ProjectSearchQueries{Queries: []query.SearchQuery{projectSearch}}) - if err != nil && !caos_errors.IsNotFound(err) { - return nil, nil, nil, nil, err + if err != nil { + return nil, nil, nil, nil, nil, err } projects := make([]*v1_pb.DataProject, len(queriedProjects.Projects)) orgProjectRoles := make([]*management_pb.AddProjectRoleRequest, 0) oidcApps := make([]*v1_pb.DataOIDCApplication, 0) apiApps := make([]*v1_pb.DataAPIApplication, 0) - if err != nil && caos_errors.IsNotFound(err) { - return projects, orgProjectRoles, oidcApps, apiApps, nil - } + appKeys := make([]*v1_pb.DataAppKey, 0) for i, queriedProject := range queriedProjects.Projects { projects[i] = &v1_pb.DataProject{ ProjectId: queriedProject.ID, @@ -710,81 +724,104 @@ func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.D projectRoleSearch, err := query.NewProjectRoleProjectIDSearchQuery(queriedProject.ID) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } queriedProjectRoles, err := s.query.SearchProjectRoles(ctx, false, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectRoleSearch}}) - if err != nil && !caos_errors.IsNotFound(err) { - return nil, nil, nil, nil, err + if err != nil { + return nil, nil, nil, nil, nil, err } - if queriedProjectRoles != nil { - for _, role := range queriedProjectRoles.ProjectRoles { - orgProjectRoles = append(orgProjectRoles, &management_pb.AddProjectRoleRequest{ - ProjectId: role.ProjectID, - RoleKey: role.Key, - DisplayName: role.DisplayName, - Group: role.Group, - }) - } + for _, role := range queriedProjectRoles.ProjectRoles { + orgProjectRoles = append(orgProjectRoles, &management_pb.AddProjectRoleRequest{ + ProjectId: role.ProjectID, + RoleKey: role.Key, + DisplayName: role.DisplayName, + Group: role.Group, + }) } appSearch, err := query.NewAppProjectIDSearchQuery(queriedProject.ID) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, err } apps, err := s.query.SearchApps(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{appSearch}}) - if err != nil && !caos_errors.IsNotFound(err) { - return nil, nil, nil, nil, err + if err != nil { + return nil, nil, nil, nil, nil, err } - if apps != nil { - for _, app := range apps.Apps { - if app.OIDCConfig != nil { - responseTypes := make([]app_pb.OIDCResponseType, 0) - for _, ty := range app.OIDCConfig.ResponseTypes { - responseTypes = append(responseTypes, app_pb.OIDCResponseType(ty)) - } - - grantTypes := make([]app_pb.OIDCGrantType, 0) - for _, ty := range app.OIDCConfig.GrantTypes { - grantTypes = append(grantTypes, app_pb.OIDCGrantType(ty)) - } - - oidcApps = append(oidcApps, &v1_pb.DataOIDCApplication{ - AppId: app.ID, - App: &management_pb.AddOIDCAppRequest{ - ProjectId: app.ProjectID, - Name: app.Name, - RedirectUris: app.OIDCConfig.RedirectURIs, - ResponseTypes: responseTypes, - GrantTypes: grantTypes, - AppType: app_pb.OIDCAppType(app.OIDCConfig.AppType), - AuthMethodType: app_pb.OIDCAuthMethodType(app.OIDCConfig.AuthMethodType), - PostLogoutRedirectUris: app.OIDCConfig.PostLogoutRedirectURIs, - Version: app_pb.OIDCVersion(app.OIDCConfig.Version), - DevMode: app.OIDCConfig.IsDevMode, - AccessTokenType: app_pb.OIDCTokenType(app.OIDCConfig.AccessTokenType), - AccessTokenRoleAssertion: app.OIDCConfig.AssertAccessTokenRole, - IdTokenRoleAssertion: app.OIDCConfig.AssertIDTokenRole, - IdTokenUserinfoAssertion: app.OIDCConfig.AssertIDTokenUserinfo, - ClockSkew: durationpb.New(app.OIDCConfig.ClockSkew), - AdditionalOrigins: app.OIDCConfig.AdditionalOrigins, - }, - }) + for _, app := range apps.Apps { + if app.OIDCConfig != nil { + responseTypes := make([]app_pb.OIDCResponseType, 0) + for _, ty := range app.OIDCConfig.ResponseTypes { + responseTypes = append(responseTypes, app_pb.OIDCResponseType(ty)) } - if app.APIConfig != nil { - apiApps = append(apiApps, &v1_pb.DataAPIApplication{ - AppId: app.ID, - App: &management_pb.AddAPIAppRequest{ - ProjectId: app.ProjectID, - Name: app.Name, - AuthMethodType: app_pb.APIAuthMethodType(app.APIConfig.AuthMethodType), - }, - }) + + grantTypes := make([]app_pb.OIDCGrantType, 0) + for _, ty := range app.OIDCConfig.GrantTypes { + grantTypes = append(grantTypes, app_pb.OIDCGrantType(ty)) } + + oidcApps = append(oidcApps, &v1_pb.DataOIDCApplication{ + AppId: app.ID, + App: &management_pb.AddOIDCAppRequest{ + ProjectId: app.ProjectID, + Name: app.Name, + RedirectUris: app.OIDCConfig.RedirectURIs, + ResponseTypes: responseTypes, + GrantTypes: grantTypes, + AppType: app_pb.OIDCAppType(app.OIDCConfig.AppType), + AuthMethodType: app_pb.OIDCAuthMethodType(app.OIDCConfig.AuthMethodType), + PostLogoutRedirectUris: app.OIDCConfig.PostLogoutRedirectURIs, + Version: app_pb.OIDCVersion(app.OIDCConfig.Version), + DevMode: app.OIDCConfig.IsDevMode, + AccessTokenType: app_pb.OIDCTokenType(app.OIDCConfig.AccessTokenType), + AccessTokenRoleAssertion: app.OIDCConfig.AssertAccessTokenRole, + IdTokenRoleAssertion: app.OIDCConfig.AssertIDTokenRole, + IdTokenUserinfoAssertion: app.OIDCConfig.AssertIDTokenUserinfo, + ClockSkew: durationpb.New(app.OIDCConfig.ClockSkew), + AdditionalOrigins: app.OIDCConfig.AdditionalOrigins, + }, + }) + } + if app.APIConfig != nil { + apiApps = append(apiApps, &v1_pb.DataAPIApplication{ + AppId: app.ID, + App: &management_pb.AddAPIAppRequest{ + ProjectId: app.ProjectID, + Name: app.Name, + AuthMethodType: app_pb.APIAuthMethodType(app.APIConfig.AuthMethodType), + }, + }) + } + appIDQuery, err := query.NewAuthNKeyObjectIDQuery(app.ID) + if err != nil { + return nil, nil, nil, nil, nil, err + } + projectIDQuery, err := query.NewAuthNKeyAggregateIDQuery(app.ProjectID) + if err != nil { + return nil, nil, nil, nil, nil, err + } + orgIDQuery, err := query.NewAuthNKeyResourceOwnerQuery(org) + if err != nil { + return nil, nil, nil, nil, nil, err + } + keys, err := s.query.SearchAuthNKeysData(ctx, &query.AuthNKeySearchQueries{Queries: []query.SearchQuery{appIDQuery, projectIDQuery, orgIDQuery}}) + if err != nil { + return nil, nil, nil, nil, nil, err + } + for _, key := range keys.AuthNKeysData { + appKeys = append(appKeys, &v1_pb.DataAppKey{ + Id: key.ID, + ProjectId: app.ProjectID, + AppId: app.ID, + Type: authn_grpc.KeyTypeToPb(key.Type), + ExpirationDate: timestamppb.New(key.Expiration), + ClientId: key.Identifier, + PublicKey: key.PublicKey, + }) } } } - return projects, orgProjectRoles, oidcApps, apiApps, nil + return projects, orgProjectRoles, oidcApps, apiApps, appKeys, nil } func (s *Server) getNecessaryProjectGrantMembersForOrg(ctx context.Context, org string, processedProjects []string, processedGrants []string, processedUsers []string) ([]*management_pb.AddProjectGrantMemberRequest, error) { diff --git a/internal/api/grpc/admin/import.go b/internal/api/grpc/admin/import.go index 4d878a144d..6c89181bd8 100644 --- a/internal/api/grpc/admin/import.go +++ b/internal/api/grpc/admin/import.go @@ -19,6 +19,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action" + "github.com/zitadel/zitadel/internal/api/grpc/authn" "github.com/zitadel/zitadel/internal/api/grpc/management" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -63,6 +64,8 @@ type count struct { orgMemberLen int projectGrantMemberCount int projectGrantMemberLen int + appKeysCount int + machineKeysCount int } func (c *count) getProgress() string { @@ -336,6 +339,8 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm count.projectMembersLen += len(org.GetProjectMembers()) count.orgMemberLen += len(org.GetOrgMembers()) count.projectGrantMemberLen += len(org.GetProjectGrantMembers()) + count.machineKeysCount += len(org.GetMachineKeys()) + count.appKeysCount += len(org.GetAppKeys()) } for _, org := range orgs { @@ -583,6 +588,31 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm successOrg.UserMetadata = append(successOrg.UserMetadata, &admin_pb.ImportDataSuccessUserMetadata{UserId: userMetadata.GetId(), Key: userMetadata.GetKey()}) } } + if org.MachineKeys != nil { + for _, key := range org.GetMachineKeys() { + logging.Debugf("import machine_user_key: %s", key.KeyId) + _, err := s.command.AddUserMachineKeyWithID(ctx, &domain.MachineKey{ + ObjectRoot: models.ObjectRoot{ + AggregateID: key.UserId, + ResourceOwner: org.GetOrgId(), + }, + KeyID: key.KeyId, + Type: authn.KeyTypeToDomain(key.Type), + ExpirationDate: key.ExpirationDate.AsTime(), + PublicKey: key.PublicKey, + }, org.GetOrgId()) + if err != nil { + errors = append(errors, &admin_pb.ImportDataError{Type: "machine_user_key", Id: key.KeyId, Message: err.Error()}) + if isCtxTimeout(ctx) { + return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err + } + continue + } + count.machineKeysCount += 1 + logging.Debugf("successful machine_user_key %d: %s", count.machineKeysCount, key.KeyId) + successOrg.MachineKeys = append(successOrg.MachineKeys, key.KeyId) + } + } if org.UserLinks != nil { for _, userLinks := range org.GetUserLinks() { logging.Debugf("import userlink: %s", userLinks.GetUserId()+"_"+userLinks.GetIdpId()+"_"+userLinks.GetProvidedUserId()+"_"+userLinks.GetProvidedUserName()) @@ -652,6 +682,33 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm successOrg.ApiAppIds = append(successOrg.ApiAppIds, app.GetAppId()) } } + if org.AppKeys != nil { + for _, key := range org.GetAppKeys() { + logging.Debugf("import app_key: %s", key.Id) + _, err := s.command.AddApplicationKeyWithID(ctx, &domain.ApplicationKey{ + ObjectRoot: models.ObjectRoot{ + AggregateID: key.ProjectId, + ResourceOwner: org.GetOrgId(), + }, + ApplicationID: key.AppId, + ClientID: key.ClientId, + KeyID: key.Id, + Type: authn.KeyTypeToDomain(key.Type), + ExpirationDate: key.ExpirationDate.AsTime(), + PublicKey: key.PublicKey, + }, org.GetOrgId()) + if err != nil { + errors = append(errors, &admin_pb.ImportDataError{Type: "app_key", Id: key.Id, Message: err.Error()}) + if isCtxTimeout(ctx) { + return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err + } + continue + } + count.appKeysCount += 1 + logging.Debugf("successful app_key %d: %s", count.appKeysCount, key.Id) + successOrg.AppKeys = append(successOrg.AppKeys, key.Id) + } + } if org.Actions != nil { for _, action := range org.GetActions() { logging.Debugf("import action: %s", action.GetActionId()) @@ -848,6 +905,8 @@ func (s *Server) dataOrgsV1ToDataOrgs(ctx context.Context, dataOrgs *v1_pb.Impor JwtIdps: orgV1.GetJwtIdps(), UserLinks: orgV1.GetUserLinks(), Domains: orgV1.GetDomains(), + AppKeys: orgV1.GetAppKeys(), + MachineKeys: orgV1.GetMachineKeys(), } if orgV1.IamPolicy != nil { defaultDomainPolicy, err := s.query.DefaultDomainPolicy(ctx) diff --git a/internal/command/project_application_api.go b/internal/command/project_application_api.go index 2046d3dc62..7354e5f99e 100644 --- a/internal/command/project_application_api.go +++ b/internal/command/project_application_api.go @@ -76,7 +76,7 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A return nil, err } if existingAPI.State != domain.AppStateUnspecified { - return nil, errors.ThrowPreconditionFailed(nil, "PROJECT-mabu12", "Errors.Application.AlreadyExisting") + return nil, errors.ThrowPreconditionFailed(nil, "PROJECT-mabu12", "Errors.Project.App.AlreadyExisting") } project, err := c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner) if err != nil { @@ -88,7 +88,7 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A 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") + return nil, errors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid") } project, err := c.getProjectByID(ctx, apiApp.AggregateID, resourceOwner) if err != nil { @@ -96,7 +96,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, } if !apiApp.IsValid() { - return nil, errors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid") + return nil, errors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Project.App.Invalid") } appID, err := c.idGenerator.Next() diff --git a/internal/command/project_application_key.go b/internal/command/project_application_key.go index c56baf779f..81fad14e00 100644 --- a/internal/command/project_application_key.go +++ b/internal/command/project_application_key.go @@ -6,8 +6,27 @@ import ( "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/repository/project" + "github.com/zitadel/zitadel/internal/telemetry/tracing" ) +func (c *Commands) AddApplicationKeyWithID(ctx context.Context, key *domain.ApplicationKey, resourceOwner string) (_ *domain.ApplicationKey, err error) { + writeModel, err := c.applicationKeyWriteModelByID(ctx, key.AggregateID, key.ApplicationID, key.KeyID, resourceOwner) + if err != nil { + return nil, err + } + if writeModel.State != domain.AppStateUnspecified { + return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-so20alo", "Errors.Project.App.Key.AlreadyExisting") + } + application, err := c.getApplicationWriteModel(ctx, key.AggregateID, key.ApplicationID, resourceOwner) + if err != nil { + return nil, err + } + if !application.State.Exists() { + return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-sak24", "Errors.Project.App.NotFound") + } + return c.addApplicationKey(ctx, key, resourceOwner) +} + func (c *Commands) AddApplicationKey(ctx context.Context, key *domain.ApplicationKey, resourceOwner string) (_ *domain.ApplicationKey, err error) { if key.AggregateID == "" || key.ApplicationID == "" { return nil, errors.ThrowInvalidArgument(nil, "COMMAND-55m9fs", "Errors.IDMissing") @@ -17,12 +36,18 @@ func (c *Commands) AddApplicationKey(ctx context.Context, key *domain.Applicatio return nil, err } if !application.State.Exists() { - return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-sak25", "Errors.Application.NotFound") + return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-sak25", "Errors.Project.App.NotFound") } key.KeyID, err = c.idGenerator.Next() if err != nil { return nil, err } + + return c.addApplicationKey(ctx, key, resourceOwner) +} + +func (c *Commands) addApplicationKey(ctx context.Context, key *domain.ApplicationKey, resourceOwner string) (_ *domain.ApplicationKey, err error) { + keyWriteModel := NewApplicationKeyWriteModel(key.AggregateID, key.ApplicationID, key.KeyID, resourceOwner) err = c.eventstore.FilterToQueryReducer(ctx, keyWriteModel) if err != nil { @@ -37,11 +62,13 @@ func (c *Commands) AddApplicationKey(ctx context.Context, key *domain.Applicatio return nil, err } - err = domain.SetNewAuthNKeyPair(key, c.applicationKeySize) - if err != nil { - return nil, err + if len(key.PublicKey) == 0 { + err = domain.SetNewAuthNKeyPair(key, c.applicationKeySize) + if err != nil { + return nil, err + } + key.ClientID = keyWriteModel.ClientID } - key.ClientID = keyWriteModel.ClientID pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationKeyAddedEvent( @@ -61,7 +88,10 @@ func (c *Commands) AddApplicationKey(ctx context.Context, key *domain.Applicatio if err != nil { return nil, err } - result := applicationKeyWriteModelToKey(keyWriteModel, key.PrivateKey) + result := applicationKeyWriteModelToKey(keyWriteModel) + if len(key.PrivateKey) > 0 { + result.PrivateKey = key.PrivateKey + } return result, nil } @@ -72,7 +102,7 @@ func (c *Commands) RemoveApplicationKey(ctx context.Context, projectID, applicat return nil, err } if !keyWriteModel.State.Exists() { - return nil, errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.Application.Key.NotFound") + return nil, errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.Project.App.Key.NotFound") } pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationKeyRemovedEvent(ctx, ProjectAggregateFromWriteModel(&keyWriteModel.WriteModel), keyID)) @@ -85,3 +115,18 @@ func (c *Commands) RemoveApplicationKey(ctx context.Context, projectID, applicat } return writeModelToObjectDetails(&keyWriteModel.WriteModel), nil } + +func (c *Commands) applicationKeyWriteModelByID(ctx context.Context, projectID, appID, keyID, resourceOwner string) (writeModel *ApplicationKeyWriteModel, err error) { + if appID == "" { + return nil, errors.ThrowInvalidArgument(nil, "COMMAND-029sn", "Errors.Project.App.NotFound") + } + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel = NewApplicationKeyWriteModel(projectID, appID, keyID, resourceOwner) + err = c.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go index 4deb39653c..b29b38cf50 100644 --- a/internal/command/project_application_oidc.go +++ b/internal/command/project_application_oidc.go @@ -40,7 +40,7 @@ type addOIDCApp struct { ClientSecretPlain string } -//AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands +// AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { return func() (preparation.CreateCommands, error) { if app.ID == "" { @@ -122,7 +122,7 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain return nil, err } if existingApp.State != domain.AppStateUnspecified { - return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Application.AlreadyExisting") + return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Project.App.AlreadyExisting") } project, err := c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner) @@ -135,7 +135,7 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain 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.Project.App.Invalid") } project, err := c.getProjectByID(ctx, oidcApp.AggregateID, resourceOwner) if err != nil { @@ -143,7 +143,7 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA } if oidcApp.AppName == "" || !oidcApp.IsValid() { - return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Application.Invalid") + return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Project.App.Invalid") } appID, err := c.idGenerator.Next() diff --git a/internal/command/project_converter.go b/internal/command/project_converter.go index b3dc652709..02a1b9eac7 100644 --- a/internal/command/project_converter.go +++ b/internal/command/project_converter.go @@ -98,7 +98,7 @@ func memberWriteModelToProjectGrantMember(writeModel *ProjectGrantMemberWriteMod } } -func applicationKeyWriteModelToKey(wm *ApplicationKeyWriteModel, privateKey []byte) *domain.ApplicationKey { +func applicationKeyWriteModelToKey(wm *ApplicationKeyWriteModel) *domain.ApplicationKey { return &domain.ApplicationKey{ ObjectRoot: writeModelToObjectRoot(wm.WriteModel), ApplicationID: wm.AppID, @@ -106,6 +106,5 @@ func applicationKeyWriteModelToKey(wm *ApplicationKeyWriteModel, privateKey []by KeyID: wm.KeyID, Type: wm.KeyType, ExpirationDate: wm.ExpirationDate, - PrivateKey: privateKey, } } diff --git a/internal/command/user_machine_key.go b/internal/command/user_machine_key.go index 5bb1b09e92..a10cdc0da1 100644 --- a/internal/command/user_machine_key.go +++ b/internal/command/user_machine_key.go @@ -9,34 +9,51 @@ import ( "github.com/zitadel/zitadel/internal/telemetry/tracing" ) -func (c *Commands) AddUserMachineKey(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) { - err := c.checkUserExists(ctx, machineKey.AggregateID, resourceOwner) +func (c *Commands) AddUserMachineKeyWithID(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) { + writeModel, err := c.machineKeyWriteModelByID(ctx, machineKey.AggregateID, machineKey.KeyID, resourceOwner) if err != nil { return nil, err } + if writeModel.State != domain.MachineKeyStateUnspecified { + return nil, errors.ThrowNotFound(nil, "COMMAND-p22101", "Errors.User.Machine.Key.AlreadyExisting") + } + return c.addUserMachineKey(ctx, machineKey, resourceOwner) +} + +func (c *Commands) AddUserMachineKey(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) { keyID, err := c.idGenerator.Next() if err != nil { return nil, err } - keyWriteModel := NewMachineKeyWriteModel(machineKey.AggregateID, keyID, resourceOwner) - err = c.eventstore.FilterToQueryReducer(ctx, keyWriteModel) + machineKey.KeyID = keyID + return c.addUserMachineKey(ctx, machineKey, resourceOwner) +} + +func (c *Commands) addUserMachineKey(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) { + err := c.checkUserExists(ctx, machineKey.AggregateID, resourceOwner) if err != nil { return nil, err } - - if err = domain.EnsureValidExpirationDate(machineKey); err != nil { + keyWriteModel := NewMachineKeyWriteModel(machineKey.AggregateID, machineKey.KeyID, resourceOwner) + if err := c.eventstore.FilterToQueryReducer(ctx, keyWriteModel); err != nil { return nil, err } - if err = domain.SetNewAuthNKeyPair(machineKey, c.machineKeySize); err != nil { + if err := domain.EnsureValidExpirationDate(machineKey); err != nil { return nil, err } + if len(machineKey.PublicKey) == 0 { + if err := domain.SetNewAuthNKeyPair(machineKey, c.machineKeySize); err != nil { + return nil, err + } + } + events, err := c.eventstore.Push(ctx, user.NewMachineKeyAddedEvent( ctx, UserAggregateFromWriteModel(&keyWriteModel.WriteModel), - keyID, + machineKey.KeyID, machineKey.Type, machineKey.ExpirationDate, machineKey.PublicKey)) @@ -49,7 +66,9 @@ func (c *Commands) AddUserMachineKey(ctx context.Context, machineKey *domain.Mac } key := keyWriteModelToMachineKey(keyWriteModel) - key.PrivateKey = machineKey.PrivateKey + if len(machineKey.PrivateKey) > 0 { + key.PrivateKey = machineKey.PrivateKey + } return key, nil } diff --git a/internal/query/authn_key.go b/internal/query/authn_key.go index e84e1a8e91..833163d230 100644 --- a/internal/query/authn_key.go +++ b/internal/query/authn_key.go @@ -84,6 +84,23 @@ type AuthNKey struct { Type domain.AuthNKeyType } +type AuthNKeysData struct { + SearchResponse + AuthNKeysData []*AuthNKeyData +} + +type AuthNKeyData struct { + ID string + CreationDate time.Time + ResourceOwner string + Sequence uint64 + + Expiration time.Time + Type domain.AuthNKeyType + Identifier string + PublicKey []byte +} + type AuthNKeySearchQueries struct { SearchRequest Queries []SearchQuery @@ -122,6 +139,30 @@ func (q *Queries) SearchAuthNKeys(ctx context.Context, queries *AuthNKeySearchQu return authNKeys, err } +func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySearchQueries) (authNKeys *AuthNKeysData, err error) { + query, scan := prepareAuthNKeysDataQuery() + query = queries.toQuery(query) + stmt, args, err := query.Where( + sq.Eq{ + AuthNKeyColumnEnabled.identifier(): true, + }, + ).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-SAg3f", "Errors.Query.InvalidRequest") + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Dbi53", "Errors.Internal") + } + authNKeys, err = scan(rows) + if err != nil { + return nil, err + } + authNKeys.LatestSequence, err = q.latestSequence(ctx, authNKeyTable) + return authNKeys, err +} + func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, id string, queries ...SearchQuery) (*AuthNKey, error) { if shouldTriggerBulk { projection.AuthNKeyProjection.Trigger(ctx) @@ -270,3 +311,50 @@ func prepareAuthNKeyPublicKeyQuery() (sq.SelectBuilder, func(row *sql.Row) ([]by return publicKey, nil } } + +func prepareAuthNKeysDataQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeysData, error)) { + return sq.Select( + AuthNKeyColumnID.identifier(), + AuthNKeyColumnCreationDate.identifier(), + AuthNKeyColumnResourceOwner.identifier(), + AuthNKeyColumnSequence.identifier(), + AuthNKeyColumnExpiration.identifier(), + AuthNKeyColumnType.identifier(), + AuthNKeyColumnIdentifier.identifier(), + AuthNKeyColumnPublicKey.identifier(), + countColumn.identifier(), + ).From(authNKeyTable.identifier()).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*AuthNKeysData, error) { + authNKeys := make([]*AuthNKeyData, 0) + var count uint64 + for rows.Next() { + authNKey := new(AuthNKeyData) + err := rows.Scan( + &authNKey.ID, + &authNKey.CreationDate, + &authNKey.ResourceOwner, + &authNKey.Sequence, + &authNKey.Expiration, + &authNKey.Type, + &authNKey.Identifier, + &authNKey.PublicKey, + &count, + ) + if err != nil { + return nil, err + } + authNKeys = append(authNKeys, authNKey) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Dgfn3", "Errors.Query.CloseRows") + } + + return &AuthNKeysData{ + AuthNKeysData: authNKeys, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} diff --git a/internal/query/authn_key_test.go b/internal/query/authn_key_test.go index 0898bf8ab7..958d0d051d 100644 --- a/internal/query/authn_key_test.go +++ b/internal/query/authn_key_test.go @@ -182,6 +182,189 @@ func Test_AuthNKeyPrepares(t *testing.T) { }, object: nil, }, + { + name: "prepareAuthNKeysDataQuery no result", + prepare: prepareAuthNKeysDataQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT projections.authn_keys.id,`+ + ` projections.authn_keys.creation_date,`+ + ` projections.authn_keys.resource_owner,`+ + ` projections.authn_keys.sequence,`+ + ` projections.authn_keys.expiration,`+ + ` projections.authn_keys.type,`+ + ` projections.authn_keys.identifier,`+ + ` projections.authn_keys.public_key,`+ + ` COUNT(*) OVER ()`+ + ` FROM projections.authn_keys`), + nil, + nil, + ), + }, + object: &AuthNKeysData{AuthNKeysData: []*AuthNKeyData{}}, + }, + { + name: "prepareAuthNKeysDataQuery one result", + prepare: prepareAuthNKeysDataQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT projections.authn_keys.id,`+ + ` projections.authn_keys.creation_date,`+ + ` projections.authn_keys.resource_owner,`+ + ` projections.authn_keys.sequence,`+ + ` projections.authn_keys.expiration,`+ + ` projections.authn_keys.type,`+ + ` projections.authn_keys.identifier,`+ + ` projections.authn_keys.public_key,`+ + ` COUNT(*) OVER ()`+ + ` FROM projections.authn_keys`), + []string{ + "id", + "creation_date", + "resource_owner", + "sequence", + "expiration", + "type", + "identifier", + "public_key", + "count", + }, + [][]driver.Value{ + { + "id", + testNow, + "ro", + uint64(20211109), + testNow, + 1, + "identifier", + []byte("public"), + }, + }, + ), + }, + object: &AuthNKeysData{ + SearchResponse: SearchResponse{ + Count: 1, + }, + AuthNKeysData: []*AuthNKeyData{ + { + ID: "id", + CreationDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + Expiration: testNow, + Type: domain.AuthNKeyTypeJSON, + Identifier: "identifier", + PublicKey: []byte("public"), + }, + }, + }, + }, + { + name: "prepareAuthNKeysDataQuery multiple result", + prepare: prepareAuthNKeysDataQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT projections.authn_keys.id,`+ + ` projections.authn_keys.creation_date,`+ + ` projections.authn_keys.resource_owner,`+ + ` projections.authn_keys.sequence,`+ + ` projections.authn_keys.expiration,`+ + ` projections.authn_keys.type,`+ + ` projections.authn_keys.identifier,`+ + ` projections.authn_keys.public_key,`+ + ` COUNT(*) OVER ()`+ + ` FROM projections.authn_keys`), + []string{ + "id", + "creation_date", + "resource_owner", + "sequence", + "expiration", + "type", + "identifier", + "public_key", + "count", + }, + [][]driver.Value{ + { + "id-1", + testNow, + "ro", + uint64(20211109), + testNow, + 1, + "identifier1", + []byte("public1"), + }, + { + "id-2", + testNow, + "ro", + uint64(20211109), + testNow, + 1, + "identifier2", + []byte("public2"), + }, + }, + ), + }, + object: &AuthNKeysData{ + SearchResponse: SearchResponse{ + Count: 2, + }, + AuthNKeysData: []*AuthNKeyData{ + { + ID: "id-1", + CreationDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + Expiration: testNow, + Type: domain.AuthNKeyTypeJSON, + Identifier: "identifier1", + PublicKey: []byte("public1"), + }, + { + ID: "id-2", + CreationDate: testNow, + ResourceOwner: "ro", + Sequence: 20211109, + Expiration: testNow, + Type: domain.AuthNKeyTypeJSON, + Identifier: "identifier2", + PublicKey: []byte("public2"), + }, + }, + }, + }, + { + name: "prepareAuthNKeysDataQuery sql err", + prepare: prepareAuthNKeysDataQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT projections.authn_keys.id,`+ + ` projections.authn_keys.creation_date,`+ + ` projections.authn_keys.resource_owner,`+ + ` projections.authn_keys.sequence,`+ + ` projections.authn_keys.expiration,`+ + ` projections.authn_keys.type,`+ + ` projections.authn_keys.identifier,`+ + ` projections.authn_keys.public_key,`+ + ` COUNT(*) OVER ()`+ + ` FROM projections.authn_keys`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, { name: "prepareAuthNKeyQuery no result", prepare: prepareAuthNKeyQuery, diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 6e5b00152d..c91ef5e39b 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -85,7 +85,8 @@ Errors: NotChanged: Adresse wurde nicht geändert Machine: Key: - NotFound: Maschinen Key nicht gefunden + NotFound: Maschinen Schlüssel nicht gefunden + AlreadyExisting: Machine Schlüssel exisiert bereits PAT: NotFound: Persönliches Access Token nicht gefunden NotHuman: Der Benutzer muss eine Person sein @@ -264,6 +265,9 @@ Errors: APIAuthMethodNoSecret: Gewählte API Auth Method benötigt kein Secret AuthMethodNoPrivateKeyJWT: Gewählte Auth Method benötigt keinen Key ClientSecretInvalid: Client Secret ist ungültig + Key: + AlreadyExisting: Applikationsschlüssel existiert bereits + NotFound: Applikationsschlüssel nicht gefunden RequiredFieldsMissing: Benötigte Felder fehlen Grant: AlreadyExists: Projekt Grant existiert bereits diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index efa53b4bdf..82b107a9f9 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -86,6 +86,7 @@ Errors: Machine: Key: NotFound: Machine key not found + AlreadyExisting: Machine key already existing PAT: NotFound: Personal Access Token not found NotHuman: The User must be personal @@ -264,6 +265,9 @@ Errors: APIAuthMethodNoSecret: Chosen API Auth Method does not require a secret AuthMethodNoPrivateKeyJWT: Chosen Auth Method does not require a key ClientSecretInvalid: Client Secret is invalid + Key: + AlreadyExisting: Application key already existing + NotFound: Application key not found RequiredFieldsMissing: Some required fields are missing Grant: AlreadyExists: Project grant already exists diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index e62a743a67..b76df02da8 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -86,6 +86,7 @@ Errors: Machine: Key: NotFound: Clé de la machine non trouvée + AlreadyExisting: Clé de la machine déjà existante PAT: NotFound: Token d'accès personnel non trouvé NotHuman: L'utilisateur doit être personnel @@ -264,6 +265,9 @@ Errors: APIAuthMethodNoSecret: La méthode d'authentification API choisie ne nécessite pas de secret. AuthMethodNoPrivateKeyJWT: La méthode d'authentification choisie ne nécessite pas de clé. ClientSecretInvalid: Le secret du client n'est pas valide + Key: + AlreadyExisting: Clé d'application déjà existante + NotFound: Clé d'application non trouvée RequiredFieldsMissing: Certains champs obligatoires sont manquants Grant: AlreadyExists: La subvention du projet existe déjà diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index 8989b473e3..6eafdbf4d4 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -85,7 +85,8 @@ Errors: NotChanged: Indirizzo non cambiato Machine: Key: - NotFound: Machine Key non trovato + NotFound: Chiave macchina non trovato + AlreadyExisting: Chiave macchina già esistente PAT: NotFound: Personal Access Token non trovato NotHuman: L'utente deve essere personale @@ -264,6 +265,9 @@ Errors: APIAuthMethodNoSecret: Il metodo di autorizzazione API scelto non richiede un segreto AuthMethodNoPrivateKeyJWT: Il metodo di autorizzazione scelto non richiede una chiave ClientSecretInvalid: Il segreto del cliente non è valido + Key: + AlreadyExisting: Chiave di applicazione già esistente + NotFound: Chiave di applicazione non trovata RequiredFieldsMissing: Mancano alcuni campi obbligatori Grant: AlreadyExists: Grant del progetto già esistente diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index ea8d2a7547..da31b3e85e 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -86,6 +86,7 @@ Errors: Machine: Key: NotFound: 未找到机器密钥 + AlreadyExisting: 已有的机器钥匙 PAT: NotFound: 未找到个人访问令牌 NotHuman: 用户必须是个人 @@ -259,6 +260,9 @@ Errors: APIAuthMethodNoSecret: 选择的 API 身份验证方法不需要秘钥 AuthMethodNoPrivateKeyJWT: 选择的身份验证方法不需要 Key ClientSecretInvalid: Client Secret 无效 + Key: + AlreadyExisting: 已经存在的应用钥匙 + NotFound: 未找到应用钥匙 RequiredFieldsMissing: 缺少一些必填字段 Grant: AlreadyExists: 项目授权已存在 diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 9585fea108..1a8d9da801 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -4763,6 +4763,9 @@ message DataOrg { repeated zitadel.idp.v1.IDPUserLink user_links = 33; repeated zitadel.org.v1.Domain domains = 34; + + repeated zitadel.v1.v1.DataAppKey app_keys = 35; + repeated zitadel.v1.v1.DataMachineKey machine_keys = 36; } message ImportDataResponse{ @@ -4801,6 +4804,8 @@ message ImportDataSuccessOrg{ repeated ImportDataSuccessUserLinks user_links = 18; repeated ImportDataSuccessUserMetadata user_metadata = 19; repeated string domains = 20; + repeated string app_keys = 21; + repeated string machine_keys = 22; } message ImportDataSuccessProjectGrant{ diff --git a/proto/zitadel/v1.proto b/proto/zitadel/v1.proto index 2887082679..bd91d6624f 100644 --- a/proto/zitadel/v1.proto +++ b/proto/zitadel/v1.proto @@ -4,9 +4,12 @@ import "zitadel/user.proto"; import "zitadel/idp.proto"; import "zitadel/org.proto"; import "zitadel/management.proto"; +import "zitadel/auth_n_key.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; +import "google/protobuf/timestamp.proto"; + import "validate/validate.proto"; package zitadel.v1.v1; @@ -84,7 +87,11 @@ message DataOrg { repeated zitadel.idp.v1.IDPUserLink user_links = 36; repeated zitadel.org.v1.Domain domains = 37; + + repeated DataAppKey app_keys = 38; + repeated DataMachineKey machine_keys = 39; } + message DataOIDCIDP{ string idp_id = 1; zitadel.management.v1.AddOrgOIDCIDPRequest idp = 2; @@ -130,6 +137,23 @@ message ExportHumanUser { string otp_code = 9; } +message DataAppKey { + string id = 1; + string project_id = 2; + string app_id = 3; + string client_id = 4; + zitadel.authn.v1.KeyType type = 5; + google.protobuf.Timestamp expiration_date = 6; + bytes public_key = 7; +} + +message DataMachineKey { + string key_id = 1; + string user_id = 2; + zitadel.authn.v1.KeyType type = 3; + google.protobuf.Timestamp expiration_date = 4; + bytes public_key = 5; +} message DataProject { string project_id = 1;