mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:17:23 +00:00
feat(instance): implement create instance with direct machine user and credentials
This commit is contained in:
parent
2bc19f55b5
commit
f0f0cad231
@ -88,7 +88,7 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
|
||||
mig.instanceSetup.Org.Human.Email.Address = "admin@" + mig.instanceSetup.CustomDomain
|
||||
}
|
||||
|
||||
_, _, err = cmd.SetUpInstance(ctx, &mig.instanceSetup)
|
||||
_, _, _, _, err = cmd.SetUpInstance(ctx, &mig.instanceSetup)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,18 @@ This might take some time
|
||||
POST: /instances
|
||||
|
||||
|
||||
### CreateInstance
|
||||
|
||||
> **rpc** CreateInstance([CreateInstanceRequest](#createinstancerequest))
|
||||
[CreateInstanceResponse](#createinstanceresponse)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
POST: /instances/_create
|
||||
|
||||
|
||||
### RemoveInstance
|
||||
|
||||
> **rpc** RemoveInstance([RemoveInstanceRequest](#removeinstancerequest))
|
||||
@ -330,6 +342,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) user.human | CreateInstanceRequest.Human | oneof field for the user managing the instance | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) user.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 /> |
|
||||
| is_email_verified | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### CreateInstanceRequest.Human
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| email | CreateInstanceRequest.Email | - | message.required: true<br /> |
|
||||
| profile | CreateInstanceRequest.Profile | - | message.required: false<br /> |
|
||||
| password | CreateInstanceRequest.Password | - | message.required: false<br /> |
|
||||
| user_name | string | - | string.max_len: 200<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
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
id, _, _, details, err := s.command.SetUpInstance(ctx, AddInstancePbToSetupInstance(req, s.DefaultInstance))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -51,6 +51,19 @@ func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequ
|
||||
}, 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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &system_pb.CreateInstanceResponse{
|
||||
Pat: pat,
|
||||
MachineKey: key,
|
||||
InstanceId: id,
|
||||
Details: object.AddToDetailsPb(details.Sequence, details.EventDate, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ExistsDomain(ctx context.Context, req *system_pb.ExistsDomainRequest) (*system_pb.ExistsDomainResponse, error) {
|
||||
domainQuery, err := query.NewInstanceDomainDomainSearchQuery(query.TextEqualsIgnoreCase, req.Domain)
|
||||
if err != nil {
|
||||
|
@ -1,16 +1,94 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"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"
|
||||
instance_pb "github.com/zitadel/zitadel/pkg/grpc/instance"
|
||||
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
)
|
||||
|
||||
func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defaultInstance command.InstanceSetup) *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 {
|
||||
defaultInstance.Org.Machine = &command.AddMachine{
|
||||
Machine: &domain.Machine{},
|
||||
}
|
||||
if user.UserName != "" {
|
||||
defaultInstance.Org.Machine.Machine.Username = user.UserName
|
||||
}
|
||||
if user.Name != "" {
|
||||
defaultInstance.Org.Machine.Machine.Name = user.Name
|
||||
}
|
||||
if user.PersonalAccessToken != nil {
|
||||
defaultInstance.Org.Machine.Pat = true
|
||||
defaultInstance.Org.Machine.PatScopes = []string{oidc.ScopeOpenID, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}
|
||||
if user.PersonalAccessToken.ExpirationDate != nil {
|
||||
defaultInstance.Org.Machine.PatExpirationDate = user.PersonalAccessToken.ExpirationDate.AsTime()
|
||||
}
|
||||
}
|
||||
if user.MachineKey != nil {
|
||||
defaultInstance.Org.Machine.MachineKey = true
|
||||
defaultInstance.Org.Machine.MachineKeyType = authn.KeyTypeToDomain(user.MachineKey.Type)
|
||||
if user.MachineKey.ExpirationDate != nil {
|
||||
defaultInstance.Org.Machine.MachineKeyExpirationDate = user.MachineKey.ExpirationDate.AsTime()
|
||||
}
|
||||
}
|
||||
defaultInstance.Org.Human = nil
|
||||
}
|
||||
if user := req.GetHuman(); user != nil {
|
||||
if user.Email != nil {
|
||||
defaultInstance.Org.Human.Email.Address = user.Email.Email
|
||||
defaultInstance.Org.Human.Email.Verified = user.Email.IsEmailVerified
|
||||
}
|
||||
if user.Profile != nil {
|
||||
if user.Profile.FirstName != "" {
|
||||
defaultInstance.Org.Human.FirstName = user.Profile.FirstName
|
||||
}
|
||||
if user.Profile.LastName != "" {
|
||||
defaultInstance.Org.Human.LastName = user.Profile.LastName
|
||||
}
|
||||
if user.Profile.PreferredLanguage != "" {
|
||||
lang, err := language.Parse(user.Profile.PreferredLanguage)
|
||||
if err == nil {
|
||||
defaultInstance.Org.Human.PreferredLanguage = lang
|
||||
}
|
||||
}
|
||||
}
|
||||
if user.UserName != "" {
|
||||
defaultInstance.Org.Human.Username = user.UserName
|
||||
}
|
||||
if user.Password != nil {
|
||||
defaultInstance.Org.Human.Password = user.Password.Password
|
||||
defaultInstance.Org.Human.PasswordChangeRequired = user.Password.PasswordChangeRequired
|
||||
}
|
||||
defaultInstance.Org.Machine = nil
|
||||
}
|
||||
|
||||
if lang := language.Make(req.DefaultLanguage); lang != language.Und {
|
||||
defaultInstance.DefaultLanguage = lang
|
||||
}
|
||||
|
||||
return &defaultInstance
|
||||
}
|
||||
|
||||
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup) *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,
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"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/id"
|
||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
@ -142,30 +143,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, []byte, *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)
|
||||
|
||||
@ -273,10 +274,21 @@ 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),
|
||||
)
|
||||
//only a human or a machine user should be created as owner
|
||||
if setup.Org.Human != nil {
|
||||
validations = append(validations,
|
||||
AddHumanCommand(userAgg, setup.Org.Human, c.userPasswordAlg, c.userEncryption),
|
||||
)
|
||||
} else if setup.Org.Machine != nil {
|
||||
validations = append(validations,
|
||||
AddMachineCommand(userAgg, setup.Org.Machine.Machine),
|
||||
)
|
||||
}
|
||||
|
||||
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),
|
||||
|
||||
@ -322,7 +334,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 != "" {
|
||||
@ -348,14 +360,40 @@ 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{
|
||||
|
||||
pat := ""
|
||||
machineKey := make([]byte, 0)
|
||||
if setup.Org.Machine != nil {
|
||||
if setup.Org.Machine.Pat {
|
||||
_, token, err := c.AddPersonalAccessToken(ctx, userID, orgID, setup.Org.Machine.PatExpirationDate, setup.Org.Machine.PatScopes, domain.UserTypeMachine)
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
pat = token
|
||||
}
|
||||
if setup.Org.Machine.MachineKey {
|
||||
key, err := c.AddUserMachineKey(ctx, &domain.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: userID,
|
||||
},
|
||||
ExpirationDate: setup.Org.Machine.MachineKeyExpirationDate,
|
||||
Type: setup.Org.Machine.MachineKeyType,
|
||||
}, orgID)
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
machineKey = key.PrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
return instanceID, pat, machineKey, &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
@ -444,7 +482,7 @@ func prepareAddInstance(a *instance.Aggregate, instanceName string, defaultLangu
|
||||
}
|
||||
}
|
||||
|
||||
//SetIAMProject defines the command to set the id of the IAM project onto the instance
|
||||
// SetIAMProject defines the command to set the id of the IAM project onto the instance
|
||||
func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
@ -455,7 +493,7 @@ func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validati
|
||||
}
|
||||
}
|
||||
|
||||
//SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
|
||||
// SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
|
||||
func SetIAMConsoleID(a *instance.Aggregate, clientID, appID *string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
caos_errs "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/org"
|
||||
user_repo "github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
@ -17,7 +18,8 @@ import (
|
||||
type OrgSetup struct {
|
||||
Name string
|
||||
CustomDomain string
|
||||
Human AddHuman
|
||||
Human *AddHuman
|
||||
Machine *AddMachine
|
||||
Roles []string
|
||||
}
|
||||
|
||||
@ -30,10 +32,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, []byte, *domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(orgID)
|
||||
userAgg := user_repo.NewAggregate(userID, orgID)
|
||||
|
||||
@ -44,23 +47,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...),
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
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{
|
||||
|
||||
pat := ""
|
||||
machineKey := make([]byte, 0)
|
||||
if o.Machine != nil {
|
||||
if o.Machine.Pat {
|
||||
_, token, err := c.AddPersonalAccessToken(ctx, userID, orgID, o.Machine.PatExpirationDate, o.Machine.PatScopes, domain.UserTypeMachine)
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
pat = token
|
||||
}
|
||||
if o.Machine.MachineKey {
|
||||
key, err := c.AddUserMachineKey(ctx, &domain.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: userID,
|
||||
},
|
||||
ExpirationDate: o.Machine.MachineKeyExpirationDate,
|
||||
Type: o.Machine.MachineKeyType,
|
||||
}, orgID)
|
||||
if err != nil {
|
||||
return "", "", nil, nil, err
|
||||
}
|
||||
machineKey = key.PrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
return userID, pat, machineKey, &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
@ -78,10 +113,11 @@ 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,
|
||||
// AddOrgCommand defines the commands to create a new org,
|
||||
// this includes the verified default domain
|
||||
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string, userIDs ...string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
|
@ -2,108 +2,166 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"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/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type AddMachine struct {
|
||||
Machine *domain.Machine
|
||||
Pat bool
|
||||
PatExpirationDate time.Time
|
||||
PatScopes []string
|
||||
MachineKey bool
|
||||
MachineKeyType domain.AuthNKeyType
|
||||
MachineKeyExpirationDate time.Time
|
||||
}
|
||||
|
||||
func AddMachineCommand(a *user.Aggregate, machine *domain.Machine) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if !machine.IsValid() {
|
||||
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")
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0dd", "Errors.User.Invalid")
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
user.NewMachineAddedEvent(ctx, &a.Aggregate, machine.Username, machine.Name, machine.Description, domainPolicy.UserLoginMustBeDomain),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain.Machine) (*domain.Machine, error) {
|
||||
if !machine.IsValid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
|
||||
}
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
userID, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.addMachineWithID(ctx, orgID, userID, machine, domainPolicy)
|
||||
return c.addMachineWithID(ctx, orgID, userID, machine)
|
||||
}
|
||||
|
||||
func (c *Commands) AddMachineWithID(ctx context.Context, orgID string, userID string, machine *domain.Machine) (*domain.Machine, error) {
|
||||
existingMachine, err := c.machineWriteModelByID(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isUserStateExists(existingMachine.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-k2una", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0dd", "Errors.User.Invalid")
|
||||
}
|
||||
return c.addMachineWithID(ctx, orgID, userID, machine, domainPolicy)
|
||||
return c.addMachineWithID(ctx, orgID, userID, machine)
|
||||
}
|
||||
|
||||
func (c *Commands) addMachineWithID(ctx context.Context, orgID string, userID string, machine *domain.Machine, domainPolicy *domain.DomainPolicy) (*domain.Machine, error) {
|
||||
func (c *Commands) addMachineWithID(ctx context.Context, orgID string, userID string, machine *domain.Machine) (*domain.Machine, error) {
|
||||
agg := user.NewAggregate(userID, orgID)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, AddMachineCommand(agg, machine))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
machine.AggregateID = userID
|
||||
addedMachine := NewMachineWriteModel(machine.AggregateID, orgID)
|
||||
userAgg := UserAggregateFromWriteModel(&addedMachine.WriteModel)
|
||||
events, err := c.eventstore.Push(ctx, user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
userAgg,
|
||||
machine.Username,
|
||||
machine.Name,
|
||||
machine.Description,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
))
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(addedMachine, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToMachine(addedMachine), nil
|
||||
|
||||
return &domain.Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: agg.ID,
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
CreationDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
InstanceID: events[len(events)-1].Aggregate().InstanceID,
|
||||
},
|
||||
Username: machine.Username,
|
||||
Name: machine.Name,
|
||||
Description: machine.Description,
|
||||
State: machine.State,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeMachine(ctx context.Context, machine *domain.Machine) (*domain.Machine, error) {
|
||||
existingMachine, err := c.machineWriteModelByID(ctx, machine.AggregateID, machine.ResourceOwner)
|
||||
agg := user.NewAggregate(machine.AggregateID, machine.ResourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, changeMachineCommand(agg, machine))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingMachine.UserState) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
userAgg := UserAggregateFromWriteModel(&existingMachine.WriteModel)
|
||||
changedEvent, hasChanged, err := existingMachine.NewChangedEvent(ctx, userAgg, machine.Name, machine.Description)
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.NotChanged")
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingMachine, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToMachine(existingMachine), nil
|
||||
return &domain.Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: agg.ID,
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
CreationDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
InstanceID: events[len(events)-1].Aggregate().InstanceID,
|
||||
},
|
||||
Username: machine.Username,
|
||||
Name: machine.Name,
|
||||
Description: machine.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) machineWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *MachineWriteModel, err error) {
|
||||
if userID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0Plof", "Errors.User.UserIDMissing")
|
||||
}
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
func changeMachineCommand(a *user.Aggregate, machine *domain.Machine) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if !machine.IsValid() {
|
||||
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.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
writeModel = NewMachineWriteModel(userID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
domainPolicy, err := domainPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0dd", "Errors.User.Invalid")
|
||||
}
|
||||
|
||||
changedEvent, hasChanged, err := writeModel.NewChangedEvent(ctx, &a.Aggregate, machine.Name, machine.Description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.NotChanged")
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
changedEvent,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return writeModel, nil
|
||||
if len(events) == 0 {
|
||||
return writeModel, nil
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
err = writeModel.Reduce()
|
||||
return writeModel, err
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "username"),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -62,7 +63,9 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
expectFilter(),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "username"),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@ -81,6 +84,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
@ -126,7 +130,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
State: domain.UserStateActive,
|
||||
State: domain.UserStateUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -171,7 +175,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,
|
||||
@ -188,6 +192,24 @@ 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(),
|
||||
orgID: "org1",
|
||||
machine: &domain.Machine{
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
@ -227,6 +249,16 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
@ -236,6 +268,7 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
},
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
},
|
||||
@ -260,6 +293,16 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@ -274,8 +317,10 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
orgID: "org1",
|
||||
machine: &domain.Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Username: "username",
|
||||
Description: "description1",
|
||||
Name: "name1",
|
||||
},
|
||||
@ -289,7 +334,7 @@ func TestCommandSide_ChangeMachine(t *testing.T) {
|
||||
Username: "username",
|
||||
Name: "name1",
|
||||
Description: "description1",
|
||||
State: domain.UserStateActive,
|
||||
State: domain.UserStateUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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";
|
||||
@ -134,6 +135,17 @@ service SystemService {
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -397,6 +409,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 = 5 [(validate.rules).string = {max_len: 10}];
|
||||
}
|
||||
message Email {
|
||||
string email = 1[(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
bool is_email_verified = 2;
|
||||
}
|
||||
message Password {
|
||||
string password = 1 [(validate.rules).string = {max_len: 200}];
|
||||
bool password_change_required = 2;
|
||||
}
|
||||
message Human {
|
||||
Email email = 1 [(validate.rules).message.required = true];
|
||||
Profile profile = 2 [(validate.rules).message.required = false];
|
||||
Password password = 3 [(validate.rules).message.required = false];
|
||||
string user_name = 4 [(validate.rules).string = {max_len: 200}];
|
||||
}
|
||||
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 user {
|
||||
option (validate.required) = true;
|
||||
|
||||
// oneof field for the user managing the instance
|
||||
Human human = 4;
|
||||
Machine machine = 5;
|
||||
}
|
||||
|
||||
string default_language = 8 [(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 RemoveInstanceRequest {
|
||||
string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user