mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 14:07:24 +00:00

# Which Problems Are Solved Project creation always requires a user as project owner, in case of a system user creating the project, there is no valid user existing at that moment. # How the Problems Are Solved Remove the initially created project owner membership, as this is something which was necessary in old versions, and all should work perfectly without. The call to add a project automatically designates the calling user as the project owner, which is irrelevant currently, as this user always already has higher permissions to be able to even create the project. # Additional Changes Cleanup of the existing checks for the project, which can be improved through the usage of the fields table. # Additional Context Closes #9182
397 lines
13 KiB
Go
397 lines
13 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
project_repo "github.com/zitadel/zitadel/internal/repository/project"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type addOIDCApp struct {
|
|
AddApp
|
|
Version domain.OIDCVersion
|
|
RedirectUris []string
|
|
ResponseTypes []domain.OIDCResponseType
|
|
GrantTypes []domain.OIDCGrantType
|
|
ApplicationType domain.OIDCApplicationType
|
|
AuthMethodType domain.OIDCAuthMethodType
|
|
PostLogoutRedirectUris []string
|
|
DevMode bool
|
|
AccessTokenType domain.OIDCTokenType
|
|
AccessTokenRoleAssertion bool
|
|
IDTokenRoleAssertion bool
|
|
IDTokenUserinfoAssertion bool
|
|
ClockSkew time.Duration
|
|
AdditionalOrigins []string
|
|
SkipSuccessPageForNativeApp bool
|
|
BackChannelLogoutURI string
|
|
LoginVersion domain.LoginVersion
|
|
LoginBaseURI string
|
|
|
|
ClientID string
|
|
ClientSecret string
|
|
ClientSecretPlain string
|
|
}
|
|
|
|
// AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands
|
|
func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
|
|
return func() (preparation.CreateCommands, error) {
|
|
if app.ID == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument")
|
|
}
|
|
|
|
if app.Name = strings.TrimSpace(app.Name); app.Name == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument")
|
|
}
|
|
|
|
if app.ClockSkew > time.Second*5 || app.ClockSkew < 0 {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "V2-PnCMS", "Errors.Invalid.Argument")
|
|
}
|
|
|
|
for _, origin := range app.AdditionalOrigins {
|
|
if !http_util.IsOrigin(strings.TrimSpace(origin)) {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "V2-DqWPX", "Errors.Invalid.Argument")
|
|
}
|
|
}
|
|
|
|
if !domain.ContainsRequiredGrantTypes(app.ResponseTypes, app.GrantTypes) {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "V2-sLpW1", "Errors.Invalid.Argument")
|
|
}
|
|
|
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
|
project, err := projectWriteModel(ctx, filter, app.Aggregate.ID, app.Aggregate.ResourceOwner)
|
|
if err != nil || !project.State.Valid() {
|
|
return nil, zerrors.ThrowNotFound(err, "PROJE-6swVG", "Errors.Project.NotFound")
|
|
}
|
|
|
|
app.ClientID, err = c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "V2-VMSQ1", "Errors.Internal")
|
|
}
|
|
|
|
if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost {
|
|
app.ClientSecret, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return []eventstore.Command{
|
|
project_repo.NewApplicationAddedEvent(
|
|
ctx,
|
|
&app.Aggregate.Aggregate,
|
|
app.ID,
|
|
app.Name,
|
|
),
|
|
project_repo.NewOIDCConfigAddedEvent(
|
|
ctx,
|
|
&app.Aggregate.Aggregate,
|
|
app.Version,
|
|
app.ID,
|
|
app.ClientID,
|
|
app.ClientSecret,
|
|
trimStringSliceWhiteSpaces(app.RedirectUris),
|
|
app.ResponseTypes,
|
|
app.GrantTypes,
|
|
app.ApplicationType,
|
|
app.AuthMethodType,
|
|
trimStringSliceWhiteSpaces(app.PostLogoutRedirectUris),
|
|
app.DevMode,
|
|
app.AccessTokenType,
|
|
app.AccessTokenRoleAssertion,
|
|
app.IDTokenRoleAssertion,
|
|
app.IDTokenUserinfoAssertion,
|
|
app.ClockSkew,
|
|
trimStringSliceWhiteSpaces(app.AdditionalOrigins),
|
|
app.SkipSuccessPageForNativeApp,
|
|
app.BackChannelLogoutURI,
|
|
app.LoginVersion,
|
|
app.LoginBaseURI,
|
|
),
|
|
}, nil
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
existingApp, err := c.getOIDCAppWriteModel(ctx, oidcApp.AggregateID, appID, resourceOwner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existingApp.State != domain.AppStateUnspecified {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-lxowmp", "Errors.Project.App.AlreadyExisting")
|
|
}
|
|
|
|
if err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
|
|
return nil, err
|
|
}
|
|
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, appID)
|
|
}
|
|
|
|
func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) {
|
|
if oidcApp == nil || oidcApp.AggregateID == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
|
|
}
|
|
if err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
|
|
return nil, err
|
|
}
|
|
if oidcApp.AppName == "" || !oidcApp.IsValid() {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Project.App.Invalid")
|
|
}
|
|
|
|
appID, err := c.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, appID)
|
|
}
|
|
|
|
func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, appID string) (_ *domain.OIDCApp, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner)
|
|
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
|
|
|
oidcApp.AppID = appID
|
|
|
|
events := []eventstore.Command{
|
|
project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
|
|
}
|
|
|
|
var plain string
|
|
err = domain.SetNewClientID(oidcApp, c.idGenerator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plain, err = domain.SetNewClientSecretIfNeeded(oidcApp, func() (string, string, error) {
|
|
return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
|
|
projectAgg,
|
|
oidcApp.OIDCVersion,
|
|
oidcApp.AppID,
|
|
oidcApp.ClientID,
|
|
oidcApp.EncodedHash,
|
|
trimStringSliceWhiteSpaces(oidcApp.RedirectUris),
|
|
oidcApp.ResponseTypes,
|
|
oidcApp.GrantTypes,
|
|
oidcApp.ApplicationType,
|
|
oidcApp.AuthMethodType,
|
|
trimStringSliceWhiteSpaces(oidcApp.PostLogoutRedirectUris),
|
|
oidcApp.DevMode,
|
|
oidcApp.AccessTokenType,
|
|
oidcApp.AccessTokenRoleAssertion,
|
|
oidcApp.IDTokenRoleAssertion,
|
|
oidcApp.IDTokenUserinfoAssertion,
|
|
oidcApp.ClockSkew,
|
|
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
|
|
oidcApp.SkipNativeAppSuccessPage,
|
|
strings.TrimSpace(oidcApp.BackChannelLogoutURI),
|
|
oidcApp.LoginVersion,
|
|
strings.TrimSpace(oidcApp.LoginBaseURI),
|
|
))
|
|
|
|
addedApplication.AppID = oidcApp.AppID
|
|
postCommit, err := c.applicationCreatedMilestone(ctx, &events)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
postCommit(ctx)
|
|
err = AppendAndReduce(addedApplication, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := oidcWriteModelToOIDCConfig(addedApplication)
|
|
result.ClientSecretString = plain
|
|
result.FillCompliance()
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
|
|
if !oidc.IsValid() || oidc.AppID == "" || oidc.AggregateID == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5m9fs", "Errors.Project.App.OIDCConfigInvalid")
|
|
}
|
|
|
|
existingOIDC, err := c.getOIDCAppWriteModel(ctx, oidc.AggregateID, oidc.AppID, resourceOwner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existingOIDC.State == domain.AppStateUnspecified || existingOIDC.State == domain.AppStateRemoved {
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-2n8uU", "Errors.Project.App.NotExisting")
|
|
}
|
|
if !existingOIDC.IsOIDC() {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr34", "Errors.Project.App.IsNotOIDC")
|
|
}
|
|
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
|
|
changedEvent, hasChanged, err := existingOIDC.NewChangedEvent(
|
|
ctx,
|
|
projectAgg,
|
|
oidc.AppID,
|
|
trimStringSliceWhiteSpaces(oidc.RedirectUris),
|
|
trimStringSliceWhiteSpaces(oidc.PostLogoutRedirectUris),
|
|
oidc.ResponseTypes,
|
|
oidc.GrantTypes,
|
|
oidc.ApplicationType,
|
|
oidc.AuthMethodType,
|
|
oidc.OIDCVersion,
|
|
oidc.AccessTokenType,
|
|
oidc.DevMode,
|
|
oidc.AccessTokenRoleAssertion,
|
|
oidc.IDTokenRoleAssertion,
|
|
oidc.IDTokenUserinfoAssertion,
|
|
oidc.ClockSkew,
|
|
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
|
|
oidc.SkipNativeAppSuccessPage,
|
|
strings.TrimSpace(oidc.BackChannelLogoutURI),
|
|
oidc.LoginVersion,
|
|
strings.TrimSpace(oidc.LoginBaseURI),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !hasChanged {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1m88i", "Errors.NoChangesFound")
|
|
}
|
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(existingOIDC, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := oidcWriteModelToOIDCConfig(existingOIDC)
|
|
result.FillCompliance()
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.OIDCApp, error) {
|
|
if projectID == "" || appID == "" {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
|
|
}
|
|
|
|
existingOIDC, err := c.getOIDCAppWriteModel(ctx, projectID, appID, resourceOwner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existingOIDC.State == domain.AppStateUnspecified || existingOIDC.State == domain.AppStateRemoved {
|
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-2g66f", "Errors.Project.App.NotExisting")
|
|
}
|
|
if !existingOIDC.IsOIDC() {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC")
|
|
}
|
|
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
|
|
|
|
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = AppendAndReduce(existingOIDC, pushedEvents...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := oidcWriteModelToOIDCConfig(existingOIDC)
|
|
result.ClientSecretString = plain
|
|
return result, err
|
|
}
|
|
|
|
func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID, secret string) (err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
app, err := c.getOIDCAppWriteModel(ctx, projectID, appID, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !app.State.Exists() {
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting")
|
|
}
|
|
if !app.IsOIDC() {
|
|
return zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC")
|
|
}
|
|
if app.HashedSecret == "" {
|
|
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid")
|
|
}
|
|
|
|
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
|
|
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
|
updated, err := c.secretHasher.Verify(app.HashedSecret, secret)
|
|
spanPasswordComparison.EndWithError(err)
|
|
if err != nil {
|
|
return zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
|
|
}
|
|
if updated != "" {
|
|
c.oidcUpdateSecret(ctx, projectAgg, appID, updated)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Commands) OIDCUpdateSecret(ctx context.Context, appID, projectID, resourceOwner, updated string) {
|
|
agg := project_repo.NewAggregate(projectID, resourceOwner)
|
|
c.oidcUpdateSecret(ctx, &agg.Aggregate, appID, updated)
|
|
}
|
|
|
|
func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (_ *OIDCApplicationWriteModel, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
appWriteModel := NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
|
|
err = c.eventstore.FilterToQueryReducer(ctx, appWriteModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return appWriteModel, nil
|
|
}
|
|
|
|
func getOIDCAppWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) {
|
|
appWriteModel := NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
|
|
events, err := filter(ctx, appWriteModel.Query())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(events) == 0 {
|
|
return appWriteModel, nil
|
|
}
|
|
appWriteModel.AppendEvents(events...)
|
|
err = appWriteModel.Reduce()
|
|
return appWriteModel, err
|
|
}
|
|
|
|
func trimStringSliceWhiteSpaces(slice []string) []string {
|
|
for i, s := range slice {
|
|
slice[i] = strings.TrimSpace(s)
|
|
}
|
|
return slice
|
|
}
|
|
|
|
func (c *Commands) oidcUpdateSecret(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) {
|
|
c.asyncPush(ctx, project_repo.NewOIDCConfigSecretHashUpdatedEvent(ctx, agg, appID, updated))
|
|
}
|