mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 11:17:39 +00:00
feat: Instance create (#4502)
* feat(instance): implement create instance with direct machine user and credentials
* fix: deprecated add endpoint and variable declaration
* fix(instance): update logic for pats and machinekeys
* fix(instance): unit test corrections and additional unit test for pats and machinekeys
* fix(instance-create): include review changes
* fix(instance-create): linter fixes
* move iframe usage to solution scenarios configurations
* Revert "move iframe usage to solution scenarios configurations"
This reverts commit 9db31f3808
.
* fix merge
* fix: add review suggestions
Co-authored-by: Livio Spring <livio.a@gmail.com>
* fix: add review changes
* fix: add review changes for default definitions
* fix: add review changes for machinekey details
* fix: add machinekey output when setup with machineuser
* fix: add changes from review
* fix instance converter for machine and allow overwriting of further machine fields
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -343,6 +343,15 @@ DefaultInstance:
|
||||
Number:
|
||||
Verified:
|
||||
Password:
|
||||
Machine:
|
||||
Machine:
|
||||
Username:
|
||||
Name:
|
||||
MachineKey:
|
||||
ExpirationDate:
|
||||
Type:
|
||||
Pat:
|
||||
ExpirationDate:
|
||||
SecretGenerators:
|
||||
PasswordSaltCost: 14
|
||||
ClientSecret:
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
@@ -21,6 +22,7 @@ type FirstInstance struct {
|
||||
InstanceName string
|
||||
DefaultLanguage language.Tag
|
||||
Org command.OrgSetup
|
||||
MachineKeyPath string
|
||||
|
||||
instanceSetup command.InstanceSetup
|
||||
userEncryptionKey *crypto.KeyConfig
|
||||
@@ -97,7 +99,25 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = cmd.SetUpInstance(ctx, &mig.instanceSetup)
|
||||
_, _, key, _, err := cmd.SetUpInstance(ctx, &mig.instanceSetup)
|
||||
if key == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := os.Stdout
|
||||
if mig.MachineKeyPath != "" {
|
||||
f, err = os.OpenFile(mig.MachineKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
keyDetails, err := key.Detail()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(f, string(keyDetails))
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package setup
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
@@ -39,6 +40,7 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
hook.Base64ToBytesHookFunc(),
|
||||
hook.TagToLanguageHookFunc(),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToTimeHookFunc(time.RFC3339),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
database.DecodeHook,
|
||||
)),
|
||||
@@ -87,6 +89,7 @@ func MustNewSteps(v *viper.Viper) *Steps {
|
||||
hook.Base64ToBytesHookFunc(),
|
||||
hook.TagToLanguageHookFunc(),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToTimeHookFunc(time.RFC3339),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
)),
|
||||
)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
FirstInstance:
|
||||
MachineKeyPath:
|
||||
InstanceName: ZITADEL
|
||||
DefaultLanguage: en
|
||||
Org:
|
||||
@@ -22,3 +23,10 @@ FirstInstance:
|
||||
Verified:
|
||||
Password: Password1!
|
||||
PasswordChangeRequired: true
|
||||
Machine:
|
||||
Machine:
|
||||
Username:
|
||||
Name:
|
||||
MachineKey:
|
||||
ExpirationDate:
|
||||
Type:
|
||||
|
@@ -6,13 +6,13 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/zitadel/internal/api/saml"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
|
||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/api/saml"
|
||||
"github.com/zitadel/zitadel/internal/api/ui/console"
|
||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||
auth_es "github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
||||
@@ -70,6 +70,7 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
hook.Base64ToBytesHookFunc(),
|
||||
hook.TagToLanguageHookFunc(),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToTimeHookFunc(time.RFC3339),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
database.DecodeHook,
|
||||
actions.HTTPConfigDecodeHook,
|
||||
|
@@ -49,6 +49,7 @@ Returns the detail of an instance
|
||||
> **rpc** AddInstance([AddInstanceRequest](#addinstancerequest))
|
||||
[AddInstanceResponse](#addinstanceresponse)
|
||||
|
||||
Deprecated: Use CreateInstance instead
|
||||
Creates a new instance with all needed setup data
|
||||
This might take some time
|
||||
|
||||
@@ -69,6 +70,19 @@ Updates name of an existing instance
|
||||
PUT: /instances/{instance_id}
|
||||
|
||||
|
||||
### CreateInstance
|
||||
|
||||
> **rpc** CreateInstance([CreateInstanceRequest](#createinstancerequest))
|
||||
[CreateInstanceResponse](#createinstanceresponse)
|
||||
|
||||
Creates a new instance with all needed setup data
|
||||
This might take some time
|
||||
|
||||
|
||||
|
||||
POST: /instances/_create
|
||||
|
||||
|
||||
### RemoveInstance
|
||||
|
||||
> **rpc** RemoveInstance([RemoveInstanceRequest](#removeinstancerequest))
|
||||
@@ -342,6 +356,124 @@ This is an empty response
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| instance_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| first_org_name | string | - | string.max_len: 200<br /> |
|
||||
| custom_domain | string | - | string.max_len: 200<br /> |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) owner.human | CreateInstanceRequest.Human | oneof field for the user managing the instance | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) owner.machine | CreateInstanceRequest.Machine | - | |
|
||||
| default_language | string | - | string.max_len: 10<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.Email
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| email | string | - | string.min_len: 1<br /> string.max_len: 200<br /> string.email: true<br /> |
|
||||
| is_email_verified | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.Human
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| user_name | string | - | string.max_len: 200<br /> |
|
||||
| email | CreateInstanceRequest.Email | - | message.required: true<br /> |
|
||||
| profile | CreateInstanceRequest.Profile | - | message.required: false<br /> |
|
||||
| password | CreateInstanceRequest.Password | - | message.required: false<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.Machine
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| user_name | string | - | string.max_len: 200<br /> |
|
||||
| name | string | - | string.max_len: 200<br /> |
|
||||
| personal_access_token | CreateInstanceRequest.PersonalAccessToken | - | |
|
||||
| machine_key | CreateInstanceRequest.MachineKey | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.MachineKey
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| type | zitadel.authn.v1.KeyType | - | enum.defined_only: true<br /> enum.not_in: [0]<br /> |
|
||||
| expiration_date | google.protobuf.Timestamp | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.Password
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| password | string | - | string.max_len: 200<br /> |
|
||||
| password_change_required | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.PersonalAccessToken
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| expiration_date | google.protobuf.Timestamp | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.Profile
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| first_name | string | - | string.max_len: 200<br /> |
|
||||
| last_name | string | - | string.max_len: 200<br /> |
|
||||
| preferred_language | string | - | string.max_len: 10<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| instance_id | string | - | |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| pat | string | - | |
|
||||
| machine_key | bytes | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### ExistsDomainRequest
|
||||
|
||||
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
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/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
@@ -597,7 +598,7 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
|
||||
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{
|
||||
_, err := s.command.AddUserMachineKey(ctx, &command.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: key.UserId,
|
||||
ResourceOwner: org.GetOrgId(),
|
||||
@@ -606,7 +607,7 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
|
||||
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) {
|
||||
|
@@ -9,11 +9,11 @@ import (
|
||||
admin_grpc "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
func setUpOrgHumanToCommand(human *admin_grpc.SetUpOrgRequest_Human) command.AddHuman {
|
||||
func setUpOrgHumanToCommand(human *admin_grpc.SetUpOrgRequest_Human) *command.AddHuman {
|
||||
var lang language.Tag
|
||||
lang, err := language.Parse(human.Profile.PreferredLanguage)
|
||||
logging.OnError(err).Debug("unable to parse language")
|
||||
return command.AddHuman{
|
||||
return &command.AddHuman{
|
||||
Username: human.UserName,
|
||||
FirstName: human.Profile.FirstName,
|
||||
LastName: human.Profile.LastName,
|
||||
|
@@ -2,7 +2,6 @@ package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
@@ -731,27 +730,24 @@ func (s *Server) ListMachineKeys(ctx context.Context, req *mgmt_pb.ListMachineKe
|
||||
}
|
||||
|
||||
func (s *Server) AddMachineKey(ctx context.Context, req *mgmt_pb.AddMachineKeyRequest) (*mgmt_pb.AddMachineKeyResponse, error) {
|
||||
key, err := s.command.AddUserMachineKey(ctx, AddMachineKeyRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
machineKey := AddMachineKeyRequestToCommand(req, authz.GetCtxData(ctx).OrgID)
|
||||
details, err := s.command.AddUserMachineKey(ctx, machineKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyDetails, err := key.Detail()
|
||||
keyDetails, err := machineKey.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddMachineKeyResponse{
|
||||
KeyId: key.KeyID,
|
||||
KeyId: machineKey.KeyID,
|
||||
KeyDetails: keyDetails,
|
||||
Details: obj_grpc.AddToDetailsPb(
|
||||
key.Sequence,
|
||||
key.ChangeDate,
|
||||
key.ResourceOwner,
|
||||
),
|
||||
Details: obj_grpc.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachineKeyRequest) (*mgmt_pb.RemoveMachineKeyResponse, error) {
|
||||
objectDetails, err := s.command.RemoveUserMachineKey(ctx, req.UserId, req.KeyId, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.RemoveUserMachineKey(ctx, RemoveMachineKeyRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -794,28 +790,21 @@ func (s *Server) ListPersonalAccessTokens(ctx context.Context, req *mgmt_pb.List
|
||||
}
|
||||
|
||||
func (s *Server) AddPersonalAccessToken(ctx context.Context, req *mgmt_pb.AddPersonalAccessTokenRequest) (*mgmt_pb.AddPersonalAccessTokenResponse, error) {
|
||||
expDate := time.Time{}
|
||||
if req.ExpirationDate != nil {
|
||||
expDate = req.ExpirationDate.AsTime()
|
||||
}
|
||||
scopes := []string{oidc.ScopeOpenID, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}
|
||||
pat, token, err := s.command.AddPersonalAccessToken(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, expDate, scopes, domain.UserTypeMachine)
|
||||
pat := AddPersonalAccessTokenRequestToCommand(req, authz.GetCtxData(ctx).OrgID, scopes, domain.UserTypeMachine)
|
||||
details, err := s.command.AddPersonalAccessToken(ctx, pat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddPersonalAccessTokenResponse{
|
||||
TokenId: pat.TokenID,
|
||||
Token: token,
|
||||
Details: obj_grpc.AddToDetailsPb(
|
||||
pat.Sequence,
|
||||
pat.ChangeDate,
|
||||
pat.ResourceOwner,
|
||||
),
|
||||
Token: pat.Token,
|
||||
Details: obj_grpc.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePersonalAccessToken(ctx context.Context, req *mgmt_pb.RemovePersonalAccessTokenRequest) (*mgmt_pb.RemovePersonalAccessTokenResponse, error) {
|
||||
objectDetails, err := s.command.RemovePersonalAccessToken(ctx, req.UserId, req.TokenId, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.RemovePersonalAccessToken(ctx, RemovePersonalAccessTokenRequestToCommand(req, authz.GetCtxData(ctx).OrgID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -255,21 +255,59 @@ func ListMachineKeysRequestToQuery(ctx context.Context, req *mgmt_pb.ListMachine
|
||||
|
||||
}
|
||||
|
||||
func AddMachineKeyRequestToDomain(req *mgmt_pb.AddMachineKeyRequest) *domain.MachineKey {
|
||||
func AddMachineKeyRequestToCommand(req *mgmt_pb.AddMachineKeyRequest, resourceOwner string) *command.MachineKey {
|
||||
expDate := time.Time{}
|
||||
if req.ExpirationDate != nil {
|
||||
expDate = req.ExpirationDate.AsTime()
|
||||
}
|
||||
|
||||
return &domain.MachineKey{
|
||||
return &command.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.UserId,
|
||||
AggregateID: req.UserId,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
ExpirationDate: expDate,
|
||||
Type: authn.KeyTypeToDomain(req.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveMachineKeyRequestToCommand(req *mgmt_pb.RemoveMachineKeyRequest, resourceOwner string) *command.MachineKey {
|
||||
return &command.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.UserId,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
KeyID: req.KeyId,
|
||||
}
|
||||
}
|
||||
|
||||
func AddPersonalAccessTokenRequestToCommand(req *mgmt_pb.AddPersonalAccessTokenRequest, resourceOwner string, scopes []string, allowedUserType domain.UserType) *command.PersonalAccessToken {
|
||||
expDate := time.Time{}
|
||||
if req.ExpirationDate != nil {
|
||||
expDate = req.ExpirationDate.AsTime()
|
||||
}
|
||||
|
||||
return &command.PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.UserId,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
ExpirationDate: expDate,
|
||||
Scopes: scopes,
|
||||
AllowedUserType: allowedUserType,
|
||||
}
|
||||
}
|
||||
|
||||
func RemovePersonalAccessTokenRequestToCommand(req *mgmt_pb.RemovePersonalAccessTokenRequest, resourceOwner string) *command.PersonalAccessToken {
|
||||
return &command.PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.UserId,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
TokenID: req.TokenId,
|
||||
}
|
||||
}
|
||||
|
||||
func ListPersonalAccessTokensRequestToQuery(ctx context.Context, req *mgmt_pb.ListPersonalAccessTokensRequest) (*query.PersonalAccessTokenSearchQueries, error) {
|
||||
resourceOwner, err := query.NewPersonalAccessTokenResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
|
@@ -41,7 +41,7 @@ func (s *Server) GetInstance(ctx context.Context, req *system_pb.GetInstanceRequ
|
||||
}
|
||||
|
||||
func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequest) (*system_pb.AddInstanceResponse, error) {
|
||||
id, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
|
||||
id, _, _, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -62,6 +62,28 @@ func (s *Server) UpdateInstance(ctx context.Context, req *system_pb.UpdateInstan
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateInstance(ctx context.Context, req *system_pb.CreateInstanceRequest) (*system_pb.CreateInstanceResponse, error) {
|
||||
id, pat, key, details, err := s.command.SetUpInstance(ctx, CreateInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var machineKey []byte
|
||||
if key != nil {
|
||||
machineKey, err = key.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &system_pb.CreateInstanceResponse{
|
||||
Pat: pat,
|
||||
MachineKey: machineKey,
|
||||
InstanceId: id,
|
||||
Details: object.AddToDetailsPb(details.Sequence, details.EventDate, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveInstance(ctx context.Context, req *system_pb.RemoveInstanceRequest) (*system_pb.RemoveInstanceResponse, error) {
|
||||
ctx = authz.WithInstanceID(ctx, req.InstanceId)
|
||||
details, err := s.command.RemoveInstance(ctx, req.InstanceId)
|
||||
|
@@ -3,10 +3,13 @@ package system
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/authn"
|
||||
instance_grpc "github.com/zitadel/zitadel/internal/api/grpc/instance"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
z_oidc "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
@@ -14,6 +17,123 @@ import (
|
||||
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
)
|
||||
|
||||
func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defaultInstance command.InstanceSetup, externalDomain string) *command.InstanceSetup {
|
||||
if req.InstanceName != "" {
|
||||
defaultInstance.InstanceName = req.InstanceName
|
||||
defaultInstance.Org.Name = req.InstanceName
|
||||
}
|
||||
if req.CustomDomain != "" {
|
||||
defaultInstance.CustomDomain = req.CustomDomain
|
||||
}
|
||||
if req.FirstOrgName != "" {
|
||||
defaultInstance.Org.Name = req.FirstOrgName
|
||||
}
|
||||
|
||||
if user := req.GetMachine(); user != nil {
|
||||
defaultMachine := command.AddMachine{}
|
||||
if defaultInstance.Org.Machine != nil {
|
||||
defaultMachine = *defaultInstance.Org.Machine
|
||||
}
|
||||
|
||||
defaultInstance.Org.Machine = createInstancePbToAddMachine(user, defaultMachine)
|
||||
defaultInstance.Org.Human = nil
|
||||
}
|
||||
if user := req.GetHuman(); user != nil {
|
||||
defaultHuman := command.AddHuman{}
|
||||
if defaultInstance.Org.Human != nil {
|
||||
defaultHuman = *defaultInstance.Org.Human
|
||||
}
|
||||
|
||||
defaultInstance.Org.Human = createInstancePbToAddHuman(user, defaultHuman, defaultInstance.DomainPolicy.UserLoginMustBeDomain, defaultInstance.Org.Name, externalDomain)
|
||||
defaultInstance.Org.Machine = nil
|
||||
}
|
||||
|
||||
if lang := language.Make(req.DefaultLanguage); lang != language.Und {
|
||||
defaultInstance.DefaultLanguage = lang
|
||||
}
|
||||
|
||||
return &defaultInstance
|
||||
}
|
||||
|
||||
func createInstancePbToAddHuman(user *system_pb.CreateInstanceRequest_Human, defaultHuman command.AddHuman, userLoginMustBeDomain bool, org, externalDomain string) *command.AddHuman {
|
||||
if user.Email != nil {
|
||||
defaultHuman.Email.Address = user.Email.Email
|
||||
defaultHuman.Email.Verified = user.Email.IsEmailVerified
|
||||
}
|
||||
if user.Profile != nil {
|
||||
if user.Profile.FirstName != "" {
|
||||
defaultHuman.FirstName = user.Profile.FirstName
|
||||
}
|
||||
if user.Profile.LastName != "" {
|
||||
defaultHuman.LastName = user.Profile.LastName
|
||||
}
|
||||
if user.Profile.PreferredLanguage != "" {
|
||||
lang, err := language.Parse(user.Profile.PreferredLanguage)
|
||||
if err == nil {
|
||||
defaultHuman.PreferredLanguage = lang
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if default username is email style or else append @<orgname>.<custom-domain>
|
||||
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||
if !userLoginMustBeDomain && !strings.Contains(defaultHuman.Username, "@") {
|
||||
defaultHuman.Username = defaultHuman.Username + "@" + domain.NewIAMDomainName(org, externalDomain)
|
||||
}
|
||||
if user.UserName != "" {
|
||||
defaultHuman.Username = user.UserName
|
||||
}
|
||||
|
||||
if user.Password != nil {
|
||||
defaultHuman.Password = user.Password.Password
|
||||
defaultHuman.PasswordChangeRequired = user.Password.PasswordChangeRequired
|
||||
}
|
||||
return &defaultHuman
|
||||
}
|
||||
|
||||
func createInstancePbToAddMachine(user *system_pb.CreateInstanceRequest_Machine, defaultMachine command.AddMachine) *command.AddMachine {
|
||||
machine := command.Machine{}
|
||||
if defaultMachine.Machine != nil {
|
||||
machine = *defaultMachine.Machine
|
||||
}
|
||||
if user.UserName != "" {
|
||||
machine.Username = user.UserName
|
||||
}
|
||||
if user.Name != "" {
|
||||
machine.Name = user.Name
|
||||
}
|
||||
defaultMachine.Machine = &machine
|
||||
|
||||
if defaultMachine.Pat != nil || user.PersonalAccessToken != nil {
|
||||
pat := command.AddPat{}
|
||||
if defaultMachine.Pat != nil {
|
||||
pat = *defaultMachine.Pat
|
||||
}
|
||||
// scopes are currently static and can not be overwritten
|
||||
pat.Scopes = []string{oidc.ScopeOpenID, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}
|
||||
if user.PersonalAccessToken.ExpirationDate != nil {
|
||||
pat.ExpirationDate = user.PersonalAccessToken.ExpirationDate.AsTime()
|
||||
}
|
||||
defaultMachine.Pat = &pat
|
||||
}
|
||||
|
||||
if defaultMachine.MachineKey != nil || user.MachineKey != nil {
|
||||
machineKey := command.AddMachineKey{}
|
||||
if defaultMachine.MachineKey != nil {
|
||||
machineKey = *defaultMachine.MachineKey
|
||||
}
|
||||
if user.MachineKey != nil {
|
||||
if user.MachineKey.Type != 0 {
|
||||
machineKey.Type = authn.KeyTypeToDomain(user.MachineKey.Type)
|
||||
}
|
||||
if user.MachineKey.ExpirationDate != nil {
|
||||
machineKey.ExpirationDate = user.MachineKey.ExpirationDate.AsTime()
|
||||
}
|
||||
}
|
||||
defaultMachine.MachineKey = &machineKey
|
||||
}
|
||||
return &defaultMachine
|
||||
}
|
||||
|
||||
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup, externalDomain string) *command.InstanceSetup {
|
||||
if req.InstanceName != "" {
|
||||
defaultInstance.InstanceName = req.InstanceName
|
||||
|
@@ -144,7 +144,7 @@ func (d registerOrgFormData) toCommandOrg() *command.OrgSetup {
|
||||
}
|
||||
return &command.OrgSetup{
|
||||
Name: d.RegisterOrgName,
|
||||
Human: command.AddHuman{
|
||||
Human: &command.AddHuman{
|
||||
Username: d.Username,
|
||||
FirstName: d.Firstname,
|
||||
LastName: d.Lastname,
|
||||
|
@@ -151,30 +151,30 @@ func (s *InstanceSetup) generateIDs(idGenerator id.Generator) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (string, *domain.ObjectDetails, error) {
|
||||
func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (string, string, *MachineKey, *domain.ObjectDetails, error) {
|
||||
instanceID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
|
||||
if err = c.eventstore.NewInstance(ctx, instanceID); err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
|
||||
ctx = authz.SetCtxData(authz.WithRequestedDomain(authz.WithInstanceID(ctx, instanceID), c.externalDomain), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
|
||||
|
||||
orgID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
|
||||
userID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
|
||||
if err = setup.generateIDs(c.idGenerator); err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
ctx = authz.WithConsole(ctx, setup.zitadel.projectID, setup.zitadel.consoleAppID)
|
||||
|
||||
@@ -285,10 +285,40 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
validations = append(validations,
|
||||
AddOrgCommand(ctx, orgAgg, setup.Org.Name),
|
||||
c.prepareSetDefaultOrg(instanceAgg, orgAgg.ID),
|
||||
AddHumanCommand(userAgg, &setup.Org.Human, c.userPasswordAlg, c.userEncryption),
|
||||
)
|
||||
|
||||
var pat *PersonalAccessToken
|
||||
var machineKey *MachineKey
|
||||
// only a human or a machine user should be created as owner
|
||||
if setup.Org.Machine != nil && setup.Org.Machine.Machine != nil && !setup.Org.Machine.Machine.IsZero() {
|
||||
validations = append(validations,
|
||||
AddMachineCommand(userAgg, setup.Org.Machine.Machine),
|
||||
)
|
||||
if setup.Org.Machine.Pat != nil {
|
||||
pat = NewPersonalAccessToken(orgID, userID, setup.Org.Machine.Pat.ExpirationDate, setup.Org.Machine.Pat.Scopes, domain.UserTypeMachine)
|
||||
pat.TokenID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
validations = append(validations, prepareAddPersonalAccessToken(pat, c.keyAlgorithm))
|
||||
}
|
||||
if setup.Org.Machine.MachineKey != nil {
|
||||
machineKey = NewMachineKey(orgID, userID, setup.Org.Machine.MachineKey.ExpirationDate, setup.Org.Machine.MachineKey.Type)
|
||||
machineKey.KeyID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
validations = append(validations, prepareAddUserMachineKey(machineKey, c.machineKeySize))
|
||||
}
|
||||
} else if setup.Org.Human != nil {
|
||||
validations = append(validations,
|
||||
AddHumanCommand(userAgg, setup.Org.Human, c.userPasswordAlg, c.userEncryption),
|
||||
)
|
||||
}
|
||||
|
||||
validations = append(validations,
|
||||
c.AddOrgMemberCommand(orgAgg, userID, domain.RoleOrgOwner),
|
||||
c.AddInstanceMemberCommand(instanceAgg, userID, domain.RoleIAMOwner),
|
||||
|
||||
AddProjectCommand(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
|
||||
SetIAMProject(instanceAgg, projectAgg.ID),
|
||||
|
||||
@@ -334,7 +364,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
|
||||
addGeneratedDomain, err := c.addGeneratedInstanceDomain(ctx, instanceAgg, setup.InstanceName)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
validations = append(validations, addGeneratedDomain...)
|
||||
if setup.CustomDomain != "" {
|
||||
@@ -372,14 +402,20 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
return instanceID, &domain.ObjectDetails{
|
||||
|
||||
var token string
|
||||
if pat != nil {
|
||||
token = pat.Token
|
||||
}
|
||||
|
||||
return instanceID, token, machineKey, &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
|
@@ -17,7 +17,8 @@ import (
|
||||
type OrgSetup struct {
|
||||
Name string
|
||||
CustomDomain string
|
||||
Human AddHuman
|
||||
Human *AddHuman
|
||||
Machine *AddMachine
|
||||
Roles []string
|
||||
}
|
||||
|
||||
@@ -30,10 +31,11 @@ func (c *Commands) SetUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, user
|
||||
return "", nil, errors.ThrowPreconditionFailed(nil, "COMMAND-poaj2", "Errors.Org.AlreadyExisting")
|
||||
}
|
||||
|
||||
return c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
|
||||
userID, _, _, details, err := c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
|
||||
return userID, details, err
|
||||
}
|
||||
|
||||
func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, *domain.ObjectDetails, error) {
|
||||
func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, string, *MachineKey, *domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(orgID)
|
||||
userAgg := user_repo.NewAggregate(userID, orgID)
|
||||
|
||||
@@ -44,23 +46,55 @@ func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, user
|
||||
|
||||
validations := []preparation.Validation{
|
||||
AddOrgCommand(ctx, orgAgg, o.Name, userIDs...),
|
||||
AddHumanCommand(userAgg, &o.Human, c.userPasswordAlg, c.userEncryption),
|
||||
c.AddOrgMemberCommand(orgAgg, userID, roles...),
|
||||
}
|
||||
|
||||
var pat *PersonalAccessToken
|
||||
var machineKey *MachineKey
|
||||
if o.Human != nil {
|
||||
validations = append(validations, AddHumanCommand(userAgg, o.Human, c.userPasswordAlg, c.userEncryption))
|
||||
} else if o.Machine != nil {
|
||||
validations = append(validations, AddMachineCommand(userAgg, o.Machine.Machine))
|
||||
if o.Machine.Pat != nil {
|
||||
pat = NewPersonalAccessToken(orgID, userID, o.Machine.Pat.ExpirationDate, o.Machine.Pat.Scopes, domain.UserTypeMachine)
|
||||
tokenID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
pat.TokenID = tokenID
|
||||
validations = append(validations, prepareAddPersonalAccessToken(pat, c.keyAlgorithm))
|
||||
}
|
||||
if o.Machine.MachineKey != nil {
|
||||
machineKey = NewMachineKey(orgID, userID, o.Machine.MachineKey.ExpirationDate, o.Machine.MachineKey.Type)
|
||||
keyID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
machineKey.KeyID = keyID
|
||||
validations = append(validations, prepareAddUserMachineKey(machineKey, c.keySize))
|
||||
}
|
||||
}
|
||||
validations = append(validations, c.AddOrgMemberCommand(orgAgg, userID, roles...))
|
||||
|
||||
if o.CustomDomain != "" {
|
||||
validations = append(validations, c.prepareAddOrgDomain(orgAgg, o.CustomDomain, userIDs))
|
||||
}
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
return userID, &domain.ObjectDetails{
|
||||
|
||||
var token string
|
||||
if pat != nil {
|
||||
token = pat.Token
|
||||
}
|
||||
|
||||
return userID, token, machineKey, &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
@@ -78,7 +112,8 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string)
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
|
||||
userID, _, _, details, err := c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
|
||||
return userID, details, err
|
||||
}
|
||||
|
||||
// AddOrgCommand defines the commands to create a new org,
|
||||
|
@@ -449,9 +449,6 @@ func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryRed
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
user.AppendEvents(events...)
|
||||
err = user.Reduce()
|
||||
return user, err
|
||||
|
@@ -11,6 +11,12 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type AddMachine struct {
|
||||
Machine *Machine
|
||||
Pat *AddPat
|
||||
MachineKey *AddMachineKey
|
||||
}
|
||||
|
||||
type Machine struct {
|
||||
models.ObjectRoot
|
||||
|
||||
@@ -19,14 +25,41 @@ type Machine struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
func (m *Machine) content() error {
|
||||
if m.ResourceOwner == "" {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-xiown2", "Errors.ResourceOwnerMissing")
|
||||
func (m *Machine) IsZero() bool {
|
||||
return m.Username == "" && m.Name == ""
|
||||
}
|
||||
|
||||
func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-xiown2", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-p0p2mi", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if machine.Name == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bs9Ds", "Errors.User.Invalid")
|
||||
}
|
||||
if machine.Username == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-k2una", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
domainPolicy, err := domainPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineAddedEvent(ctx, &a.Aggregate, machine.Username, machine.Name, machine.Description, domainPolicy.UserLoginMustBeDomain),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
if m.AggregateID == "" {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-p0p2mi", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddMachine(ctx context.Context, machine *Machine) (*domain.ObjectDetails, error) {
|
||||
@@ -37,20 +70,18 @@ func (c *Commands) AddMachine(ctx context.Context, machine *Machine) (*domain.Ob
|
||||
}
|
||||
machine.AggregateID = userID
|
||||
}
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, machine.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
|
||||
validation := prepareAddUserMachine(machine, domainPolicy)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
agg := user.NewAggregate(machine.AggregateID, machine.ResourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, AddMachineCommand(agg, machine))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
@@ -58,49 +89,18 @@ func (c *Commands) AddMachine(ctx context.Context, machine *Machine) (*domain.Ob
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareAddUserMachine(machine *Machine, domainPolicy *domain.DomainPolicy) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if err := machine.content(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if machine.Name == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bs9Ds", "Errors.User.Invalid")
|
||||
}
|
||||
if machine.Username == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModelByID(ctx, filter, machine.AggregateID, machine.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-k2una", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
machine.Username,
|
||||
machine.Name,
|
||||
machine.Description,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeMachine(ctx context.Context, machine *Machine) (*domain.ObjectDetails, error) {
|
||||
validation := prepareChangeUserMachine(machine)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
agg := user.NewAggregate(machine.AggregateID, machine.ResourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, changeMachineCommand(agg, machine))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
@@ -108,21 +108,23 @@ func (c *Commands) ChangeMachine(ctx context.Context, machine *Machine) (*domain
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareChangeUserMachine(machine *Machine) preparation.Validation {
|
||||
func changeMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if err := machine.content(); err != nil {
|
||||
return nil, err
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-xiown3", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-p0p3mi", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModelByID(ctx, filter, machine.AggregateID, machine.ResourceOwner)
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
event, hasChanged, err := writeModel.NewChangedEvent(ctx, UserAggregateFromWriteModel(&writeModel.WriteModel), machine.Name, machine.Description)
|
||||
changedEvent, hasChanged, err := writeModel.NewChangedEvent(ctx, &a.Aggregate, machine.Name, machine.Description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,18 +133,21 @@ func prepareChangeUserMachine(machine *Machine) preparation.Validation {
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
event,
|
||||
changedEvent,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getMachineWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (_ *MachineWriteModel, err error) {
|
||||
func getMachineWriteModel(ctx context.Context, userID, resourceOwner string, filter preparation.FilterToQueryReducer) (*MachineWriteModel, error) {
|
||||
writeModel := NewMachineWriteModel(userID, resourceOwner)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
return writeModel, err
|
||||
|
@@ -2,108 +2,204 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
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)
|
||||
type AddMachineKey struct {
|
||||
Type domain.AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
machineKey.KeyID = keyID
|
||||
return c.addUserMachineKey(ctx, machineKey, resourceOwner)
|
||||
type MachineKey struct {
|
||||
models.ObjectRoot
|
||||
|
||||
KeyID string
|
||||
Type domain.AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
keyWriteModel := NewMachineKeyWriteModel(machineKey.AggregateID, machineKey.KeyID, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, keyWriteModel); err != nil {
|
||||
return nil, err
|
||||
func NewMachineKey(resourceOwner string, userID string, expirationDate time.Time, keyType domain.AuthNKeyType) *MachineKey {
|
||||
return &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
ExpirationDate: expirationDate,
|
||||
Type: keyType,
|
||||
}
|
||||
}
|
||||
|
||||
if err := domain.EnsureValidExpirationDate(machineKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (key *MachineKey) SetPublicKey(publicKey []byte) {
|
||||
key.PublicKey = publicKey
|
||||
}
|
||||
func (key *MachineKey) SetPrivateKey(privateKey []byte) {
|
||||
key.PrivateKey = privateKey
|
||||
}
|
||||
func (key *MachineKey) GetExpirationDate() time.Time {
|
||||
return key.ExpirationDate
|
||||
}
|
||||
func (key *MachineKey) SetExpirationDate(t time.Time) {
|
||||
key.ExpirationDate = t
|
||||
}
|
||||
|
||||
if len(machineKey.PublicKey) == 0 {
|
||||
if err := domain.SetNewAuthNKeyPair(machineKey, c.machineKeySize); err != nil {
|
||||
func (key *MachineKey) Detail() ([]byte, error) {
|
||||
if len(key.PrivateKey) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "KEY-sp2l2m", "Errors.Internal")
|
||||
}
|
||||
if key.Type == domain.AuthNKeyTypeJSON {
|
||||
return domain.MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.AggregateID)
|
||||
}
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (key *MachineKey) content() error {
|
||||
if key.ResourceOwner == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-kqpoix", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if key.AggregateID == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-xuiwk2", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if key.KeyID == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-0p2m1h", "Errors.IDMissing")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (key *MachineKey) valid() (err error) {
|
||||
if err := key.content(); err != nil {
|
||||
return err
|
||||
}
|
||||
key.ExpirationDate, err = domain.ValidateExpirationDate(key.ExpirationDate)
|
||||
return err
|
||||
}
|
||||
|
||||
func (key *MachineKey) checkAggregate(ctx context.Context, filter preparation.FilterToQueryReducer) error {
|
||||
if exists, err := ExistsUser(ctx, filter, key.AggregateID, key.ResourceOwner); err != nil || !exists {
|
||||
return errors.ThrowPreconditionFailed(err, "COMMAND-bnipwm1", "Errors.User.NotFound")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddUserMachineKey(ctx context.Context, machineKey *MachineKey) (*domain.ObjectDetails, error) {
|
||||
if machineKey.KeyID == "" {
|
||||
keyID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
machineKey.KeyID = keyID
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx,
|
||||
user.NewMachineKeyAddedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&keyWriteModel.WriteModel),
|
||||
machineKey.KeyID,
|
||||
machineKey.Type,
|
||||
machineKey.ExpirationDate,
|
||||
machineKey.PublicKey))
|
||||
validation := prepareAddUserMachineKey(machineKey, c.machineKeySize)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(keyWriteModel, events...)
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := keyWriteModelToMachineKey(keyWriteModel)
|
||||
if len(machineKey.PrivateKey) > 0 {
|
||||
key.PrivateKey = machineKey.PrivateKey
|
||||
}
|
||||
return key, nil
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveUserMachineKey(ctx context.Context, userID, keyID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
keyWriteModel, err := c.machineKeyWriteModelByID(ctx, userID, keyID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func prepareAddUserMachineKey(machineKey *MachineKey, keySize int) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if err := machineKey.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if err := machineKey.checkAggregate(ctx, filter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(machineKey.PublicKey) == 0 {
|
||||
if err = domain.SetNewAuthNKeyPair(machineKey, keySize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
writeModel, err := getMachineKeyWriteModelByID(ctx, filter, machineKey.AggregateID, machineKey.KeyID, machineKey.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.Exists() {
|
||||
return nil, errors.ThrowAlreadyExists(nil, "COMMAND-091mops", "Errors.User.Machine.Key.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineKeyAddedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
machineKey.KeyID,
|
||||
machineKey.Type,
|
||||
machineKey.ExpirationDate,
|
||||
machineKey.PublicKey,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
if !keyWriteModel.Exists() {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.User.Machine.Key.NotFound")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
user.NewMachineKeyRemovedEvent(ctx, UserAggregateFromWriteModel(&keyWriteModel.WriteModel), keyID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(keyWriteModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&keyWriteModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) machineKeyWriteModelByID(ctx context.Context, userID, keyID, resourceOwner string) (writeModel *MachineKeyWriteModel, err error) {
|
||||
if userID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-4n8vs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewMachineKeyWriteModel(userID, keyID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
func (c *Commands) RemoveUserMachineKey(ctx context.Context, machineKey *MachineKey) (*domain.ObjectDetails, error) {
|
||||
validation := prepareRemoveUserMachineKey(machineKey)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareRemoveUserMachineKey(machineKey *MachineKey) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if err := machineKey.content(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineKeyWriteModelByID(ctx, filter, machineKey.AggregateID, machineKey.KeyID, machineKey.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.User.Machine.Key.NotFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineKeyRemovedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
machineKey.KeyID,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getMachineKeyWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, keyID, resourceOwner string) (_ *MachineKeyWriteModel, err error) {
|
||||
writeModel := NewMachineKeyWriteModel(userID, keyID, resourceOwner)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
return writeModel, err
|
||||
}
|
||||
|
253
internal/command/user_machine_key_test.go
Normal file
253
internal/command/user_machine_key_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"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/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
id_mock "github.com/zitadel/zitadel/internal/id/mock"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestCommands_AddMachineKey(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
keyAlgorithm crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
key *MachineKey
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
key bool
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"user does not exist, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
key: &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Time{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid expiration date, error",
|
||||
fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
key: &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Now().Add(-24 * time.Hour),
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no userID, error",
|
||||
fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
key: &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Time{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no resourceowner, error",
|
||||
fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
key: &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "",
|
||||
},
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Time{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"key added with public key",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"machine",
|
||||
"Machine",
|
||||
"",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMachineKeyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key1",
|
||||
domain.AuthNKeyTypeJSON,
|
||||
time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
[]byte("public"),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
key: &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
PublicKey: []byte("public"),
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
key: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"key added with ID and public key",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"machine",
|
||||
"Machine",
|
||||
"",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewMachineKeyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key1",
|
||||
domain.AuthNKeyTypeJSON,
|
||||
time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
[]byte("public"),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
key: &MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
KeyID: "key1",
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ExpirationDate: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
PublicKey: []byte("public"),
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
key: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
keyAlgorithm: tt.fields.keyAlgorithm,
|
||||
}
|
||||
got, err := c.AddUserMachineKey(tt.args.ctx, tt.args.key)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
if tt.res.key {
|
||||
assert.NotEqual(t, "", tt.args.key.PrivateKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -41,16 +41,6 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
},
|
||||
@@ -72,16 +62,6 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
},
|
||||
@@ -105,6 +85,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
|
||||
},
|
||||
@@ -127,6 +108,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
@@ -137,7 +119,6 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@@ -212,7 +193,7 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user invalid, invalid argument error",
|
||||
name: "user invalid, invalid argument error name",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
@@ -231,6 +212,26 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user invalid, invalid argument error username",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
machine: &Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Name: "username",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
@@ -279,6 +280,7 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "user1",
|
||||
},
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
},
|
||||
|
@@ -2,91 +2,195 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *Commands) AddPersonalAccessToken(ctx context.Context, userID, resourceOwner string, expirationDate time.Time, scopes []string, allowedUserType domain.UserType) (*domain.Token, string, error) {
|
||||
userWriteModel, err := c.userWriteModelByID(ctx, userID, resourceOwner)
|
||||
type AddPat struct {
|
||||
ExpirationDate time.Time
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
type PersonalAccessToken struct {
|
||||
models.ObjectRoot
|
||||
|
||||
ExpirationDate time.Time
|
||||
Scopes []string
|
||||
AllowedUserType domain.UserType
|
||||
|
||||
TokenID string
|
||||
Token string
|
||||
}
|
||||
|
||||
func NewPersonalAccessToken(resourceOwner string, userID string, expirationDate time.Time, scopes []string, allowedUserType domain.UserType) *PersonalAccessToken {
|
||||
return &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
ExpirationDate: expirationDate,
|
||||
Scopes: scopes,
|
||||
AllowedUserType: allowedUserType,
|
||||
}
|
||||
}
|
||||
|
||||
func (pat *PersonalAccessToken) content() error {
|
||||
if pat.ResourceOwner == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-xs0k2n", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if pat.AggregateID == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-0pzb1", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if pat.TokenID == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-68xm2o", "Errors.IDMissing")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pat *PersonalAccessToken) valid() (err error) {
|
||||
if err := pat.content(); err != nil {
|
||||
return err
|
||||
}
|
||||
pat.ExpirationDate, err = domain.ValidateExpirationDate(pat.ExpirationDate)
|
||||
return err
|
||||
}
|
||||
|
||||
func (pat *PersonalAccessToken) checkAggregate(ctx context.Context, filter preparation.FilterToQueryReducer) error {
|
||||
userWriteModel, err := userWriteModelByID(ctx, filter, pat.AggregateID, pat.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return err
|
||||
}
|
||||
if !isUserStateExists(userWriteModel.UserState) {
|
||||
return nil, "", errors.ThrowPreconditionFailed(nil, "COMMAND-Dggw2", "Errors.User.NotFound")
|
||||
return errors.ThrowPreconditionFailed(nil, "COMMAND-Dggw2", "Errors.User.NotFound")
|
||||
}
|
||||
if allowedUserType != domain.UserTypeUnspecified && userWriteModel.UserType != allowedUserType {
|
||||
return nil, "", errors.ThrowPreconditionFailed(nil, "COMMAND-Df2f1", "Errors.User.WrongType")
|
||||
if pat.AllowedUserType != domain.UserTypeUnspecified && userWriteModel.UserType != pat.AllowedUserType {
|
||||
return errors.ThrowPreconditionFailed(nil, "COMMAND-Df2f1", "Errors.User.WrongType")
|
||||
}
|
||||
tokenID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
tokenWriteModel := NewPersonalAccessTokenWriteModel(userID, tokenID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, tokenWriteModel)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
expirationDate, err = domain.ValidateExpirationDate(expirationDate)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx,
|
||||
user.NewPersonalAccessTokenAddedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&tokenWriteModel.WriteModel),
|
||||
tokenID,
|
||||
expirationDate,
|
||||
scopes,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
err = AppendAndReduce(tokenWriteModel, events...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return personalTokenWriteModelToToken(tokenWriteModel, c.keyAlgorithm)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemovePersonalAccessToken(ctx context.Context, userID, tokenID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
tokenWriteModel, err := c.personalAccessTokenWriteModelByID(ctx, userID, tokenID, resourceOwner)
|
||||
func (c *Commands) AddPersonalAccessToken(ctx context.Context, pat *PersonalAccessToken) (_ *domain.ObjectDetails, err error) {
|
||||
if pat.TokenID == "" {
|
||||
pat.TokenID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
validation := prepareAddPersonalAccessToken(pat, c.keyAlgorithm)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !tokenWriteModel.Exists() {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.User.PAT.NotFound")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
user.NewPersonalAccessTokenRemovedEvent(ctx, UserAggregateFromWriteModel(&tokenWriteModel.WriteModel), tokenID))
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(tokenWriteModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&tokenWriteModel.WriteModel), nil
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) personalAccessTokenWriteModelByID(ctx context.Context, userID, tokenID, resourceOwner string) (writeModel *PersonalAccessTokenWriteModel, err error) {
|
||||
if userID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-4n8vs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
func prepareAddPersonalAccessToken(pat *PersonalAccessToken, algorithm crypto.EncryptionAlgorithm) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if err := pat.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
||||
if err := pat.checkAggregate(ctx, filter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel, err := getPersonalAccessTokenWriteModelByID(ctx, filter, pat.AggregateID, pat.TokenID, pat.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writeModel = NewPersonalAccessTokenWriteModel(userID, tokenID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
pat.Token, err = createToken(algorithm, writeModel.TokenID, writeModel.AggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
user.NewPersonalAccessTokenAddedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
pat.TokenID,
|
||||
pat.ExpirationDate,
|
||||
pat.Scopes,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) RemovePersonalAccessToken(ctx context.Context, pat *PersonalAccessToken) (*domain.ObjectDetails, error) {
|
||||
validation := prepareRemovePersonalAccessToken(pat)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareRemovePersonalAccessToken(pat *PersonalAccessToken) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if err := pat.content(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
||||
writeModel, err := getPersonalAccessTokenWriteModelByID(ctx, filter, pat.AggregateID, pat.TokenID, pat.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.User.PAT.NotFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewPersonalAccessTokenRemovedEvent(
|
||||
ctx,
|
||||
UserAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
pat.TokenID,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func createToken(algorithm crypto.EncryptionAlgorithm, tokenID, userID string) (string, error) {
|
||||
encrypted, err := algorithm.Encrypt([]byte(tokenID + ":" + userID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func getPersonalAccessTokenWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, tokenID, resourceOwner string) (_ *PersonalAccessTokenWriteModel, err error) {
|
||||
writeModel := NewPersonalAccessTokenWriteModel(userID, tokenID, resourceOwner)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
return writeModel, err
|
||||
}
|
||||
|
@@ -32,15 +32,11 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
keyAlgorithm crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
expirationDate time.Time
|
||||
scopes []string
|
||||
allowedUserType domain.UserType
|
||||
ctx context.Context
|
||||
pat *PersonalAccessToken
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Token
|
||||
want *domain.ObjectDetails
|
||||
token string
|
||||
err func(error) bool
|
||||
}
|
||||
@@ -56,13 +52,18 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
scopes: []string{"openid"},
|
||||
expirationDate: time.Time{},
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Time{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
@@ -84,14 +85,19 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
expirationDate: time.Time{},
|
||||
scopes: []string{"openid"},
|
||||
allowedUserType: domain.UserTypeHuman,
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Time{},
|
||||
AllowedUserType: domain.UserTypeHuman,
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
@@ -100,28 +106,58 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
{
|
||||
"invalid expiration date, error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"machine",
|
||||
"Machine",
|
||||
"",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
expirationDate: time.Now().Add(-24 * time.Hour),
|
||||
scopes: []string{"openid"},
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Now().Add(-24 * time.Hour),
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no userID, error",
|
||||
fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Time{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no resourceowner, error",
|
||||
fields{
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "token1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "",
|
||||
},
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Time{},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
@@ -160,20 +196,71 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
expirationDate: time.Time{},
|
||||
scopes: []string{"openid"},
|
||||
},
|
||||
res{
|
||||
want: &domain.Token{
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TokenID: "token1",
|
||||
Expiration: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Time{},
|
||||
AllowedUserType: domain.UserTypeMachine,
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
token: base64.RawURLEncoding.EncodeToString([]byte("token1:user1")),
|
||||
},
|
||||
},
|
||||
{
|
||||
"token added with ID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"machine",
|
||||
"Machine",
|
||||
"",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
user.NewPersonalAccessTokenAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"token1",
|
||||
time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||
[]string{"openid"},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TokenID: "token1",
|
||||
Scopes: []string{"openid"},
|
||||
ExpirationDate: time.Time{},
|
||||
AllowedUserType: domain.UserTypeMachine,
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
token: base64.RawURLEncoding.EncodeToString([]byte("token1:user1")),
|
||||
},
|
||||
@@ -186,7 +273,7 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
keyAlgorithm: tt.fields.keyAlgorithm,
|
||||
}
|
||||
got, token, err := c.AddPersonalAccessToken(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.expirationDate, tt.args.scopes, tt.args.allowedUserType)
|
||||
got, err := c.AddPersonalAccessToken(tt.args.ctx, tt.args.pat)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -195,7 +282,7 @@ func TestCommands_AddPersonalAccessToken(t *testing.T) {
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assert.Equal(t, tt.res.token, token)
|
||||
assert.Equal(t, tt.res.token, tt.args.pat.Token)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -206,10 +293,8 @@ func TestCommands_RemovePersonalAccessToken(t *testing.T) {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
tokenID string
|
||||
resourceOwner string
|
||||
ctx context.Context
|
||||
pat *PersonalAccessToken
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -229,10 +314,14 @@ func TestCommands_RemovePersonalAccessToken(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
tokenID: "token1",
|
||||
resourceOwner: "org1",
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TokenID: "token1",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: caos_errs.IsNotFound,
|
||||
@@ -265,10 +354,14 @@ func TestCommands_RemovePersonalAccessToken(t *testing.T) {
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
tokenID: "token1",
|
||||
resourceOwner: "org1",
|
||||
ctx: context.Background(),
|
||||
pat: &PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TokenID: "token1",
|
||||
},
|
||||
},
|
||||
res{
|
||||
want: &domain.ObjectDetails{
|
||||
@@ -282,7 +375,7 @@ func TestCommands_RemovePersonalAccessToken(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := c.RemovePersonalAccessToken(tt.args.ctx, tt.args.userID, tt.args.tokenID, tt.args.resourceOwner)
|
||||
got, err := c.RemovePersonalAccessToken(tt.args.ctx, tt.args.pat)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -20,19 +20,19 @@ type ApplicationKey struct {
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setPublicKey(publicKey []byte) {
|
||||
func (k *ApplicationKey) SetPublicKey(publicKey []byte) {
|
||||
k.PublicKey = publicKey
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setPrivateKey(privateKey []byte) {
|
||||
func (k *ApplicationKey) SetPrivateKey(privateKey []byte) {
|
||||
k.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) expirationDate() time.Time {
|
||||
func (k *ApplicationKey) GetExpirationDate() time.Time {
|
||||
return k.ExpirationDate
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setExpirationDate(expiration time.Time) {
|
||||
func (k *ApplicationKey) SetExpirationDate(expiration time.Time) {
|
||||
k.ExpirationDate = expiration
|
||||
}
|
||||
|
||||
|
@@ -8,8 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type authNKey interface {
|
||||
setPublicKey([]byte)
|
||||
setPrivateKey([]byte)
|
||||
SetPublicKey([]byte)
|
||||
SetPrivateKey([]byte)
|
||||
expiration
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ func SetNewAuthNKeyPair(key authNKey, keySize int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.setPrivateKey(privateKey)
|
||||
key.setPublicKey(publicKey)
|
||||
key.SetPrivateKey(privateKey)
|
||||
key.SetPublicKey(publicKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -12,16 +12,16 @@ var (
|
||||
)
|
||||
|
||||
type expiration interface {
|
||||
expirationDate() time.Time
|
||||
setExpirationDate(time.Time)
|
||||
GetExpirationDate() time.Time
|
||||
SetExpirationDate(time.Time)
|
||||
}
|
||||
|
||||
func EnsureValidExpirationDate(key expiration) error {
|
||||
date, err := ValidateExpirationDate(key.expirationDate())
|
||||
date, err := ValidateExpirationDate(key.GetExpirationDate())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.setExpirationDate(date)
|
||||
key.SetExpirationDate(date)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -42,17 +42,7 @@ func (key *MachineKey) Detail() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (key *MachineKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Type string `json:"type"`
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
UserID string `json:"userId"`
|
||||
}{
|
||||
Type: "serviceaccount",
|
||||
KeyID: key.KeyID,
|
||||
Key: string(key.PrivateKey),
|
||||
UserID: key.AggregateID,
|
||||
})
|
||||
return MachineKeyMarshalJSON(key.KeyID, key.PrivateKey, key.AggregateID)
|
||||
}
|
||||
|
||||
type MachineKeyState int32
|
||||
@@ -68,3 +58,17 @@ const (
|
||||
func (f MachineKeyState) Valid() bool {
|
||||
return f >= 0 && f < machineKeyStateCount
|
||||
}
|
||||
|
||||
func MachineKeyMarshalJSON(keyID string, privateKey []byte, userID string) ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Type string `json:"type"`
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
UserID string `json:"userId"`
|
||||
}{
|
||||
Type: "serviceaccount",
|
||||
KeyID: keyID,
|
||||
Key: string(privateKey),
|
||||
UserID: userID,
|
||||
})
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ syntax = "proto3";
|
||||
import "zitadel/object.proto";
|
||||
import "zitadel/options.proto";
|
||||
import "zitadel/instance.proto";
|
||||
import "zitadel/auth_n_key.proto";
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
@@ -121,6 +122,7 @@ service SystemService {
|
||||
};
|
||||
}
|
||||
|
||||
// Deprecated: Use CreateInstance instead
|
||||
// Creates a new instance with all needed setup data
|
||||
// This might take some time
|
||||
rpc AddInstance(AddInstanceRequest) returns (AddInstanceResponse) {
|
||||
@@ -146,6 +148,19 @@ service SystemService {
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a new instance with all needed setup data
|
||||
// This might take some time
|
||||
rpc CreateInstance(CreateInstanceRequest) returns (CreateInstanceResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/instances/_create"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated";
|
||||
};
|
||||
}
|
||||
|
||||
// Removes a instances
|
||||
// This might take some time
|
||||
rpc RemoveInstance(RemoveInstanceRequest) returns (RemoveInstanceResponse) {
|
||||
@@ -409,6 +424,72 @@ message AddInstanceResponse {
|
||||
zitadel.v1.ObjectDetails details = 2;
|
||||
}
|
||||
|
||||
message CreateInstanceRequest {
|
||||
message Profile {
|
||||
string first_name = 1 [(validate.rules).string = {max_len: 200}];
|
||||
string last_name = 2 [(validate.rules).string = {max_len: 200}];
|
||||
string preferred_language = 3 [(validate.rules).string = {max_len: 10}];
|
||||
}
|
||||
message Email {
|
||||
string email = 1[(validate.rules).string = {min_len: 1, max_len: 200, email: true}];
|
||||
bool is_email_verified = 2;
|
||||
}
|
||||
message Password {
|
||||
string password = 1 [(validate.rules).string = {max_len: 200}];
|
||||
bool password_change_required = 2;
|
||||
}
|
||||
message Human {
|
||||
string user_name = 1 [(validate.rules).string = {max_len: 200}];
|
||||
Email email = 2 [(validate.rules).message.required = true];
|
||||
Profile profile = 3 [(validate.rules).message.required = false];
|
||||
Password password = 4 [(validate.rules).message.required = false];
|
||||
}
|
||||
message PersonalAccessToken {
|
||||
google.protobuf.Timestamp expiration_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2519-04-01T08:45:00.000000Z\"";
|
||||
description: "The date the token will expire and no logins will be possible";
|
||||
}
|
||||
];
|
||||
}
|
||||
message MachineKey {
|
||||
zitadel.authn.v1.KeyType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}];
|
||||
google.protobuf.Timestamp expiration_date = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2519-04-01T08:45:00.000000Z\"";
|
||||
description: "The date the key will expire and no logins will be possible";
|
||||
}
|
||||
];
|
||||
}
|
||||
message Machine {
|
||||
string user_name = 1 [(validate.rules).string = {max_len: 200}];
|
||||
string name = 2 [(validate.rules).string = {max_len: 200}];
|
||||
PersonalAccessToken personal_access_token = 3;
|
||||
MachineKey machine_key = 4;
|
||||
}
|
||||
|
||||
string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
|
||||
string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
|
||||
|
||||
oneof owner {
|
||||
option (validate.required) = true;
|
||||
|
||||
// oneof field for the user managing the instance
|
||||
Human human = 4;
|
||||
Machine machine = 5;
|
||||
}
|
||||
|
||||
string default_language = 6 [(validate.rules).string = {max_len: 10}];
|
||||
}
|
||||
|
||||
message CreateInstanceResponse {
|
||||
string instance_id = 1;
|
||||
zitadel.v1.ObjectDetails details = 2;
|
||||
string pat = 3;
|
||||
bytes machine_key = 4;
|
||||
}
|
||||
|
||||
message UpdateInstanceRequest{
|
||||
string instance_id = 1;
|
||||
string instance_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
|
Reference in New Issue
Block a user