mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:17:23 +00:00
feat: add apis and application keys (#1327)
* feat: add apis and application keys * VerifyOIDCClientSecret * Update internal/v2/repository/project/api_config.go Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * Update internal/v2/repository/project/key.go Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * fix append ApplicationKeyWriteModel Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
parent
a7cc57822b
commit
2ba56595b1
@ -45,7 +45,7 @@ SystemDefaults:
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
MachineKeySize: 2048
|
||||
ClientKeySize: 2048
|
||||
ApplicationKeySize: 2048
|
||||
Multifactors:
|
||||
OTP:
|
||||
Issuer: 'ZITADEL'
|
||||
|
@ -2,6 +2,7 @@ package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
@ -32,13 +33,15 @@ func (s *Server) CreateOIDCApplication(ctx context.Context, in *management.OIDCA
|
||||
}
|
||||
return oidcAppFromDomain(app), nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateAPIApplication(ctx context.Context, in *management.APIApplicationCreate) (*management.Application, error) {
|
||||
app, err := s.project.AddApplication(ctx, apiAppCreateToModel(in))
|
||||
app, err := s.command.AddAPIApplication(ctx, apiAppCreateToModel(in), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appFromModel(app), nil
|
||||
return apiAppFromDomain(app), nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateApplication(ctx context.Context, in *management.ApplicationUpdate) (*management.Application, error) {
|
||||
app, err := s.command.ChangeApplication(ctx, in.ProjectId, appUpdateToDomain(in), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
@ -46,10 +49,12 @@ func (s *Server) UpdateApplication(ctx context.Context, in *management.Applicati
|
||||
}
|
||||
return appFromDomain(app), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateApplication(ctx context.Context, in *management.ApplicationID) (*empty.Empty, error) {
|
||||
err := s.command.DeactivateApplication(ctx, in.ProjectId, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateApplication(ctx context.Context, in *management.ApplicationID) (*empty.Empty, error) {
|
||||
err := s.command.ReactivateApplication(ctx, in.ProjectId, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
@ -69,15 +74,15 @@ func (s *Server) UpdateApplicationOIDCConfig(ctx context.Context, in *management
|
||||
}
|
||||
|
||||
func (s *Server) UpdateApplicationAPIConfig(ctx context.Context, in *management.APIConfigUpdate) (*management.APIConfig, error) {
|
||||
config, err := s.project.ChangeAPIConfig(ctx, apiConfigUpdateToModel(in))
|
||||
config, err := s.command.ChangeAPIApplication(ctx, apiConfigUpdateToDomain(in), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apiConfigFromModel(config), nil
|
||||
return apiConfigFromDomain(config), nil
|
||||
}
|
||||
|
||||
func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, in *management.ApplicationID) (*management.ClientSecret, error) {
|
||||
config, err := s.command.ChangeOIDCApplicationSecret(ctx, in.ProjectId, in.Id, authz.GetCtxData(ctx).ResourceOwner)
|
||||
config, err := s.command.ChangeOIDCApplicationSecret(ctx, in.ProjectId, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -85,7 +90,7 @@ func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, in *management.
|
||||
}
|
||||
|
||||
func (s *Server) RegenerateAPIClientSecret(ctx context.Context, in *management.ApplicationID) (*management.ClientSecret, error) {
|
||||
config, err := s.project.ChangeAPIConfigSecret(ctx, in.ProjectId, in.Id)
|
||||
config, err := s.command.ChangeAPIApplicationSecret(ctx, in.ProjectId, in.Id, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -117,14 +122,14 @@ func (s *Server) GetClientKey(ctx context.Context, req *management.ClientKeyIDRe
|
||||
}
|
||||
|
||||
func (s *Server) AddClientKey(ctx context.Context, req *management.AddClientKeyRequest) (*management.AddClientKeyResponse, error) {
|
||||
key, err := s.project.AddClientKey(ctx, addClientKeyToModel(req))
|
||||
key, err := s.command.AddApplicationKey(ctx, addClientKeyToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addClientKeyFromModel(key), nil
|
||||
return addClientKeyFromDomain(key), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteClientKey(ctx context.Context, req *management.ClientKeyIDRequest) (*empty.Empty, error) {
|
||||
err := s.project.RemoveClientKey(ctx, req.ProjectId, req.ApplicationId, req.KeyId)
|
||||
err := s.command.RemoveApplicationKey(ctx, req.ProjectId, req.ApplicationId, req.KeyId, authz.GetCtxData(ctx).OrgID)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
@ -61,11 +61,26 @@ func oidcAppFromDomain(app *domain.OIDCApp) *management.Application {
|
||||
}
|
||||
}
|
||||
|
||||
func apiAppFromDomain(app *domain.APIApp) *management.Application {
|
||||
return &management.Application{
|
||||
Id: app.AppID,
|
||||
State: appStateFromDomain(app.State),
|
||||
ChangeDate: timestamppb.New(app.ChangeDate),
|
||||
Name: app.AppName,
|
||||
Sequence: app.Sequence,
|
||||
AppConfig: apiAppConfigFromDomain(app),
|
||||
}
|
||||
}
|
||||
|
||||
func oidcAppConfigFromDomain(app *domain.OIDCApp) management.AppConfig {
|
||||
return &management.Application_OidcConfig{
|
||||
OidcConfig: oidcConfigFromDomain(app),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func apiAppConfigFromDomain(app *domain.APIApp) management.AppConfig {
|
||||
return &management.Application_ApiConfig{
|
||||
ApiConfig: apiConfigFromDomain(app),
|
||||
}
|
||||
}
|
||||
|
||||
func oidcConfigFromDomain(config *domain.OIDCApp) *management.OIDCConfig {
|
||||
@ -90,6 +105,14 @@ func oidcConfigFromDomain(config *domain.OIDCApp) *management.OIDCConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func apiConfigFromDomain(config *domain.APIApp) *management.APIConfig {
|
||||
return &management.APIConfig{
|
||||
ClientId: config.ClientID,
|
||||
ClientSecret: config.ClientSecretString,
|
||||
AuthMethodType: apiAuthMethodTypeFromDomain(config.AuthMethodType),
|
||||
}
|
||||
}
|
||||
|
||||
func apiConfigFromModel(config *proj_model.APIConfig) *management.APIConfig {
|
||||
return &management.APIConfig{
|
||||
ClientId: config.ClientID,
|
||||
@ -150,6 +173,16 @@ func oidcAppCreateToDomain(app *management.OIDCApplicationCreate) *domain.OIDCAp
|
||||
}
|
||||
}
|
||||
|
||||
func apiAppCreateToModel(app *management.APIApplicationCreate) *domain.APIApp {
|
||||
return &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: app.ProjectId,
|
||||
},
|
||||
AppName: app.Name,
|
||||
AuthMethodType: apiAuthMethodTypeToDomain(app.AuthMethodType),
|
||||
}
|
||||
}
|
||||
|
||||
func appUpdateToDomain(app *management.ApplicationUpdate) domain.Application {
|
||||
return &domain.ChangeApp{
|
||||
AppID: app.Id,
|
||||
@ -157,29 +190,6 @@ func appUpdateToDomain(app *management.ApplicationUpdate) domain.Application {
|
||||
}
|
||||
}
|
||||
|
||||
func apiAppCreateToModel(app *management.APIApplicationCreate) *proj_model.Application {
|
||||
return &proj_model.Application{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: app.ProjectId,
|
||||
},
|
||||
Name: app.Name,
|
||||
Type: proj_model.AppTypeAPI,
|
||||
APIConfig: &proj_model.APIConfig{
|
||||
AuthMethodType: apiAuthMethodTypeToModel(app.AuthMethodType),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func appUpdateToModel(app *management.ApplicationUpdate) *proj_model.Application {
|
||||
return &proj_model.Application{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: app.ProjectId,
|
||||
},
|
||||
AppID: app.Id,
|
||||
Name: app.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func oidcConfigUpdateToDomain(app *management.OIDCConfigUpdate) *domain.OIDCApp {
|
||||
return &domain.OIDCApp{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
@ -201,13 +211,43 @@ func oidcConfigUpdateToDomain(app *management.OIDCConfigUpdate) *domain.OIDCApp
|
||||
}
|
||||
}
|
||||
|
||||
func apiConfigUpdateToModel(app *management.APIConfigUpdate) *proj_model.APIConfig {
|
||||
return &proj_model.APIConfig{
|
||||
func apiConfigUpdateToDomain(app *management.APIConfigUpdate) *domain.APIApp {
|
||||
return &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: app.ProjectId,
|
||||
},
|
||||
AppID: app.ApplicationId,
|
||||
AuthMethodType: apiAuthMethodTypeToModel(app.AuthMethodType),
|
||||
AuthMethodType: apiAuthMethodTypeToDomain(app.AuthMethodType),
|
||||
}
|
||||
}
|
||||
|
||||
func addClientKeyToDomain(key *management.AddClientKeyRequest) *domain.ApplicationKey {
|
||||
expirationDate := time.Time{}
|
||||
if key.ExpirationDate != nil {
|
||||
expirationDate = key.ExpirationDate.AsTime()
|
||||
}
|
||||
|
||||
return &domain.ApplicationKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: key.ProjectId,
|
||||
},
|
||||
ExpirationDate: expirationDate,
|
||||
Type: authNKeyTypeToDomain(key.Type),
|
||||
ApplicationID: key.ApplicationId,
|
||||
}
|
||||
}
|
||||
|
||||
func addClientKeyFromDomain(key *domain.ApplicationKey) *management.AddClientKeyResponse {
|
||||
detail, err := key.Detail()
|
||||
logging.Log("MANAG-adt42").OnError(err).Warn("unable to marshal key")
|
||||
|
||||
return &management.AddClientKeyResponse{
|
||||
Id: key.KeyID,
|
||||
CreationDate: timestamppb.New(key.CreationDate),
|
||||
ExpirationDate: timestamppb.New(key.ExpirationDate),
|
||||
Sequence: key.Sequence,
|
||||
KeyDetails: detail,
|
||||
Type: authNKeyTypeFromDomain(key.Type),
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,19 +525,32 @@ func oidcAuthMethodTypeFromDomain(authType domain.OIDCAuthMethodType) management
|
||||
return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_POST
|
||||
case domain.OIDCAuthMethodTypeNone:
|
||||
return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_NONE
|
||||
case domain.OIDCAuthMethodTypePrivateKeyJWT:
|
||||
return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_PRIVATE_KEY_JWT
|
||||
default:
|
||||
return management.OIDCAuthMethodType_OIDCAUTHMETHODTYPE_BASIC
|
||||
}
|
||||
}
|
||||
|
||||
func apiAuthMethodTypeToModel(authType management.APIAuthMethodType) proj_model.APIAuthMethodType {
|
||||
func apiAuthMethodTypeToDomain(authType management.APIAuthMethodType) domain.APIAuthMethodType {
|
||||
switch authType {
|
||||
case management.APIAuthMethodType_APIAUTHMETHODTYPE_BASIC:
|
||||
return proj_model.APIAuthMethodTypeBasic
|
||||
return domain.APIAuthMethodTypeBasic
|
||||
case management.APIAuthMethodType_APIAUTHMETHODTYPE_PRIVATE_KEY_JWT:
|
||||
return proj_model.APIAuthMethodTypePrivateKeyJWT
|
||||
return domain.APIAuthMethodTypePrivateKeyJWT
|
||||
default:
|
||||
return proj_model.APIAuthMethodTypeBasic
|
||||
return domain.APIAuthMethodTypeBasic
|
||||
}
|
||||
}
|
||||
|
||||
func apiAuthMethodTypeFromDomain(authType domain.APIAuthMethodType) management.APIAuthMethodType {
|
||||
switch authType {
|
||||
case domain.APIAuthMethodTypeBasic:
|
||||
return management.APIAuthMethodType_APIAUTHMETHODTYPE_BASIC
|
||||
case domain.APIAuthMethodTypePrivateKeyJWT:
|
||||
return management.APIAuthMethodType_APIAUTHMETHODTYPE_PRIVATE_KEY_JWT
|
||||
default:
|
||||
return management.APIAuthMethodType_APIAUTHMETHODTYPE_BASIC
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,6 +611,24 @@ func oidcVersionFromDomain(version domain.OIDCVersion) management.OIDCVersion {
|
||||
}
|
||||
}
|
||||
|
||||
func authNKeyTypeToDomain(keyType management.AuthNKeyType) domain.AuthNKeyType {
|
||||
switch keyType {
|
||||
case management.AuthNKeyType_AUTHNKEY_JSON:
|
||||
return domain.AuthNKeyTypeJSON
|
||||
default:
|
||||
return domain.AuthNKeyTypeNONE
|
||||
}
|
||||
}
|
||||
|
||||
func authNKeyTypeFromDomain(typ domain.AuthNKeyType) management.AuthNKeyType {
|
||||
switch typ {
|
||||
case domain.AuthNKeyTypeJSON:
|
||||
return management.AuthNKeyType_AUTHNKEY_JSON
|
||||
default:
|
||||
return management.AuthNKeyType_AUTHNKEY_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func appChangesToResponse(response *proj_model.ApplicationChanges, offset uint64, limit uint64) (_ *management.Changes) {
|
||||
return &management.Changes{
|
||||
Limit: limit,
|
||||
@ -612,63 +683,6 @@ func clientKeyViewFromModel(key *key_model.AuthNKeyView) *management.ClientKeyVi
|
||||
}
|
||||
}
|
||||
|
||||
func addClientKeyToModel(key *management.AddClientKeyRequest) *proj_model.ClientKey {
|
||||
expirationDate := time.Time{}
|
||||
if key.ExpirationDate != nil {
|
||||
var err error
|
||||
expirationDate, err = ptypes.Timestamp(key.ExpirationDate)
|
||||
logging.Log("MANAG-Dgt42").OnError(err).Debug("unable to parse expiration date")
|
||||
}
|
||||
|
||||
return &proj_model.ClientKey{
|
||||
ExpirationDate: expirationDate,
|
||||
Type: authNKeyTypeToModel(key.Type),
|
||||
ApplicationID: key.ApplicationId,
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: key.ProjectId},
|
||||
}
|
||||
}
|
||||
|
||||
func addClientKeyFromModel(key *proj_model.ClientKey) *management.AddClientKeyResponse {
|
||||
creationDate, err := ptypes.TimestampProto(key.CreationDate)
|
||||
logging.Log("MANAG-FBzz4").OnError(err).Debug("unable to parse cretaion date")
|
||||
|
||||
expirationDate, err := ptypes.TimestampProto(key.ExpirationDate)
|
||||
logging.Log("MANAG-sag21").OnError(err).Debug("unable to parse cretaion date")
|
||||
|
||||
detail, err := json.Marshal(struct {
|
||||
Type string `json:"type"`
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
AppID string `json:"appId"`
|
||||
ClientID string `json:"clientID"`
|
||||
}{
|
||||
Type: "application",
|
||||
KeyID: key.KeyID,
|
||||
Key: string(key.PrivateKey),
|
||||
AppID: key.ApplicationID,
|
||||
ClientID: key.ClientID,
|
||||
})
|
||||
logging.Log("MANAG-adt42").OnError(err).Warn("unable to marshall key")
|
||||
|
||||
return &management.AddClientKeyResponse{
|
||||
Id: key.KeyID,
|
||||
CreationDate: creationDate,
|
||||
ExpirationDate: expirationDate,
|
||||
Sequence: key.Sequence,
|
||||
KeyDetails: detail,
|
||||
Type: authNKeyTypeFromModel(key.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func authNKeyTypeToModel(typ management.AuthNKeyType) key_model.AuthNKeyType {
|
||||
switch typ {
|
||||
case management.AuthNKeyType_AUTHNKEY_JSON:
|
||||
return key_model.AuthNKeyTypeJSON
|
||||
default:
|
||||
return key_model.AuthNKeyTypeNONE
|
||||
}
|
||||
}
|
||||
|
||||
func authNKeyTypeFromModel(typ key_model.AuthNKeyType) management.AuthNKeyType {
|
||||
switch typ {
|
||||
case key_model.AuthNKeyTypeJSON:
|
||||
|
@ -116,18 +116,18 @@ func addMachineKeyFromDomain(key *domain.MachineKey) *management.AddMachineKeyRe
|
||||
}
|
||||
}
|
||||
|
||||
func machineKeyTypeToDomain(typ management.MachineKeyType) domain.MachineKeyType {
|
||||
func machineKeyTypeToDomain(typ management.MachineKeyType) domain.AuthNKeyType {
|
||||
switch typ {
|
||||
case management.MachineKeyType_MACHINEKEY_JSON:
|
||||
return domain.MachineKeyTypeJSON
|
||||
return domain.AuthNKeyTypeJSON
|
||||
default:
|
||||
return domain.MachineKeyTypeNONE
|
||||
return domain.AuthNKeyTypeNONE
|
||||
}
|
||||
}
|
||||
|
||||
func machineKeyTypeFromDomain(typ domain.MachineKeyType) management.MachineKeyType {
|
||||
func machineKeyTypeFromDomain(typ domain.AuthNKeyType) management.MachineKeyType {
|
||||
switch typ {
|
||||
case domain.MachineKeyTypeJSON:
|
||||
case domain.AuthNKeyTypeJSON:
|
||||
return management.MachineKeyType_MACHINEKEY_JSON
|
||||
default:
|
||||
return management.MachineKeyType_MACHINEKEY_UNSPECIFIED
|
||||
|
@ -8,9 +8,11 @@ import (
|
||||
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"github.com/caos/zitadel/internal/v2/command"
|
||||
)
|
||||
|
||||
type ApplicationRepo struct {
|
||||
Commands *command.CommandSide
|
||||
View *view.View
|
||||
ProjectEvents *proj_event.ProjectEventstore
|
||||
}
|
||||
@ -31,5 +33,5 @@ func (a *ApplicationRepo) AuthorizeOIDCApplication(ctx context.Context, clientID
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.ProjectEvents.VerifyOIDCClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||
return a.Commands.VerifyOIDCClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||
}
|
||||
|
@ -158,6 +158,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
SigningKeyRotation: systemDefaults.KeyConfig.SigningKeyRotation.Duration,
|
||||
},
|
||||
eventstore.ApplicationRepo{
|
||||
Commands: command,
|
||||
View: view,
|
||||
ProjectEvents: project,
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ type SecretGenerators struct {
|
||||
PhoneVerificationCode crypto.GeneratorConfig
|
||||
PasswordVerificationCode crypto.GeneratorConfig
|
||||
MachineKeySize uint32
|
||||
ClientKeySize uint32
|
||||
ApplicationKeySize uint32
|
||||
}
|
||||
|
||||
type MultifactorConfig struct {
|
||||
|
@ -227,10 +227,6 @@ func (repo *ProjectRepo) ApplicationByID(ctx context.Context, projectID, appID s
|
||||
return model.ApplicationViewToModel(app), nil
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) AddApplication(ctx context.Context, app *proj_model.Application) (*proj_model.Application, error) {
|
||||
return repo.ProjectEvents.AddApplication(ctx, app)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) SearchApplications(ctx context.Context, request *proj_model.ApplicationSearchRequest) (*proj_model.ApplicationSearchResponse, error) {
|
||||
request.EnsureLimit(repo.SearchLimit)
|
||||
sequence, sequenceErr := repo.View.GetLatestApplicationSequence()
|
||||
@ -322,22 +318,6 @@ func (repo *ProjectRepo) GetClientKey(ctx context.Context, projectID, applicatio
|
||||
return key_view_model.AuthNKeyToModel(key), nil
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) AddClientKey(ctx context.Context, key *proj_model.ClientKey) (*proj_model.ClientKey, error) {
|
||||
return repo.ProjectEvents.AddClientKey(ctx, key)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) RemoveClientKey(ctx context.Context, projectID, applicationID, keyID string) error {
|
||||
return repo.ProjectEvents.RemoveApplicationKey(ctx, projectID, applicationID, keyID)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ChangeAPIConfig(ctx context.Context, config *proj_model.APIConfig) (*proj_model.APIConfig, error) {
|
||||
return repo.ProjectEvents.ChangeAPIConfig(ctx, config)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.APIConfig, error) {
|
||||
return repo.ProjectEvents.ChangeAPIConfigSecret(ctx, projectID, appID)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ProjectGrantByID(ctx context.Context, grantID string) (*proj_model.ProjectGrantView, error) {
|
||||
grant, err := repo.View.ProjectGrantByID(grantID)
|
||||
if err != nil {
|
||||
|
@ -24,9 +24,6 @@ type ProjectRepository interface {
|
||||
ProjectChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*model.ProjectChanges, error)
|
||||
|
||||
ApplicationByID(ctx context.Context, projectID, appID string) (*model.ApplicationView, error)
|
||||
AddApplication(ctx context.Context, app *model.Application) (*model.Application, error)
|
||||
ChangeAPIConfig(ctx context.Context, config *model.APIConfig) (*model.APIConfig, error)
|
||||
ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*model.APIConfig, error)
|
||||
SearchApplications(ctx context.Context, request *model.ApplicationSearchRequest) (*model.ApplicationSearchResponse, error)
|
||||
ApplicationChanges(ctx context.Context, id string, secId string, lastSequence uint64, limit uint64, sortAscending bool) (*model.ApplicationChanges, error)
|
||||
SearchClientKeys(ctx context.Context, request *key_model.AuthNKeySearchRequest) (*key_model.AuthNKeySearchResponse, error)
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
@ -18,10 +17,8 @@ import (
|
||||
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 (
|
||||
@ -56,7 +53,7 @@ func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*Projec
|
||||
passwordAlg: passwordAlg,
|
||||
pwGenerator: pwGenerator,
|
||||
idGenerator: id.SonyFlakeGenerator,
|
||||
ClientKeySize: int(systemDefaults.SecretGenerators.ClientKeySize),
|
||||
ClientKeySize: int(systemDefaults.SecretGenerators.ApplicationKeySize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -147,63 +144,19 @@ func ChangesQuery(projectID string, latestSequence, limit uint64, sortAscending
|
||||
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")
|
||||
func (es *ProjectEventstore) ApplicationByIDs(ctx context.Context, projectID, appID string) (*proj_model.Application, error) {
|
||||
if projectID == "" || appID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ld93d", "Errors.Project.IDMissing")
|
||||
}
|
||||
existingProject, err := es.ProjectByID(ctx, app.AggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app.AppID, err = es.idGenerator.Next()
|
||||
project, err := es.ProjectByID(ctx, projectID)
|
||||
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 _, a := project.GetApp(appID); a != nil {
|
||||
return a, nil
|
||||
}
|
||||
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")
|
||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-8ei2s", "Errors.Project.App.NotFound")
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) ApplicationChanges(ctx context.Context, projectID string, appID string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ApplicationChanges, error) {
|
||||
@ -251,232 +204,3 @@ func (es *ProjectEventstore) ApplicationChanges(ctx context.Context, projectID s
|
||||
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
|
||||
}
|
||||
|
@ -197,6 +197,7 @@ Errors:
|
||||
OIDCAuthMethodNoSecret: Gewählte OIDC Auth Method benötigt kein Secret
|
||||
APIAuthMethodNoSecret: Gewählte API Auth Method benötigt kein Secret
|
||||
AuthMethodNoPrivateKeyJWT: Gewählte Auth Method benötigt keinen Key
|
||||
OIDCSecretInvalid: Client Secret ist ungültig
|
||||
RequiredFieldsMissing: Benötigte Felder fehlen
|
||||
Grant:
|
||||
AlreadyExists: Projekt Grant existiert bereits
|
||||
@ -207,7 +208,6 @@ Errors:
|
||||
NotActive: Projekt Grant ist nicht aktiv
|
||||
NotInactive: Projekt Grant ist nicht inaktiv
|
||||
UserIDMisisng: User ID fehlt
|
||||
OIDCSecretInvalid: Client Secret ist ungültig
|
||||
IAM:
|
||||
Member:
|
||||
RolesNotChanged: Rollen wurden nicht verändert
|
||||
|
@ -185,9 +185,9 @@ Errors:
|
||||
IDMissing: ID missing
|
||||
App:
|
||||
AlreadyExists: Application already exists
|
||||
NotFound: Application not found
|
||||
Invalid: Application invalid
|
||||
NotExisting: Application doesn't exist
|
||||
IsNotOIDC: Application is not type oidc
|
||||
NotActive:: Application is not active
|
||||
NotInactive: Application is not inactive
|
||||
OIDCConfigInvalid: OIDC configuration is invalid
|
||||
@ -197,6 +197,7 @@ Errors:
|
||||
OIDCAuthMethodNoSecret: Chosen OIDC Auth Method does not require a secret
|
||||
APIAuthMethodNoSecret: Chosen API Auth Method does not require a secret
|
||||
AuthMethodNoPrivateKeyJWT: Chosen Auth Method does not require a key
|
||||
OIDCSecretInvalid: Client Secret is invalid
|
||||
RequiredFieldsMissing: Some required fields are missing
|
||||
Grant:
|
||||
AlreadyExists: Project grant already exists
|
||||
@ -207,7 +208,6 @@ Errors:
|
||||
NotActive: Project grant is not active
|
||||
NotInactive: Project grant is not inactive
|
||||
UserIDMisisng: User ID missing
|
||||
OIDCSecretInvalid: Client Secret is invalid
|
||||
IAM:
|
||||
Member:
|
||||
RolesNotChanged: Roles habe not been changed
|
||||
|
@ -34,14 +34,15 @@ type CommandSide struct {
|
||||
passwordVerificationCode crypto.Generator
|
||||
machineKeyAlg crypto.EncryptionAlgorithm
|
||||
machineKeySize int
|
||||
applicationKeySize int
|
||||
applicationSecretGenerator crypto.Generator
|
||||
domainVerificationAlg *crypto.AESCrypto
|
||||
domainVerificationGenerator crypto.Generator
|
||||
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
|
||||
//TODO: remove global model, or move to domain
|
||||
multifactors global_model.Multifactors
|
||||
webauthn *webauthn_helper.WebAuthN
|
||||
|
||||
webauthn *webauthn_helper.WebAuthN
|
||||
keySize int
|
||||
keyAlgorithm crypto.EncryptionAlgorithm
|
||||
privateKeyLifetime time.Duration
|
||||
@ -85,6 +86,7 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) {
|
||||
repo.userPasswordAlg = crypto.NewBCrypt(config.SystemDefaults.SecretGenerators.PasswordSaltCost)
|
||||
repo.machineKeyAlg = userEncryptionAlgorithm
|
||||
repo.machineKeySize = int(config.SystemDefaults.SecretGenerators.MachineKeySize)
|
||||
repo.applicationKeySize = int(config.SystemDefaults.SecretGenerators.ApplicationKeySize)
|
||||
|
||||
aesOTPCrypto, err := crypto.NewAESCrypto(config.SystemDefaults.Multifactors.OTP.VerificationKey)
|
||||
if err != nil {
|
||||
|
145
internal/v2/command/project_application_api.go
Normal file
145
internal/v2/command/project_application_api.go
Normal file
@ -0,0 +1,145 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/project"
|
||||
)
|
||||
|
||||
func (r *CommandSide) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) {
|
||||
project, err := r.getProjectByID(ctx, application.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
||||
events, stringPw, err := r.addAPIApplication(ctx, projectAgg, project, application, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedApplication.AppID = application.AppID
|
||||
pushedEvents, err := r.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(addedApplication, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := apiWriteModelToAPIConfig(addedApplication)
|
||||
result.ClientSecretString = stringPw
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string) (events []eventstore.EventPusher, stringPW string, err error) {
|
||||
if !apiAppApp.IsValid() {
|
||||
return nil, "", caos_errs.ThrowPreconditionFailed(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
|
||||
}
|
||||
apiAppApp.AppID, err = r.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
events = []eventstore.EventPusher{
|
||||
project.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName, resourceOwner),
|
||||
}
|
||||
|
||||
var stringPw string
|
||||
err = domain.SetNewClientID(apiAppApp, r.idGenerator, proj)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
stringPw, err = domain.SetNewClientSecretIfNeeded(apiAppApp, r.applicationSecretGenerator)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
events = append(events, project.NewAPIConfigAddedEvent(ctx,
|
||||
projectAgg,
|
||||
apiAppApp.AppID,
|
||||
apiAppApp.ClientID,
|
||||
apiAppApp.ClientSecret,
|
||||
apiAppApp.AuthMethodType))
|
||||
|
||||
return events, stringPw, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
|
||||
if !apiApp.IsValid() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
|
||||
}
|
||||
|
||||
existingAPI, err := r.getAPIAppWriteModel(ctx, apiApp.AggregateID, apiApp.AppID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2n8uU", "Errors.Project.App.NotExisting")
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
||||
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
|
||||
ctx,
|
||||
projectAgg,
|
||||
apiApp.AppID,
|
||||
apiApp.AuthMethodType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1m88i", "Errors.NoChangesFound")
|
||||
}
|
||||
|
||||
pushedEvents, err := r.eventstore.PushEvents(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingAPI, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := apiWriteModelToAPIConfig(existingAPI)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.APIApp, error) {
|
||||
if projectID == "" || appID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-99i83", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingAPI, err := r.getAPIAppWriteModel(ctx, projectID, appID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2g66f", "Errors.Project.App.NotExisting")
|
||||
}
|
||||
cryptoSecret, stringPW, err := domain.NewClientSecret(r.applicationSecretGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
||||
|
||||
pushedEvents, err := r.eventstore.PushEvents(ctx, project.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingAPI, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := apiWriteModelToAPIConfig(existingAPI)
|
||||
result.ClientSecretString = stringPW
|
||||
return result, err
|
||||
}
|
||||
func (r *CommandSide) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) {
|
||||
appWriteModel := NewAPIApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, appWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appWriteModel, nil
|
||||
}
|
173
internal/v2/command/project_application_api_model.go
Normal file
173
internal/v2/command/project_application_api_model.go
Normal file
@ -0,0 +1,173 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/project"
|
||||
)
|
||||
|
||||
type APIApplicationWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
AppID string
|
||||
AppName string
|
||||
ClientID string
|
||||
ClientSecret *crypto.CryptoValue
|
||||
ClientSecretString string
|
||||
AuthMethodType domain.APIAuthMethodType
|
||||
State domain.AppState
|
||||
}
|
||||
|
||||
func NewAPIApplicationWriteModelWithAppID(projectID, appID, resourceOwner string) *APIApplicationWriteModel {
|
||||
return &APIApplicationWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: projectID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
AppID: appID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAPIApplicationWriteModel(projectID, resourceOwner string) *APIApplicationWriteModel {
|
||||
return &APIApplicationWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: projectID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
func (wm *APIApplicationWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *project.ApplicationAddedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ApplicationChangedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ApplicationDeactivatedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ApplicationReactivatedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ApplicationRemovedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.APIConfigAddedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.APIConfigChangedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.APIConfigSecretChangedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ProjectRemovedEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *APIApplicationWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *project.ApplicationAddedEvent:
|
||||
wm.AppName = e.Name
|
||||
wm.State = domain.AppStateActive
|
||||
case *project.ApplicationChangedEvent:
|
||||
wm.AppName = e.Name
|
||||
case *project.ApplicationDeactivatedEvent:
|
||||
if wm.State == domain.AppStateRemoved {
|
||||
continue
|
||||
}
|
||||
wm.State = domain.AppStateInactive
|
||||
case *project.ApplicationReactivatedEvent:
|
||||
if wm.State == domain.AppStateRemoved {
|
||||
continue
|
||||
}
|
||||
wm.State = domain.AppStateActive
|
||||
case *project.ApplicationRemovedEvent:
|
||||
wm.State = domain.AppStateRemoved
|
||||
case *project.APIConfigAddedEvent:
|
||||
wm.appendAddAPIEvent(e)
|
||||
case *project.APIConfigChangedEvent:
|
||||
wm.appendChangeAPIEvent(e)
|
||||
case *project.APIConfigSecretChangedEvent:
|
||||
wm.ClientSecret = e.ClientSecret
|
||||
case *project.ProjectRemovedEvent:
|
||||
wm.State = domain.AppStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *APIApplicationWriteModel) appendAddAPIEvent(e *project.APIConfigAddedEvent) {
|
||||
wm.ClientID = e.ClientID
|
||||
wm.ClientSecret = e.ClientSecret
|
||||
wm.AuthMethodType = e.AuthMethodType
|
||||
}
|
||||
|
||||
func (wm *APIApplicationWriteModel) appendChangeAPIEvent(e *project.APIConfigChangedEvent) {
|
||||
if e.AuthMethodType != nil {
|
||||
wm.AuthMethodType = *e.AuthMethodType
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *APIApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, project.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
EventTypes(
|
||||
project.ApplicationAddedType,
|
||||
project.ApplicationChangedType,
|
||||
project.ApplicationDeactivatedType,
|
||||
project.ApplicationReactivatedType,
|
||||
project.ApplicationRemovedType,
|
||||
project.APIConfigAddedType,
|
||||
project.APIConfigChangedType,
|
||||
project.APIConfigSecretChangedType,
|
||||
project.ProjectRemovedType,
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *APIApplicationWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID string,
|
||||
authMethodType domain.APIAuthMethodType,
|
||||
) (*project.APIConfigChangedEvent, bool, error) {
|
||||
changes := make([]project.APIConfigChanges, 0)
|
||||
var err error
|
||||
|
||||
if wm.AuthMethodType != authMethodType {
|
||||
changes = append(changes, project.ChangeAPIAuthMethodType(authMethodType))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
changeEvent, err := project.NewAPIConfigChangedEvent(ctx, aggregate, appID, changes)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return changeEvent, true, nil
|
||||
}
|
77
internal/v2/command/project_application_key.go
Normal file
77
internal/v2/command/project_application_key.go
Normal file
@ -0,0 +1,77 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/project"
|
||||
)
|
||||
|
||||
func (r *CommandSide) AddApplicationKey(ctx context.Context, key *domain.ApplicationKey, resourceOwner string) (_ *domain.ApplicationKey, err error) {
|
||||
application, err := r.getApplicationWriteModel(ctx, key.AggregateID, key.ApplicationID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !application.State.Exists() {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-sak25", "Errors.Application.NotFound")
|
||||
}
|
||||
key.KeyID, err = r.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyWriteModel := NewApplicationKeyWriteModel(key.AggregateID, key.ApplicationID, key.KeyID, resourceOwner)
|
||||
err = r.eventstore.FilterToQueryReducer(ctx, keyWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !keyWriteModel.KeysAllowed {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-Dff54", "Errors.Project.App.AuthMethodNoPrivateKeyJWT")
|
||||
}
|
||||
|
||||
if err := domain.EnsureValidExpirationDate(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = domain.SetNewAuthNKeyPair(key, r.applicationKeySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key.ClientID = keyWriteModel.ClientID
|
||||
|
||||
pushedEvents, err := r.eventstore.PushEvents(ctx,
|
||||
project.NewApplicationKeyAddedEvent(
|
||||
ctx,
|
||||
ProjectAggregateFromWriteModel(&keyWriteModel.WriteModel),
|
||||
key.ApplicationID,
|
||||
key.ClientID,
|
||||
key.KeyID,
|
||||
key.Type,
|
||||
key.ExpirationDate,
|
||||
key.PublicKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(keyWriteModel, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := applicationKeyWriteModelToKey(keyWriteModel, key.PrivateKey)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *CommandSide) RemoveApplicationKey(ctx context.Context, projectID, applicationID, keyID, resourceOwner string) error {
|
||||
keyWriteModel := NewApplicationKeyWriteModel(projectID, applicationID, keyID, resourceOwner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, keyWriteModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !keyWriteModel.State.Exists() {
|
||||
return errors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.Application.Key.NotFound")
|
||||
}
|
||||
|
||||
_, err = r.eventstore.PushEvents(ctx, project.NewApplicationKeyRemovedEvent(ctx, ProjectAggregateFromWriteModel(&keyWriteModel.WriteModel), keyID))
|
||||
return err
|
||||
}
|
141
internal/v2/command/project_application_key_model.go
Normal file
141
internal/v2/command/project_application_key_model.go
Normal file
@ -0,0 +1,141 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/project"
|
||||
)
|
||||
|
||||
type ApplicationKeyWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
AppID string
|
||||
ClientID string
|
||||
KeyID string
|
||||
KeyType domain.AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
|
||||
State domain.AppState
|
||||
KeysAllowed bool
|
||||
}
|
||||
|
||||
func NewApplicationKeyWriteModel(projectID, appID, keyID, resourceOwner string) *ApplicationKeyWriteModel {
|
||||
return &ApplicationKeyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: projectID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
AppID: appID,
|
||||
KeyID: keyID,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *project.ApplicationRemovedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.OIDCConfigAddedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.OIDCConfigChangedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.APIConfigAddedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.APIConfigChangedEvent:
|
||||
if e.AppID != wm.AppID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ApplicationKeyAddedEvent:
|
||||
if e.AppID != wm.AppID || e.KeyID != wm.KeyID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ApplicationKeyRemovedEvent:
|
||||
if e.KeyID != wm.KeyID {
|
||||
continue
|
||||
}
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
case *project.ProjectRemovedEvent:
|
||||
wm.WriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *project.ApplicationRemovedEvent:
|
||||
wm.State = domain.AppStateRemoved
|
||||
case *project.OIDCConfigAddedEvent:
|
||||
wm.appendAddOIDCEvent(e)
|
||||
case *project.OIDCConfigChangedEvent:
|
||||
wm.appendChangeOIDCEvent(e)
|
||||
case *project.APIConfigAddedEvent:
|
||||
wm.appendAddAPIEvent(e)
|
||||
case *project.APIConfigChangedEvent:
|
||||
wm.appendChangeAPIEvent(e)
|
||||
case *project.ApplicationKeyAddedEvent:
|
||||
wm.ClientID = e.ClientID
|
||||
wm.ExpirationDate = e.ExpirationDate
|
||||
wm.KeyType = e.KeyType
|
||||
case *project.ApplicationKeyRemovedEvent:
|
||||
wm.State = domain.AppStateRemoved
|
||||
case *project.ProjectRemovedEvent:
|
||||
wm.State = domain.AppStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAddedEvent) {
|
||||
wm.ClientID = e.ClientID
|
||||
wm.KeysAllowed = e.AuthMethodType == domain.OIDCAuthMethodTypePrivateKeyJWT
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
||||
if e.AuthMethodType != nil {
|
||||
wm.KeysAllowed = *e.AuthMethodType == domain.OIDCAuthMethodTypePrivateKeyJWT
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) appendAddAPIEvent(e *project.APIConfigAddedEvent) {
|
||||
wm.ClientID = e.ClientID
|
||||
wm.KeysAllowed = e.AuthMethodType == domain.APIAuthMethodTypePrivateKeyJWT
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) appendChangeAPIEvent(e *project.APIConfigChangedEvent) {
|
||||
if e.AuthMethodType != nil {
|
||||
wm.KeysAllowed = *e.AuthMethodType == domain.APIAuthMethodTypePrivateKeyJWT
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *ApplicationKeyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, project.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
EventTypes(
|
||||
project.ApplicationRemovedType,
|
||||
project.OIDCConfigAddedType,
|
||||
project.OIDCConfigChangedType,
|
||||
project.APIConfigAddedType,
|
||||
project.APIConfigChangedType,
|
||||
project.ApplicationKeyAddedEventType,
|
||||
project.ApplicationKeyRemovedEventType,
|
||||
project.ProjectRemovedType,
|
||||
)
|
||||
}
|
@ -2,8 +2,13 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
"github.com/caos/zitadel/internal/v2/repository/project"
|
||||
)
|
||||
@ -48,11 +53,11 @@ func (r *CommandSide) addOIDCApplication(ctx context.Context, projectAgg *events
|
||||
}
|
||||
|
||||
var stringPw string
|
||||
err = oidcApp.GenerateNewClientID(r.idGenerator, proj)
|
||||
err = domain.SetNewClientID(oidcApp, r.idGenerator, proj)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
stringPw, err = oidcApp.GenerateClientSecretIfNeeded(r.applicationSecretGenerator)
|
||||
stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, r.applicationSecretGenerator)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -95,7 +100,6 @@ func (r *CommandSide) ChangeOIDCApplication(ctx context.Context, oidc *domain.OI
|
||||
ctx,
|
||||
projectAgg,
|
||||
oidc.AppID,
|
||||
oidc.ClientID,
|
||||
oidc.RedirectUris,
|
||||
oidc.PostLogoutRedirectUris,
|
||||
oidc.ResponseTypes,
|
||||
@ -162,8 +166,34 @@ func (r *CommandSide) ChangeOIDCApplicationSecret(ctx context.Context, projectID
|
||||
result.ClientSecretString = stringPW
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (r *CommandSide) VerifyOIDCClientSecret(ctx context.Context, projectID, appID, secret string) error {
|
||||
app, err := r.getOIDCAppWriteModel(ctx, projectID, appID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !app.State.Exists() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NoExisting")
|
||||
}
|
||||
if app.ClientSecret == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid")
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
|
||||
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
|
||||
err = crypto.CompareHash(app.ClientSecret, []byte(secret), r.userPasswordAlg)
|
||||
spanPasswordComparison.EndWithError(err)
|
||||
if err == nil {
|
||||
_, err = r.eventstore.PushEvents(ctx, project.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
|
||||
return err
|
||||
}
|
||||
_, err = r.eventstore.PushEvents(ctx, project.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
|
||||
logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.OIDCSecretInvalid")
|
||||
}
|
||||
|
||||
func (r *CommandSide) getOIDCAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) {
|
||||
appWriteModel := NewOIDCApplicationWriteModelWithAppIDC(projectID, appID, resourceOwner)
|
||||
appWriteModel := NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
|
||||
err := r.eventstore.FilterToQueryReducer(ctx, appWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -35,7 +35,7 @@ type OIDCApplicationWriteModel struct {
|
||||
State domain.AppState
|
||||
}
|
||||
|
||||
func NewOIDCApplicationWriteModelWithAppIDC(projectID, appID, resourceOwner string) *OIDCApplicationWriteModel {
|
||||
func NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner string) *OIDCApplicationWriteModel {
|
||||
return &OIDCApplicationWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: projectID,
|
||||
@ -154,9 +154,6 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd
|
||||
}
|
||||
|
||||
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
||||
if e.ClientID != nil {
|
||||
wm.ClientID = *e.ClientID
|
||||
}
|
||||
if e.RedirectUris != nil {
|
||||
wm.RedirectUris = *e.RedirectUris
|
||||
}
|
||||
@ -218,8 +215,7 @@ func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID,
|
||||
clientID string,
|
||||
appID string,
|
||||
redirectURIS,
|
||||
postLogoutRedirectURIs []string,
|
||||
responseTypes []domain.OIDCResponseType,
|
||||
@ -237,9 +233,6 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
||||
changes := make([]project.OIDCConfigChanges, 0)
|
||||
var err error
|
||||
|
||||
if wm.ClientID != clientID {
|
||||
changes = append(changes, project.ChangeClientID(clientID))
|
||||
}
|
||||
if !reflect.DeepEqual(wm.RedirectUris, redirectURIS) {
|
||||
changes = append(changes, project.ChangeRedirectURIs(redirectURIS))
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func applicationWriteModelToApplication(writeModel *ApplicationWriteModel) domai
|
||||
func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.OIDCApp {
|
||||
return &domain.OIDCApp{
|
||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
||||
AppID: writeModel.AggregateID,
|
||||
AppID: writeModel.AppID,
|
||||
AppName: writeModel.AppName,
|
||||
State: writeModel.State,
|
||||
ClientID: writeModel.ClientID,
|
||||
@ -55,6 +55,18 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
|
||||
}
|
||||
}
|
||||
|
||||
func apiWriteModelToAPIConfig(writeModel *APIApplicationWriteModel) *domain.APIApp {
|
||||
return &domain.APIApp{
|
||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
||||
AppID: writeModel.AppID,
|
||||
AppName: writeModel.AppName,
|
||||
State: writeModel.State,
|
||||
ClientID: writeModel.ClientID,
|
||||
ClientSecret: writeModel.ClientSecret,
|
||||
AuthMethodType: writeModel.AuthMethodType,
|
||||
}
|
||||
}
|
||||
|
||||
func roleWriteModelToRole(writeModel *ProjectRoleWriteModel) *domain.ProjectRole {
|
||||
return &domain.ProjectRole{
|
||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
||||
@ -72,3 +84,15 @@ func memberWriteModelToProjectGrantMember(writeModel *ProjectGrantMemberWriteMod
|
||||
UserID: writeModel.UserID,
|
||||
}
|
||||
}
|
||||
|
||||
func applicationKeyWriteModelToKey(wm *ApplicationKeyWriteModel, privateKey []byte) *domain.ApplicationKey {
|
||||
return &domain.ApplicationKey{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
ApplicationID: wm.AppID,
|
||||
ClientID: wm.ClientID,
|
||||
KeyID: wm.KeyID,
|
||||
Type: wm.KeyType,
|
||||
ExpirationDate: wm.ExpirationDate,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
@ -10,11 +9,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/v2/repository/user"
|
||||
)
|
||||
|
||||
var (
|
||||
//most of us won't survive until 12-31-9999 23:59:59, maybe ZITADEL does
|
||||
defaultExpDate = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||
)
|
||||
|
||||
func (r *CommandSide) AddUserMachineKey(ctx context.Context, machineKey *domain.MachineKey, resourceOwner string) (*domain.MachineKey, error) {
|
||||
err := r.checkUserExists(ctx, machineKey.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
@ -30,15 +24,11 @@ func (r *CommandSide) AddUserMachineKey(ctx context.Context, machineKey *domain.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if machineKey.ExpirationDate.IsZero() {
|
||||
machineKey.ExpirationDate = defaultExpDate
|
||||
}
|
||||
if machineKey.ExpirationDate.Before(time.Now()) {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-38vns", "Errors.MachineKey.ExpireBeforeNow")
|
||||
if err = domain.EnsureValidExpirationDate(machineKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = machineKey.GenerateNewMachineKeyPair(r.machineKeySize)
|
||||
if err != nil {
|
||||
if err = domain.SetNewAuthNKeyPair(machineKey, r.machineKeySize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ type MachineKeyWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
KeyID string
|
||||
KeyType domain.MachineKeyType
|
||||
KeyType domain.AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
|
||||
State domain.MachineKeyState
|
||||
|
@ -15,6 +15,10 @@ const (
|
||||
AppStateRemoved
|
||||
)
|
||||
|
||||
func (a AppState) Exists() bool {
|
||||
return !(a == AppStateUnspecified || a == AppStateRemoved)
|
||||
}
|
||||
|
||||
type ChangeApp struct {
|
||||
AppID string
|
||||
AppName string
|
||||
|
61
internal/v2/domain/application_api.go
Normal file
61
internal/v2/domain/application_api.go
Normal file
@ -0,0 +1,61 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type APIApp struct {
|
||||
models.ObjectRoot
|
||||
|
||||
AppID string
|
||||
AppName string
|
||||
ClientID string
|
||||
ClientSecret *crypto.CryptoValue
|
||||
ClientSecretString string
|
||||
AuthMethodType APIAuthMethodType
|
||||
|
||||
State AppState
|
||||
}
|
||||
|
||||
func (a *APIApp) GetApplicationName() string {
|
||||
return a.AppName
|
||||
}
|
||||
|
||||
func (a *APIApp) GetState() AppState {
|
||||
return a.State
|
||||
}
|
||||
|
||||
type APIAuthMethodType int32
|
||||
|
||||
const (
|
||||
APIAuthMethodTypeBasic APIAuthMethodType = iota
|
||||
APIAuthMethodTypePrivateKeyJWT
|
||||
)
|
||||
|
||||
func (a *APIApp) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *APIApp) setClientID(clientID string) {
|
||||
a.ClientID = clientID
|
||||
}
|
||||
|
||||
func (a *APIApp) setClientSecret(clientSecret *crypto.CryptoValue) {
|
||||
a.ClientSecret = clientSecret
|
||||
}
|
||||
|
||||
func (a *APIApp) requiresClientSecret() bool {
|
||||
return a.AuthMethodType == APIAuthMethodTypeBasic
|
||||
}
|
||||
|
||||
func (a *APIApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (secret string, err error) {
|
||||
if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT {
|
||||
return "", nil
|
||||
}
|
||||
a.ClientSecret, secret, err = NewClientSecret(generator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return secret, nil
|
||||
}
|
60
internal/v2/domain/application_key.go
Normal file
60
internal/v2/domain/application_key.go
Normal file
@ -0,0 +1,60 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type ApplicationKey struct {
|
||||
models.ObjectRoot
|
||||
|
||||
ApplicationID string
|
||||
ClientID string
|
||||
KeyID string
|
||||
Type AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setPublicKey(publicKey []byte) {
|
||||
k.PublicKey = publicKey
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setPrivateKey(privateKey []byte) {
|
||||
k.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) expirationDate() time.Time {
|
||||
return k.ExpirationDate
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) setExpirationDate(expiration time.Time) {
|
||||
k.ExpirationDate = expiration
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) Detail() ([]byte, error) {
|
||||
if k.Type == AuthNKeyTypeJSON {
|
||||
return k.MarshalJSON()
|
||||
}
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "KEY-dsg52", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (k *ApplicationKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Type string `json:"type"`
|
||||
KeyID string `json:"keyId"`
|
||||
Key string `json:"key"`
|
||||
AppID string `json:"appId"`
|
||||
ClientID string `json:"clientID"`
|
||||
}{
|
||||
Type: "application",
|
||||
KeyID: k.KeyID,
|
||||
Key: string(k.PrivateKey),
|
||||
AppID: k.ApplicationID,
|
||||
ClientID: k.ClientID,
|
||||
})
|
||||
}
|
50
internal/v2/domain/application_oauth.go
Normal file
50
internal/v2/domain/application_oauth.go
Normal file
@ -0,0 +1,50 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
)
|
||||
|
||||
type oAuthApplication interface {
|
||||
setClientID(clientID string)
|
||||
setClientSecret(secret *crypto.CryptoValue)
|
||||
requiresClientSecret() bool
|
||||
}
|
||||
|
||||
//ClientID random_number@projectname (eg. 495894098234@zitadel)
|
||||
func SetNewClientID(a oAuthApplication, idGenerator id.Generator, project *Project) error {
|
||||
rndID, err := idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.setClientID(fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_")))
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetNewClientSecretIfNeeded(a oAuthApplication, generator crypto.Generator) (string, error) {
|
||||
if !a.requiresClientSecret() {
|
||||
return "", nil
|
||||
}
|
||||
clientSecret, secretString, err := NewClientSecret(generator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
a.setClientSecret(clientSecret)
|
||||
return secretString, nil
|
||||
}
|
||||
|
||||
func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
|
||||
cryptoValue, stringSecret, err := crypto.NewCode(generator)
|
||||
if err != nil {
|
||||
logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret")
|
||||
return nil, "", errors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
return cryptoValue, stringSecret, nil
|
||||
}
|
@ -1,16 +1,11 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -46,7 +41,7 @@ type OIDCApp struct {
|
||||
State AppState
|
||||
}
|
||||
|
||||
func (h OIDCApp) GetUsername() string {
|
||||
func (h OIDCApp) GetApplicationName() string {
|
||||
return h.AppName
|
||||
}
|
||||
|
||||
@ -54,6 +49,18 @@ func (h OIDCApp) GetState() AppState {
|
||||
return h.State
|
||||
}
|
||||
|
||||
func (h OIDCApp) setClientID(clientID string) {
|
||||
h.ClientID = clientID
|
||||
}
|
||||
|
||||
func (h OIDCApp) setClientSecret(clientSecret *crypto.CryptoValue) {
|
||||
h.ClientSecret = clientSecret
|
||||
}
|
||||
|
||||
func (h OIDCApp) requiresClientSecret() bool {
|
||||
return h.AuthMethodType == OIDCAuthMethodTypeBasic || h.AuthMethodType == OIDCAuthMethodTypePost
|
||||
}
|
||||
|
||||
type OIDCVersion int32
|
||||
|
||||
const (
|
||||
@ -116,42 +123,6 @@ func (c *OIDCApp) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
//ClientID random_number@projectname (eg. 495894098234@zitadel)
|
||||
func (c *OIDCApp) GenerateNewClientID(idGenerator id.Generator, project *Project) error {
|
||||
rndID, err := idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ClientID = fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OIDCApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (string, error) {
|
||||
if c.AuthMethodType == OIDCAuthMethodTypeNone {
|
||||
return "", nil
|
||||
}
|
||||
return c.GenerateNewClientSecret(generator)
|
||||
}
|
||||
|
||||
func (c *OIDCApp) GenerateNewClientSecret(generator crypto.Generator) (string, error) {
|
||||
cryptoValue, stringSecret, err := NewClientSecret(generator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.ClientSecret = cryptoValue
|
||||
return stringSecret, nil
|
||||
}
|
||||
|
||||
func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
|
||||
cryptoValue, stringSecret, err := crypto.NewCode(generator)
|
||||
if err != nil {
|
||||
logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret")
|
||||
return nil, "", errors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
return cryptoValue, stringSecret, nil
|
||||
}
|
||||
|
||||
func (c *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
||||
grantTypes := make([]OIDCGrantType, 0)
|
||||
implicit := false
|
||||
|
86
internal/v2/domain/authn_key.go
Normal file
86
internal/v2/domain/authn_key.go
Normal file
@ -0,0 +1,86 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
//most of us won't survive until 12-31-9999 23:59:59, maybe ZITADEL does
|
||||
defaultExpDate = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||
)
|
||||
|
||||
type AuthNKey interface {
|
||||
}
|
||||
|
||||
type authNKey interface {
|
||||
setPublicKey([]byte)
|
||||
setPrivateKey([]byte)
|
||||
expirationDate() time.Time
|
||||
setExpirationDate(time.Time)
|
||||
}
|
||||
|
||||
type AuthNKeyType int32
|
||||
|
||||
const (
|
||||
AuthNKeyTypeNONE = iota
|
||||
AuthNKeyTypeJSON
|
||||
|
||||
keyCount
|
||||
)
|
||||
|
||||
func (k AuthNKeyType) Valid() bool {
|
||||
return k >= 0 && k < keyCount
|
||||
}
|
||||
|
||||
func (key *MachineKey) GenerateNewMachineKeyPair(keySize int) error {
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PrivateKey = crypto.PrivateKeyToBytes(privateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureValidExpirationDate(key authNKey) error {
|
||||
if key.expirationDate().IsZero() {
|
||||
key.setExpirationDate(defaultExpDate)
|
||||
}
|
||||
if key.expirationDate().Before(time.Now()) {
|
||||
return errors.ThrowInvalidArgument(nil, "AUTHN-dv3t5", "Errors.AuthNKey.ExpireBeforeNow")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetNewAuthNKeyPair(key authNKey, keySize int) error {
|
||||
privateKey, publicKey, err := NewAuthNKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.setPrivateKey(privateKey)
|
||||
key.setPublicKey(publicKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAuthNKeyPair(keySize int) (privateKey, publicKey []byte, err error) {
|
||||
private, public, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
logging.Log("AUTHN-Ud51I").WithError(err).Error("unable to create authn key pair")
|
||||
return nil, nil, errors.ThrowInternal(err, "AUTHN-gdg2l", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
publicKey, err = crypto.PublicKeyToBytes(public)
|
||||
if err != nil {
|
||||
logging.Log("AUTHN-Dbb35").WithError(err).Error("unable to convert public key")
|
||||
return nil, nil, errors.ThrowInternal(err, "AUTHN-Bne3f", "Errors.Project.CouldNotGenerateClientSecret")
|
||||
}
|
||||
privateKey = crypto.PrivateKeyToBytes(private)
|
||||
return privateKey, publicKey, nil
|
||||
}
|
@ -1,29 +1,36 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
)
|
||||
|
||||
type MachineKey struct {
|
||||
models.ObjectRoot
|
||||
|
||||
KeyID string
|
||||
Type MachineKeyType
|
||||
Type AuthNKeyType
|
||||
ExpirationDate time.Time
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
type MachineKeyType int32
|
||||
func (key *MachineKey) setPublicKey(publicKey []byte) {
|
||||
key.PublicKey = publicKey
|
||||
}
|
||||
|
||||
const (
|
||||
MachineKeyTypeNONE = iota
|
||||
MachineKeyTypeJSON
|
||||
func (key *MachineKey) setPrivateKey(privateKey []byte) {
|
||||
key.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
keyCount
|
||||
)
|
||||
func (key *MachineKey) expirationDate() time.Time {
|
||||
return key.ExpirationDate
|
||||
}
|
||||
|
||||
func (key *MachineKey) setExpirationDate(expiration time.Time) {
|
||||
key.ExpirationDate = expiration
|
||||
}
|
||||
|
||||
type MachineKeyState int32
|
||||
|
||||
@ -38,20 +45,3 @@ const (
|
||||
func (f MachineKeyState) Valid() bool {
|
||||
return f >= 0 && f < machineKeyStateCount
|
||||
}
|
||||
|
||||
func (f MachineKeyType) Valid() bool {
|
||||
return f >= 0 && f < keyCount
|
||||
}
|
||||
|
||||
func (key *MachineKey) GenerateNewMachineKeyPair(keySize int) error {
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(keySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PrivateKey = crypto.PrivateKeyToBytes(privateKey)
|
||||
return nil
|
||||
}
|
||||
|
261
internal/v2/repository/project/api_config.go
Normal file
261
internal/v2/repository/project/api_config.go
Normal file
@ -0,0 +1,261 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2/repository"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
APIConfigAddedType = applicationEventTypePrefix + "config.api.added"
|
||||
APIConfigChangedType = applicationEventTypePrefix + "config.api.changed"
|
||||
APIConfigSecretChangedType = applicationEventTypePrefix + "config.api.secret.changed"
|
||||
APIClientSecretCheckSucceededType = applicationEventTypePrefix + "api.secret.check.succeeded"
|
||||
APIClientSecretCheckFailedType = applicationEventTypePrefix + "api.secret.check.failed"
|
||||
)
|
||||
|
||||
type APIConfigAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
AppID string `json:"appId"`
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||
AuthMethodType domain.APIAuthMethodType `json:"authMethodType,omitempty"`
|
||||
}
|
||||
|
||||
func (e *APIConfigAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIConfigAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAPIConfigAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID,
|
||||
clientID string,
|
||||
clientSecret *crypto.CryptoValue,
|
||||
authMethodType domain.APIAuthMethodType,
|
||||
) *APIConfigAddedEvent {
|
||||
return &APIConfigAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
APIConfigAddedType,
|
||||
),
|
||||
AppID: appID,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
AuthMethodType: authMethodType,
|
||||
}
|
||||
}
|
||||
|
||||
func APIConfigAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &APIConfigAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "API-BFd15", "unable to unmarshal api config")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type APIConfigChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
AppID string `json:"appId"`
|
||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||
AuthMethodType *domain.APIAuthMethodType `json:"authMethodType,omitempty"`
|
||||
}
|
||||
|
||||
func (e *APIConfigChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIConfigChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAPIConfigChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID string,
|
||||
changes []APIConfigChanges,
|
||||
) (*APIConfigChangedEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "API-i8idç", "Errors.NoChangesFound")
|
||||
}
|
||||
|
||||
changeEvent := &APIConfigChangedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
APIConfigChangedType,
|
||||
),
|
||||
AppID: appID,
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(changeEvent)
|
||||
}
|
||||
return changeEvent, nil
|
||||
}
|
||||
|
||||
type APIConfigChanges func(event *APIConfigChangedEvent)
|
||||
|
||||
func ChangeAPIAuthMethodType(authMethodType domain.APIAuthMethodType) func(event *APIConfigChangedEvent) {
|
||||
return func(e *APIConfigChangedEvent) {
|
||||
e.AuthMethodType = &authMethodType
|
||||
}
|
||||
}
|
||||
|
||||
func APIConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &APIConfigChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "API-BFd15", "unable to unmarshal api config")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type APIConfigSecretChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
AppID string `json:"appId"`
|
||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||
}
|
||||
|
||||
func (e *APIConfigSecretChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIConfigSecretChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAPIConfigSecretChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID string,
|
||||
clientSecret *crypto.CryptoValue,
|
||||
) *APIConfigSecretChangedEvent {
|
||||
return &APIConfigSecretChangedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
APIConfigSecretChangedType,
|
||||
),
|
||||
AppID: appID,
|
||||
ClientSecret: clientSecret,
|
||||
}
|
||||
}
|
||||
|
||||
func APIConfigSecretChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &APIConfigSecretChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "API-M893d", "unable to unmarshal api config")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type APIConfigSecretCheckSucceededEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
AppID string `json:"appId"`
|
||||
}
|
||||
|
||||
func (e *APIConfigSecretCheckSucceededEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIConfigSecretCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAPIConfigSecretCheckSucceededEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID string,
|
||||
) *APIConfigSecretCheckSucceededEvent {
|
||||
return &APIConfigSecretCheckSucceededEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
APIClientSecretCheckSucceededType,
|
||||
),
|
||||
AppID: appID,
|
||||
}
|
||||
}
|
||||
|
||||
func APIConfigSecretCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &APIConfigSecretCheckSucceededEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "API-837gV", "unable to unmarshal api config")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type APIConfigSecretCheckFailedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
AppID string `json:"appId"`
|
||||
}
|
||||
|
||||
func (e *APIConfigSecretCheckFailedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIConfigSecretCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAPIConfigSecretCheckFailedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID string,
|
||||
) *APIConfigSecretCheckFailedEvent {
|
||||
return &APIConfigSecretCheckFailedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
APIClientSecretCheckFailedType,
|
||||
),
|
||||
AppID: appID,
|
||||
}
|
||||
}
|
||||
|
||||
func APIConfigSecretCheckFailedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &APIConfigSecretCheckFailedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "API-987g%", "unable to unmarshal api config")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
@ -34,5 +34,10 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(OIDCConfigChangedType, OIDCConfigChangedEventMapper).
|
||||
RegisterFilterEventMapper(OIDCConfigSecretChangedType, OIDCConfigSecretChangedEventMapper).
|
||||
RegisterFilterEventMapper(OIDCClientSecretCheckSucceededType, OIDCConfigSecretCheckSucceededEventMapper).
|
||||
RegisterFilterEventMapper(OIDCClientSecretCheckFailedType, OIDCConfigSecretCheckFailedEventMapper)
|
||||
RegisterFilterEventMapper(OIDCClientSecretCheckFailedType, OIDCConfigSecretCheckFailedEventMapper).
|
||||
RegisterFilterEventMapper(APIConfigAddedType, APIConfigAddedEventMapper).
|
||||
RegisterFilterEventMapper(APIConfigChangedType, APIConfigChangedEventMapper).
|
||||
RegisterFilterEventMapper(APIConfigSecretChangedType, APIConfigSecretChangedEventMapper).
|
||||
RegisterFilterEventMapper(ApplicationKeyAddedEventType, ApplicationKeyAddedEventMapper).
|
||||
RegisterFilterEventMapper(ApplicationKeyRemovedEventType, ApplicationKeyRemovedEventMapper)
|
||||
}
|
||||
|
116
internal/v2/repository/project/key.go
Normal file
116
internal/v2/repository/project/key.go
Normal file
@ -0,0 +1,116 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2/repository"
|
||||
"github.com/caos/zitadel/internal/v2/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationKeyEventPrefix = applicationEventTypePrefix + "oidc.key."
|
||||
ApplicationKeyAddedEventType = applicationKeyEventPrefix + "added"
|
||||
ApplicationKeyRemovedEventType = applicationKeyEventPrefix + "removed"
|
||||
)
|
||||
|
||||
type ApplicationKeyAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
AppID string `json:"applicationId"`
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
KeyID string `json:"keyId,omitempty"`
|
||||
KeyType domain.AuthNKeyType `json:"type,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey []byte `json:"publicKey,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ApplicationKeyAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *ApplicationKeyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewApplicationKeyAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
appID,
|
||||
clientID,
|
||||
keyID string,
|
||||
keyType domain.AuthNKeyType,
|
||||
expirationDate time.Time,
|
||||
publicKey []byte,
|
||||
) *ApplicationKeyAddedEvent {
|
||||
return &ApplicationKeyAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
ApplicationKeyAddedEventType,
|
||||
),
|
||||
AppID: appID,
|
||||
ClientID: clientID,
|
||||
KeyID: keyID,
|
||||
KeyType: keyType,
|
||||
ExpirationDate: expirationDate,
|
||||
PublicKey: publicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func ApplicationKeyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &ApplicationKeyAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "API-BFd15", "unable to unmarshal api config")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type ApplicationKeyRemovedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
KeyID string `json:"keyId,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ApplicationKeyRemovedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *ApplicationKeyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewApplicationKeyRemovedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
keyID string,
|
||||
) *ApplicationKeyRemovedEvent {
|
||||
return &ApplicationKeyRemovedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
ApplicationKeyRemovedEventType,
|
||||
),
|
||||
KeyID: keyID,
|
||||
}
|
||||
}
|
||||
|
||||
func ApplicationKeyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
applicationKeyRemoved := &ApplicationKeyRemovedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, applicationKeyRemoved)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "USER-5Gm9s", "unable to unmarshal application key removed")
|
||||
}
|
||||
|
||||
return applicationKeyRemoved, nil
|
||||
}
|
@ -112,8 +112,6 @@ type OIDCConfigChangedEvent struct {
|
||||
|
||||
Version *domain.OIDCVersion `json:"oidcVersion,omitempty"`
|
||||
AppID string `json:"appId"`
|
||||
ClientID *string `json:"clientId,omitempty"`
|
||||
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
|
||||
RedirectUris *[]string `json:"redirectUris,omitempty"`
|
||||
ResponseTypes *[]domain.OIDCResponseType `json:"responseTypes,omitempty"`
|
||||
GrantTypes *[]domain.OIDCGrantType `json:"grantTypes,omitempty"`
|
||||
@ -168,12 +166,6 @@ func ChangeVersion(version domain.OIDCVersion) func(event *OIDCConfigChangedEven
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeClientID(clientID string) func(event *OIDCConfigChangedEvent) {
|
||||
return func(e *OIDCConfigChangedEvent) {
|
||||
e.ClientID = &clientID
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeRedirectURIs(uris []string) func(event *OIDCConfigChangedEvent) {
|
||||
return func(e *OIDCConfigChangedEvent) {
|
||||
e.RedirectUris = &uris
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
mfaEventPrefix = humanEventPrefix + "mfa."
|
||||
HumanMFAInitSkippedType = mfaEventPrefix + "init.skiped"
|
||||
HumanMFAInitSkippedType = mfaEventPrefix + "init.skipped"
|
||||
)
|
||||
|
||||
type HumanMFAInitSkippedEvent struct {
|
||||
|
@ -20,10 +20,10 @@ const (
|
||||
type MachineKeyAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
KeyID string `json:"keyId,omitempty"`
|
||||
KeyType domain.MachineKeyType `json:"type,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey []byte `json:"publicKey,omitempty"`
|
||||
KeyID string `json:"keyId,omitempty"`
|
||||
KeyType domain.AuthNKeyType `json:"type,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey []byte `json:"publicKey,omitempty"`
|
||||
}
|
||||
|
||||
func (e *MachineKeyAddedEvent) Data() interface{} {
|
||||
@ -38,7 +38,7 @@ func NewMachineKeyAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
keyID string,
|
||||
keyType domain.MachineKeyType,
|
||||
keyType domain.AuthNKeyType,
|
||||
expirationDate time.Time,
|
||||
publicKey []byte,
|
||||
) *MachineKeyAddedEvent {
|
||||
|
Loading…
x
Reference in New Issue
Block a user