feat: features (#1427)

* features

* features

* features

* fix json tags

* add features handler to auth

* mocks for tests

* add setup step

* fixes

* add featurelist to auth api

* grandfather state and typos

* typo

* merge new-eventstore

* fix login policy tests

* label policy in features

* audit log retention
This commit is contained in:
Livio Amstutz
2021-03-25 17:26:21 +01:00
committed by GitHub
parent c9b3839f3d
commit a4763b1e4c
97 changed files with 3335 additions and 109 deletions

View File

@@ -0,0 +1,85 @@
package model
import (
"time"
"github.com/caos/zitadel/internal/domain"
)
type FeaturesView struct {
AggregateID string
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
Default bool
TierName string
TierDescription string
State domain.FeaturesState
StateDescription string
AuditLogRetention time.Duration
LoginPolicyFactors bool
LoginPolicyIDP bool
LoginPolicyPasswordless bool
LoginPolicyRegistration bool
LoginPolicyUsernameLogin bool
PasswordComplexityPolicy bool
LabelPolicy bool
}
func (f *FeaturesView) FeatureList() []string {
list := make([]string, 0, 6)
if f.LoginPolicyFactors {
list = append(list, domain.FeatureLoginPolicyFactors)
}
if f.LoginPolicyIDP {
list = append(list, domain.FeatureLoginPolicyIDP)
}
if f.LoginPolicyPasswordless {
list = append(list, domain.FeatureLoginPolicyPasswordless)
}
if f.LoginPolicyRegistration {
list = append(list, domain.FeatureLoginPolicyRegistration)
}
if f.LoginPolicyUsernameLogin {
list = append(list, domain.FeatureLoginPolicyUsernameLogin)
}
if f.PasswordComplexityPolicy {
list = append(list, domain.FeaturePasswordComplexityPolicy)
}
if f.LabelPolicy {
list = append(list, domain.FeatureLabelPolicy)
}
return list
}
type FeaturesSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn FeaturesSearchKey
Asc bool
Queries []*FeaturesSearchQuery
}
type FeaturesSearchKey int32
const (
FeaturesSearchKeyUnspecified FeaturesSearchKey = iota
FeaturesSearchKeyAggregateID
FeaturesSearchKeyDefault
)
type FeaturesSearchQuery struct {
Key FeaturesSearchKey
Method domain.SearchMethod
Value interface{}
}
type FeaturesSearchResult struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*FeaturesView
Sequence uint64
Timestamp time.Time
}

View File

@@ -0,0 +1,48 @@
package view
import (
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/features/model"
view_model "github.com/caos/zitadel/internal/features/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
)
func GetDefaultFeatures(db *gorm.DB, table string) ([]*view_model.FeaturesView, error) {
features := make([]*view_model.FeaturesView, 0)
queries := []*model.FeaturesSearchQuery{
{Key: model.FeaturesSearchKeyDefault, Value: true, Method: domain.SearchMethodEquals},
}
query := repository.PrepareSearchQuery(table, view_model.FeaturesSearchRequest{Queries: queries})
_, err := query(db, &features)
if err != nil {
return nil, err
}
return features, nil
}
func GetFeaturesByAggregateID(db *gorm.DB, table, aggregateID string) (*view_model.FeaturesView, error) {
features := new(view_model.FeaturesView)
query := repository.PrepareGetByKey(table, view_model.FeaturesSearchKey(model.FeaturesSearchKeyAggregateID), aggregateID)
err := query(db, features)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Dbf3h", "Errors.Features.NotFound")
}
return features, err
}
func PutFeatures(db *gorm.DB, table string, features *view_model.FeaturesView) error {
save := repository.PrepareSave(table)
return save(db, features)
}
func PutFeaturesList(db *gorm.DB, table string, featuresList ...*view_model.FeaturesView) error {
save := repository.PrepareBulkSave(table)
f := make([]interface{}, len(featuresList))
for i, features := range featuresList {
f[i] = features
}
return save(db, f...)
}

View File

