Fabi a7cc57822b
fix: cleanup (#1312)
* fix: remove unused code

* fix: remove unused code

* fix: remove unused code

* fix: remove unused code
2021-02-19 14:35:42 +01:00

483 lines
17 KiB
Go

package eventsourcing
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/caos/logging"
"github.com/golang/protobuf/ptypes"
"github.com/caos/zitadel/internal/cache/config"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
es_int "github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
"github.com/caos/zitadel/internal/id"
key_model "github.com/caos/zitadel/internal/key/model"
proj_model "github.com/caos/zitadel/internal/project/model"
"github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
const (
projectOwnerRole = "PROJECT_OWNER"
projectOwnerGlobalRole = "PROJECT_OWNER_GLOBAL"
)
type ProjectEventstore struct {
es_int.Eventstore
projectCache *ProjectCache
passwordAlg crypto.HashAlgorithm
pwGenerator crypto.Generator
idGenerator id.Generator
ClientKeySize int
}
type ProjectConfig struct {
es_int.Eventstore
Cache *config.CacheConfig
}
func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*ProjectEventstore, error) {
projectCache, err := StartCache(conf.Cache)
if err != nil {
return nil, err
}
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost)
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
return &ProjectEventstore{
Eventstore: conf.Eventstore,
projectCache: projectCache,
passwordAlg: passwordAlg,
pwGenerator: pwGenerator,
idGenerator: id.SonyFlakeGenerator,
ClientKeySize: int(systemDefaults.SecretGenerators.ClientKeySize),
}, nil
}
func (es *ProjectEventstore) ProjectByID(ctx context.Context, id string) (*proj_model.Project, error) {
project := es.projectCache.getProject(id)
query, err := ProjectByIDQuery(project.AggregateID, project.Sequence)
if err != nil {
return nil, err
}
err = es_sdk.Filter(ctx, es.FilterEvents, project.AppendEvents, query)
if err != nil && !(caos_errs.IsNotFound(err) && project.Sequence != 0) {
return nil, err
}
if project.State == int32(proj_model.ProjectStateRemoved) {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dG8ie", "Errors.Project.NotFound")
}
es.projectCache.cacheProject(project)
return model.ProjectToModel(project), nil
}
func (es *ProjectEventstore) ProjectEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
query, err := ProjectByIDQuery(id, sequence)
if err != nil {
return nil, err
}
return es.FilterEvents(ctx, query)
}
func (es *ProjectEventstore) ProjectChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ProjectChanges, error) {
query := ChangesQuery(id, lastSequence, limit, sortAscending)
events, err := es.Eventstore.FilterEvents(context.Background(), query)
if err != nil {
logging.Log("EVENT-ZRffs").WithError(err).Warn("eventstore unavailable")
return nil, errors.ThrowInternal(err, "EVENT-328b1", "Errors.Internal")
}
if len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-FpQqK", "Errors.Changes.NotFound")
}
changes := make([]*proj_model.ProjectChange, len(events))
for i, event := range events {
creationDate, err := ptypes.TimestampProto(event.CreationDate)
logging.Log("EVENT-qxIR7").OnError(err).Debug("unable to parse timestamp")
change := &proj_model.ProjectChange{
ChangeDate: creationDate,
EventType: event.Type.String(),
ModifierId: event.EditorUser,
Sequence: event.Sequence,
}
if event.Data != nil {
var data interface{}
if strings.Contains(change.EventType, "application") {
data = new(model.Application)
} else {
data = new(model.Project)
}
err = json.Unmarshal(event.Data, data)
logging.Log("EVENT-NCkpN").OnError(err).Debug("unable to unmarshal data")
change.Data = data
}
changes[i] = change
if lastSequence < event.Sequence {
lastSequence = event.Sequence
}
}
return &proj_model.ProjectChanges{
Changes: changes,
LastSequence: lastSequence,
}, nil
}
func ChangesQuery(projectID string, latestSequence, limit uint64, sortAscending bool) *es_models.SearchQuery {
query := es_models.NewSearchQuery().
AggregateTypeFilter(model.ProjectAggregate)
if !sortAscending {
query.OrderDesc()
}
query.LatestSequenceFilter(latestSequence).
AggregateIDFilter(projectID).
SetLimit(limit)
return query
}
func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model.Application) (*proj_model.Application, error) {
if app == nil || !app.IsValid(true) {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9eidw", "Errors.Project.App.Invalid")
}
existingProject, err := es.ProjectByID(ctx, app.AggregateID)
if err != nil {
return nil, err
}
app.AppID, err = es.idGenerator.Next()
if err != nil {
return nil, err
}
var stringPw string
if app.OIDCConfig != nil {
app.OIDCConfig.AppID = app.AppID
err := app.OIDCConfig.GenerateNewClientID(es.idGenerator, existingProject)
if err != nil {
return nil, err
}
stringPw, err = app.OIDCConfig.GenerateClientSecretIfNeeded(es.pwGenerator)
if err != nil {
return nil, err
}
}
if app.APIConfig != nil {
app.APIConfig.AppID = app.AppID
err := app.APIConfig.GenerateNewClientID(es.idGenerator, existingProject)
if err != nil {
return nil, err
}
stringPw, err = app.APIConfig.GenerateClientSecretIfNeeded(es.pwGenerator)
if err != nil {
return nil, err
}
}
repoProject := model.ProjectFromModel(existingProject)
repoApp := model.AppFromModel(app)
addAggregate := ApplicationAddedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoApp)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
converted := model.AppToModel(a)
if converted.OIDCConfig != nil {
converted.OIDCConfig.ClientSecretString = stringPw
converted.OIDCConfig.FillCompliance()
}
if converted.APIConfig != nil {
converted.APIConfig.ClientSecretString = stringPw
}
return converted, nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-GvPct", "Errors.Internal")
}
func (es *ProjectEventstore) ApplicationChanges(ctx context.Context, projectID string, appID string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ApplicationChanges, error) {
query := ChangesQuery(projectID, lastSequence, limit, sortAscending)
events, err := es.Eventstore.FilterEvents(ctx, query)
if err != nil {
logging.Log("EVENT-ZRffs").WithError(err).Warn("eventstore unavailable")
return nil, errors.ThrowInternal(err, "EVENT-sw6Ku", "Errors.Internal")
}
if len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-9IHLP", "Errors.Changes.NotFound")
}
result := make([]*proj_model.ApplicationChange, 0)
for _, event := range events {
if !strings.Contains(event.Type.String(), "application") || event.Data == nil {
continue
}
app := new(model.Application)
err := json.Unmarshal(event.Data, app)
logging.Log("EVENT-GIiKD").OnError(err).Debug("unable to unmarshal data")
if app.AppID != appID {
continue
}
creationDate, err := ptypes.TimestampProto(event.CreationDate)
logging.Log("EVENT-MJzeN").OnError(err).Debug("unable to parse timestamp")
result = append(result, &proj_model.ApplicationChange{
ChangeDate: creationDate,
EventType: event.Type.String(),
ModifierId: event.EditorUser,
Sequence: event.Sequence,
Data: app,
})
if lastSequence < event.Sequence {
lastSequence = event.Sequence
}
}
return &proj_model.ApplicationChanges{
Changes: result,
LastSequence: lastSequence,
}, nil
}
func (es *ProjectEventstore) ChangeAPIConfig(ctx context.Context, config *proj_model.APIConfig) (*proj_model.APIConfig, error) {
if config == nil || !config.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-SDg54", "Errors.Project.APIConfigInvalid")
}
existingProject, err := es.ProjectByID(ctx, config.AggregateID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(config.AppID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Rgu63", "Errors.Project.AppNotExisting")
}
if app.Type != proj_model.AppTypeAPI {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-RHj63", "Errors.Project.AppIsNotAPI")
}
repoProject := model.ProjectFromModel(existingProject)
repoConfig := model.APIConfigFromModel(config)
projectAggregate := APIConfigChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoConfig)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
return model.APIConfigToModel(a.APIConfig), nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-aebn5", "Errors.Internal")
}
func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.OIDCConfig, error) {
if appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-7ue34", "Errors.Project.App.OIDCConfigInvalid")
}
existingProject, err := es.ProjectByID(ctx, projectID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(appID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9odi4", "Errors.Project.App.NotExisting")
}
if app.Type != proj_model.AppTypeOIDC {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dile4", "Errors.Project.App.IsNotOIDC")
}
if app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypeNone || app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypePrivateKeyJWT {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-GDrg2", "Errors.Project.OIDCAuthMethodNoSecret")
}
repoProject := model.ProjectFromModel(existingProject)
stringPw, err := app.OIDCConfig.GenerateNewClientSecret(es.pwGenerator)
if err != nil {
return nil, err
}
projectAggregate := OIDCConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, app.OIDCConfig.ClientSecret)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
config := model.OIDCConfigToModel(a.OIDCConfig)
config.ClientSecretString = stringPw
return config, nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal")
}
func (es *ProjectEventstore) ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.APIConfig, error) {
if appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdfb3", "Errors.Project.APIConfigInvalid")
}
existingProject, err := es.ProjectByID(ctx, projectID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(appID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADbg3", "Errors.Project.AppNotExisting")
}
if app.Type != proj_model.AppTypeAPI {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Ntwqw", "Errors.Project.AppIsNotAPI")
}
if app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypeBasic {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-HW4tw", "Errors.Project.APIAuthMethodNoSecret")
}
repoProject := model.ProjectFromModel(existingProject)
stringPw, err := app.APIConfig.GenerateNewClientSecret(es.pwGenerator)
if err != nil {
return nil, err
}
projectAggregate := APIConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, app.APIConfig.ClientSecret)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
config := model.APIConfigToModel(a.APIConfig)
config.ClientSecretString = stringPw
return config, nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-HBfju", "Errors.Internal")
}
func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, projectID, appID string, secret string) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if appID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-H3RT2", "Errors.Project.RequiredFieldsMissing")
}
existingProject, err := es.ProjectByID(ctx, projectID)
if err != nil {
return err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(appID); app == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-D6hba", "Errors.Project.AppNoExisting")
}
if app.Type != proj_model.AppTypeOIDC {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-huywq", "Errors.Project.App.IsNotOIDC")
}
ctx, spanHash := tracing.NewSpan(ctx)
err = crypto.CompareHash(app.OIDCConfig.ClientSecret, []byte(secret), es.passwordAlg)
spanHash.EndWithError(err)
if err == nil {
err = es.setOIDCClientSecretCheckResult(ctx, existingProject, app.AppID, OIDCClientSecretCheckSucceededAggregate)
logging.Log("EVENT-AE1vf").OnError(err).Warn("could not push event OIDCClientSecretCheckSucceeded")
return nil
}
err = es.setOIDCClientSecretCheckResult(ctx, existingProject, app.AppID, OIDCClientSecretCheckFailedAggregate)
logging.Log("EVENT-GD1gh").OnError(err).Warn("could not push event OIDCClientSecretCheckFailed")
return caos_errs.ThrowInvalidArgument(nil, "EVENT-wg24q", "Errors.Project.OIDCSecretInvalid")
}
func (es *ProjectEventstore) setOIDCClientSecretCheckResult(ctx context.Context, project *proj_model.Project, appID string, check func(*es_models.AggregateCreator, *model.Project, string) es_sdk.AggregateFunc) error {
repoProject := model.ProjectFromModel(project)
agg := check(es.AggregateCreator(), repoProject, appID)
err := es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
if err != nil {
return err
}
es.projectCache.cacheProject(repoProject)
return nil
}
func (es *ProjectEventstore) AddClientKey(ctx context.Context, key *proj_model.ClientKey) (*proj_model.ClientKey, error) {
existingProject, err := es.ProjectByID(ctx, key.AggregateID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(key.ApplicationID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dbf32", "Errors.Project.AppNoExisting")
}
if (app.OIDCConfig == nil || app.OIDCConfig != nil && app.OIDCConfig.AuthMethodType != proj_model.OIDCAuthMethodTypePrivateKeyJWT) &&
(app.APIConfig == nil || app.APIConfig != nil && app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypePrivateKeyJWT) {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dff54", "Errors.Project.AuthMethodNoPrivateKeyJWT")
}
if app.OIDCConfig != nil {
key.ClientID = app.OIDCConfig.ClientID
}
if app.APIConfig != nil {
key.ClientID = app.APIConfig.ClientID
}
key.KeyID, err = es.idGenerator.Next()
if err != nil {
return nil, err
}
if key.ExpirationDate.IsZero() {
key.ExpirationDate, err = key_model.DefaultExpiration()
if err != nil {
logging.Log("EVENT-Adgf2").WithError(err).Warn("unable to set default date")
return nil, errors.ThrowInternal(err, "EVENT-j68fg", "Errors.Internal")
}
}
if key.ExpirationDate.Before(time.Now()) {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-C6YV5", "Errors.MachineKey.ExpireBeforeNow")
}
repoProject := model.ProjectFromModel(existingProject)
repoKey := model.ClientKeyFromModel(key)
err = repoKey.GenerateClientKeyPair(es.ClientKeySize)
if err != nil {
return nil, err
}
agg := OIDCApplicationKeyAddedAggregate(es.AggregateCreator(), repoProject, repoKey)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
return model.ClientKeyToModel(repoKey), nil
}
func (es *ProjectEventstore) RemoveApplicationKey(ctx context.Context, projectID, applicationID, keyID string) error {
existingProject, err := es.ProjectByID(ctx, projectID)
if err != nil {
return err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(applicationID); app == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADfzz", "Errors.Project.AppNotExisting")
}
if app.Type != proj_model.AppTypeOIDC {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADffh", "Errors.Project.AppIsNotOIDC")
}
if _, key := app.GetKey(keyID); key == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-D2Sff", "Errors.Project.AppKeyNotExisting")
}
repoProject := model.ProjectFromModel(existingProject)
agg := OIDCApplicationKeyRemovedAggregate(es.AggregateCreator(), repoProject, keyID)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
if err != nil {
return err
}
es.projectCache.cacheProject(repoProject)
return nil
}