@@ -0,0 +1,95 @@
package model
import (
"encoding/json"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
features_model "github.com/caos/zitadel/internal/features/model"
iam_repo "github.com/caos/zitadel/internal/repository/iam"
org_repo "github.com/caos/zitadel/internal/repository/org"
)
const (
FeaturesKeyAggregateID = "aggregate_id"
FeaturesKeyDefault = "default_features"
)
type FeaturesView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
Default bool `json:"-" gorm:"column:default_features"`
TierName string `json:"tierName" gorm:"column:tier_name"`
TierDescription string `json:"tierDescription" gorm:"column:tier_description"`
State int32 `json:"state" gorm:"column:state"`
StateDescription string `json:"stateDescription" gorm:"column:state_description"`
AuditLogRetention time.Duration `json:"auditLogRetention" gorm:"column:audit_log_retention"`
LoginPolicyFactors bool `json:"loginPolicyFactors" gorm:"column:login_policy_factors"`
LoginPolicyIDP bool `json:"loginPolicyIDP" gorm:"column:login_policy_idp"`
LoginPolicyPasswordless bool `json:"loginPolicyPasswordless" gorm:"column:login_policy_passwordless"`
LoginPolicyRegistration bool `json:"loginPolicyRegistration" gorm:"column:login_policy_registration"`
LoginPolicyUsernameLogin bool `json:"loginPolicyUsernameLogin" gorm:"column:login_policy_username_login"`
PasswordComplexityPolicy bool `json:"passwordComplexityPolicy" gorm:"column:password_complexity_policy"`
LabelPolicy bool `json:"labelPolicy" gorm:"column:label_policy"`
}
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
return &features_model.FeaturesView{
AggregateID: features.AggregateID,
CreationDate: features.CreationDate,
ChangeDate: features.ChangeDate,
Sequence: features.Sequence,
Default: features.Default,
TierName: features.TierName,
TierDescription: features.TierDescription,
State: domain.FeaturesState(features.State),
StateDescription: features.StateDescription,
AuditLogRetention: features.AuditLogRetention,
LoginPolicyFactors: features.LoginPolicyFactors,
LoginPolicyIDP: features.LoginPolicyIDP,
LoginPolicyPasswordless: features.LoginPolicyPasswordless,
LoginPolicyRegistration: features.LoginPolicyRegistration,
LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin,
PasswordComplexityPolicy: features.PasswordComplexityPolicy,
LabelPolicy: features.LabelPolicy,
}
}
func (f *FeaturesView) AppendEvent(event *models.Event) (err error) {
f.Sequence = event.Sequence
f.ChangeDate = event.CreationDate
switch string(event.Type) {
case string(iam_repo.FeaturesSetEventType):
f.SetRootData(event)
f.CreationDate = event.CreationDate
f.Default = true
err = f.SetData(event)
case string(org_repo.FeaturesSetEventType):
f.SetRootData(event)
f.CreationDate = event.CreationDate
err = f.SetData(event)
f.Default = false
}
return err
}
func (f *FeaturesView) SetRootData(event *models.Event) {
if f.AggregateID == "" {
f.AggregateID = event.AggregateID
}
}
func (f *FeaturesView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, f); err != nil {
logging.Log("EVEN-DVsf2").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-Bfg31", "Could not unmarshal data")
}
return nil
}

View File

@@ -0,0 +1,61 @@
package model
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/features/model"
"github.com/caos/zitadel/internal/view/repository"
)
type FeaturesSearchRequest model.FeaturesSearchRequest
type FeaturesSearchQuery model.FeaturesSearchQuery
type FeaturesSearchKey model.FeaturesSearchKey
func (req FeaturesSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req FeaturesSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req FeaturesSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == model.FeaturesSearchKeyUnspecified {
return nil
}
return FeaturesSearchKey(req.SortingColumn)
}
func (req FeaturesSearchRequest) GetAsc() bool {
return req.Asc
}
func (req FeaturesSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = FeaturesSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req FeaturesSearchQuery) GetKey() repository.ColumnKey {
return FeaturesSearchKey(req.Key)
}
func (req FeaturesSearchQuery) GetMethod() domain.SearchMethod {
return req.Method
}
func (req FeaturesSearchQuery) GetValue() interface{} {
return req.Value
}
func (key FeaturesSearchKey) ToColumnName() string {
switch model.FeaturesSearchKey(key) {
case model.FeaturesSearchKeyAggregateID:
return FeaturesKeyAggregateID
case model.FeaturesSearchKeyDefault:
return FeaturesKeyDefault
default:
return ""
}
}