feat: label policy (#1708)

* feat: label policy proto extension

* feat: label policy and activate event

* feat: label policy asset events

* feat: label policy asset commands

* feat: add storage key

* feat: storage key validation

* feat: label policy asset tests

* feat: label policy query side

* feat: avatar

* feat: avatar event

* feat: human avatar

* feat: avatar read side

* feat: font on iam label policy

* feat: label policy font

* feat: possiblity to create bucket on put file

* uplaoder

* login policy logo

* set bucket prefix

* feat: avatar upload

* feat: avatar upload

* feat: use assets on command side

* feat: fix human avatar removed event

* feat: remove human avatar

* feat: mock asset storage

* feat: remove human avatar

* fix(operator): add configuration of asset storage to zitadel operator

* feat(console): private labeling policy (#1697)

* private labeling component, routing, preview

* font, colors, upload, i18n

* show logo

* fix: uniqueness (#1710)

* fix: uniqueconstraint to lower

* feat: change org

* feat: org change test

* feat: change org

* fix: tests

* fix: handle domain claims correctly

* feat: update org

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: handle domain claimed event correctly for service users (#1711)

* fix: handle domain claimed event correctly on user view

* fix: ignore domain claimed events for email notifications

* fix: change org

* handle org changed in read models correctly

* fix: change org in user grant handler

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: correct value (#1695)

* docs(api): correct link (#1712)

* upload service

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
Co-authored-by: Florian Forster <florian@caos.ch>

* feat: fix tests,

* feat: remove assets from label policy

* fix npm, set environment

* lint ts

* remove stylelinting

* fix(operator): add mapping for console with changed unit tests

* fix(operator): add secrets as env variables to pod

* feat: remove human avatar

* fix(operator): add secrets as env variables to pod

* feat: map label policy

* feat: labelpolicy, admin, mgmt, adv settings (#1715)

* fetch label policy, mgmt, admin service

* feat: advanced beh, links, add, update

* lint ts

* feat: watermark

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: custom css

* css

* css

* css

* css

* css

* getobject

* feat: dynamic handler

* feat: varibale css

* content info

* css overwrite

* feat: variablen css

* feat: generate css file

* feat: dark mode

* feat: dark mode

* fix logo css

* feat: upload logos

* dark mode with cookie

* feat: handle images in login

* avatar css and begin font

* feat: avatar

* feat: user avatar

* caching of static assets in login

* add avatar.js to main.html

* feat: header dont show logo if no url

* feat: label policy colors

* feat: mock asset storage

* feat: mock asset storage

* feat: fix tests

* feat: user avatar

* feat: header logo

* avatar

* avatar

* make it compatible with go 1.15

* feat: remove unused logos

* fix handler

* fix: styling error handling

* fonts

* fix: download func

* switch to mux

* fix: change upload api to assets

* fix build

* fix: download avatar

* fix: download logos

* fix: my avatar

* font

* fix: remove error msg popup possibility

* fix: docs

* fix: svalidate colors

* rem msg popup from frontend

* fix: email with private labeling

* fix: tests

* fix: email templates

* fix: change migration version

* fix: fix duplicate imports

* fix(console): assets, service url, upload, policy current and preview  (#1781)

* upload endpoint, layout

* fetch current, preview, fix upload

* cleanup private labeling

* fix linting

* begin generated asset handler

* generate asset api in dockerfile

* features for label policy

* features for label policy

* features

* flag for asset generator

* change asset generator flag

* fix label policy view in grpc

* fix: layout, activate policy (#1786)

* theme switcher up on top

* change layout

* activate policy

* feat(console): label policy back color, layout (#1788)

* theme switcher up on top

* change layout

* activate policy

* fix overwrite value fc

* reset policy, reset service

* autosave policy, preview desc, layout impv

* layout, i18n

* background colors, inject material styles

* load images

* clean, lint

* fix layout

* set custom hex

* fix content size conversion

* remove font format in generated css

* fix features for assets

* fix(console): label policy colors, image downloads, preview (#1804)

* load images

* colors, images binding

* lint

* refresh emitter

* lint

* propagate font colors

* upload error handling

* label policy feature check

* add blob in csp for console

* log

* fix: feature edits for label policy, refresh state on upload (#1807)

* show error on load image, stop spinner

* fix merge

* fix migration versions

* fix assets

* fix csp

* fix background color

* scss

* fix build

* lint scss

* fix statik for console

* fix features check for label policy

* cleanup

* lint

* public links

* fix notifications

* public links

* feat: merge main

* feat: fix translation files

* fix migration

* set api domain

* fix logo in email

* font face in email

* font face in email

* validate assets on upload

* cleanup

* add missing translations

* add missing translations

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Fabi
2021-06-04 14:53:51 +02:00
committed by GitHub
parent c0d9d86b09
commit 73d37459bb
257 changed files with 18248 additions and 7178 deletions

View File

@@ -300,7 +300,15 @@ func (repo *IAMRepository) GetOrgIAMPolicy(ctx context.Context) (*iam_model.OrgI
}
func (repo *IAMRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
policy, err := repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}
return iam_es_model.LabelPolicyViewToModel(policy), err
}
func (repo *IAMRepository) GetDefaultPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return nil, err
}

View File

@@ -4,6 +4,7 @@ import (
"time"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/systemdefaults"
@@ -30,8 +31,8 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults) []query.Handler {
return []query.Handler{
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, static static.Storage, localDevMode bool) []query.Handler {
handlers := []query.Handler{
newOrg(
handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}),
newIAMMember(
@@ -66,6 +67,13 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
}
if static != nil {
handlers = append(handlers, newStyling(
handler{view, bulkLimit, configs.cycleDuration("Styling"), errorCount, es},
static,
localDevMode))
}
return handlers
}
func (configs Configs) cycleDuration(viewModel string) time.Duration {

View File

@@ -2,6 +2,8 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
@@ -77,8 +79,25 @@ func (p *LabelPolicy) processLabelPolicy(event *es_models.Event) (err error) {
switch event.Type {
case model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case model.LabelPolicyChanged:
policy, err = p.view.LabelPolicyByAggregateID(event.AggregateID)
case model.LabelPolicyChanged,
model.LabelPolicyLogoAdded,
model.LabelPolicyLogoRemoved,
model.LabelPolicyIconAdded,
model.LabelPolicyIconRemoved,
model.LabelPolicyLogoDarkAdded,
model.LabelPolicyLogoDarkRemoved,
model.LabelPolicyIconDarkAdded,
model.LabelPolicyIconDarkRemoved,
model.LabelPolicyFontAdded,
model.LabelPolicyFontRemoved,
model.LabelPolicyAssetsRemoved:
policy, err = p.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
case model.LabelPolicyActivated:
policy, err = p.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}

View File

@@ -0,0 +1,298 @@
package handler
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/caos/logging"
"github.com/lucasb-eyer/go-colorful"
"github.com/muesli/gamut"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/static"
)
const (
stylingTable = "adminapi.styling"
)
type Styling struct {
handler
static static.Storage
subscription *v1.Subscription
devMode bool
resourceUrl string
}
func newStyling(handler handler, static static.Storage, localDevMode bool) *Styling {
h := &Styling{
handler: handler,
static: static,
}
prefix := ""
if localDevMode {
prefix = "/login"
}
h.resourceUrl = prefix + "/resources/dynamic" //TODO: ?
h.subscribe()
return h
}
func (m *Styling) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
func (m *Styling) ViewModel() string {
return stylingTable
}
func (_ *Styling) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (m *Styling) CurrentSequence() (uint64, error) {
sequence, err := m.view.GetLatestStylingSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *Styling) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestStylingSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *Styling) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processLabelPolicy(event)
}
return err
}
func (m *Styling) processLabelPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LabelPolicyView)
switch event.Type {
case iam_es_model.LabelPolicyAdded, model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged,
iam_es_model.LabelPolicyLogoAdded, model.LabelPolicyLogoAdded,
iam_es_model.LabelPolicyLogoRemoved, model.LabelPolicyLogoRemoved,
iam_es_model.LabelPolicyIconAdded, model.LabelPolicyIconAdded,
iam_es_model.LabelPolicyIconRemoved, model.LabelPolicyIconRemoved,
iam_es_model.LabelPolicyLogoDarkAdded, model.LabelPolicyLogoDarkAdded,
iam_es_model.LabelPolicyLogoDarkRemoved, model.LabelPolicyLogoDarkRemoved,
iam_es_model.LabelPolicyIconDarkAdded, model.LabelPolicyIconDarkAdded,
iam_es_model.LabelPolicyIconDarkRemoved, model.LabelPolicyIconDarkRemoved,
iam_es_model.LabelPolicyFontAdded, model.LabelPolicyFontAdded,
iam_es_model.LabelPolicyFontRemoved, model.LabelPolicyFontRemoved,
iam_es_model.LabelPolicyAssetsRemoved, model.LabelPolicyAssetsRemoved:
policy, err = m.view.StylingByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyActivated, model.LabelPolicyActivated:
policy, err = m.view.StylingByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
if err != nil {
return err
}
err = m.generateStylingFile(policy)
default:
return m.view.ProcessedStylingSequence(event)
}
if err != nil {
return err
}
return m.view.PutStyling(policy, event)
}
func (m *Styling) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-2m9fs", "id", event.AggregateID).WithError(err).Warn("something went wrong in label policy handler")
return spooler.HandleError(event, err, m.view.GetLatestLabelPolicyFailedEvent, m.view.ProcessedLabelPolicyFailedEvent, m.view.ProcessedLabelPolicySequence, m.errorCountUntilSkip)
}
func (m *Styling) OnSuccess() error {
return spooler.HandleSuccess(m.view.UpdateLabelPolicySpoolerRunTimestamp)
}
func (m *Styling) generateStylingFile(policy *iam_model.LabelPolicyView) error {
reader, size, err := m.writeFile(policy)
if err != nil {
return err
}
return m.uploadFilesToBucket(policy.AggregateID, "text/css", reader, size)
}
func (m *Styling) writeFile(policy *iam_model.LabelPolicyView) (io.Reader, int64, error) {
cssContent := ""
cssContent += fmt.Sprint(":root {")
if policy.PrimaryColor != "" {
palette := m.generateColorPaletteRGBA255(policy.PrimaryColor)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-primary-%v: %s;", i, color)
}
}
if policy.BackgroundColor != "" {
palette := m.generateColorPaletteRGBA255(policy.BackgroundColor)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-background-%v: %s;", i, color)
}
}
if policy.WarnColor != "" {
palette := m.generateColorPaletteRGBA255(policy.WarnColor)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-warn-%v: %s;", i, color)
}
}
var fontname string
if policy.FontURL != "" {
split := strings.Split(policy.FontURL, "/")
fontname = split[len(split)-1]
cssContent += fmt.Sprintf("--zitadel-font-family: %s;", fontname)
}
cssContent += fmt.Sprint("}")
if policy.FontURL != "" {
cssContent += fmt.Sprintf(fontFaceTemplate, fontname, m.resourceUrl, policy.AggregateID, policy.FontURL)
}
cssContent += fmt.Sprint(".lgn-dark-theme {")
if policy.PrimaryColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.PrimaryColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-primary-%v: %s;", i, color)
}
}
if policy.BackgroundColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.BackgroundColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-background-%v: %s;", i, color)
}
}
if policy.WarnColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.WarnColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-warn-%v: %s;", i, color)
}
}
if policy.FontColorDark != "" {
palette := m.generateColorPaletteRGBA255(policy.FontColorDark)
for i, color := range palette {
cssContent += fmt.Sprintf("--zitadel-color-font-%v: %s;", i, color)
}
}
cssContent += fmt.Sprint("}")
data := []byte(cssContent)
buffer := bytes.NewBuffer(data)
return buffer, int64(buffer.Len()), nil
}
const fontFaceTemplate = `
@font-face {
font-family: '%s';
font-style: normal;
font-display: swap;
src: url(%s?orgId=%s&filename=%s);
}
`
func (m *Styling) uploadFilesToBucket(aggregateID, contentType string, reader io.Reader, size int64) error {
fileName := domain.CssPath + "/" + domain.CssVariablesFileName
_, err := m.static.PutObject(context.Background(), aggregateID, fileName, contentType, reader, size, true)
return err
}
func (m *Styling) generateColorPaletteRGBA255(hex string) map[string]string {
palette := make(map[string]string)
defaultColor := gamut.Hex(hex)
color50, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 1.0))
if ok {
palette["50"] = cssRGB(color50.RGB255())
}
color100, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.8))
if ok {
palette["100"] = cssRGB(color100.RGB255())
}
color200, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.6))
if ok {
palette["200"] = cssRGB(color200.RGB255())
}
color300, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.4))
if ok {
palette["300"] = cssRGB(color300.RGB255())
}
color400, ok := colorful.MakeColor(gamut.Lighter(defaultColor, 0.1))
if ok {
palette["400"] = cssRGB(color400.RGB255())
}
color500, ok := colorful.MakeColor(defaultColor)
if ok {
palette["500"] = cssRGB(color500.RGB255())
}
color600, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.1))
if ok {
palette["600"] = cssRGB(color600.RGB255())
}
color700, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.2))
if ok {
palette["700"] = cssRGB(color700.RGB255())
}
color800, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.3))
if ok {
palette["800"] = cssRGB(color800.RGB255())
}
color900, ok := colorful.MakeColor(gamut.Darker(defaultColor, 0.4))
if ok {
palette["900"] = cssRGB(color900.RGB255())
}
colorContrast, ok := colorful.MakeColor(gamut.Contrast(defaultColor))
if ok {
palette["contrast"] = cssRGB(colorContrast.RGB255())
}
return palette
}
func cssRGB(r, g, b uint8) string {
return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/eventstore/v1"
es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/static"
)
type Config struct {
@@ -28,7 +29,7 @@ type EsRepository struct {
eventstore.UserRepo
}
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, roles []string) (*EsRepository, error) {
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, static static.Storage, roles []string, localDevMode bool) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
if err != nil {
return nil, err
@@ -42,7 +43,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, r
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, static, localDevMode)
return &EsRepository{
spooler: spool,

View File

@@ -4,6 +4,7 @@ import (
"database/sql"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/handler"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
@@ -17,12 +18,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults, static static.Storage, localDevMode bool) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults, static, localDevMode),
}
spool := spoolerConfig.New()
spool.Start()

View File

@@ -11,8 +11,8 @@ const (
labelPolicyTable = "adminapi.label_policies"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTable, aggregateID, state)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {

View File

@@ -0,0 +1,44 @@
package view
import (
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
stylingTyble = "adminapi.styling"
)
func (v *View) StylingByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, stylingTyble, aggregateID, state)
}
func (v *View) PutStyling(policy *model.LabelPolicyView, event *models.Event) error {
err := view.PutLabelPolicy(v.Db, stylingTyble, policy)
if err != nil {
return err
}
return v.ProcessedStylingSequence(event)
}
func (v *View) GetLatestStylingSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(stylingTyble)
}
func (v *View) ProcessedStylingSequence(event *models.Event) error {
return v.saveCurrentSequence(stylingTyble, event)
}
func (v *View) UpdateStylingSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(stylingTyble)
}
func (v *View) GetLatestStylingFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(stylingTyble, sequence)
}
func (v *View) ProcessedStylingFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@@ -24,6 +24,7 @@ type IAMRepository interface {
ExternalIDPsByIDPConfigIDFromDefaultPolicy(ctx context.Context, idpConfigID string) ([]*usr_model.ExternalIDPView, error)
GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)

View File

@@ -0,0 +1,194 @@
package assets
import (
"context"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/caos/logging"
"github.com/gorilla/mux"
"github.com/caos/zitadel/internal/api/authz"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/management/repository"
"github.com/caos/zitadel/internal/static"
)
type Handler struct {
errorHandler ErrorHandler
storage static.Storage
commands *command.Commands
authInterceptor *http_mw.AuthInterceptor
idGenerator id.Generator
orgRepo repository.OrgRepository
}
func (h *Handler) AuthInterceptor() *http_mw.AuthInterceptor {
return h.authInterceptor
}
func (h *Handler) Commands() *command.Commands {
return h.commands
}
func (h *Handler) ErrorHandler() ErrorHandler {
return DefaultErrorHandler
}
func (h *Handler) Storage() static.Storage {
return h.storage
}
type Uploader interface {
Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error
ObjectName(data authz.CtxData) (string, error)
BucketName(data authz.CtxData) string
ContentTypeAllowed(contentType string) bool
MaxFileSize() int64
}
type Downloader interface {
ObjectName(ctx context.Context, path string) (string, error)
BucketName(ctx context.Context, id string) string
}
type ErrorHandler func(http.ResponseWriter, *http.Request, error)
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
logging.Log("ASSET-g5ef1").WithError(err).WithField("uri", r.RequestURI).Error("error occurred on asset api")
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func NewHandler(
commands *command.Commands,
verifier *authz.TokenVerifier,
authConfig authz.Config,
idGenerator id.Generator,
storage static.Storage,
orgRepo repository.OrgRepository,
) http.Handler {
h := &Handler{
commands: commands,
errorHandler: DefaultErrorHandler,
authInterceptor: http_mw.AuthorizationInterceptor(verifier, authConfig),
idGenerator: idGenerator,
storage: storage,
orgRepo: orgRepo,
}
verifier.RegisterServer("Management-API", "assets", AssetsService_AuthMethods) //TODO: separate api?
router := mux.NewRouter()
RegisterRoutes(router, h)
router.PathPrefix("/{id}").Methods("GET").HandlerFunc(DownloadHandleFunc(h, h.GetFile()))
return router
}
func (h *Handler) GetFile() Downloader {
return &publicFileDownloader{}
}
type publicFileDownloader struct{}
func (l *publicFileDownloader) ObjectName(_ context.Context, path string) (string, error) {
return path, nil
}
func (l *publicFileDownloader) BucketName(_ context.Context, id string) string {
return id
}
const maxMemory = 2 << 20
const paramFile = "file"
func UploadHandleFunc(s AssetsService, uploader Uploader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctxData := authz.GetCtxData(ctx)
err := r.ParseMultipartForm(maxMemory)
file, handler, err := r.FormFile(paramFile)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
defer func() {
err = file.Close()
logging.Log("UPLOAD-GDg34").OnError(err).Warn("could not close file")
}()
contentType := handler.Header.Get("content-type")
size := handler.Size
if !uploader.ContentTypeAllowed(contentType) {
s.ErrorHandler()(w, r, caos_errs.ThrowInvalidArgument(nil, "UPLOAD-Dbvfs", "invalid content-type"))
return
}
if size > uploader.MaxFileSize() {
s.ErrorHandler()(w, r, caos_errs.ThrowInvalidArgumentf(nil, "UPLOAD-Bfb32", "file to big, max file size is %v", uploader.MaxFileSize()))
return
}
bucketName := uploader.BucketName(ctxData)
objectName, err := uploader.ObjectName(ctxData)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
info, err := s.Commands().UploadAsset(ctx, bucketName, objectName, contentType, file, size)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
err = uploader.Callback(ctx, info, ctxData.OrgID, s.Commands())
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
}
}
func DownloadHandleFunc(s AssetsService, downloader Downloader) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if s.Storage() == nil {
return
}
ctx := r.Context()
id := mux.Vars(r)["id"]
bucketName := downloader.BucketName(ctx, id)
path := ""
if id != "" {
path = strings.Split(r.RequestURI, id+"/")[1]
}
objectName, err := downloader.ObjectName(ctx, path)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
if objectName == "" {
s.ErrorHandler()(w, r, caos_errs.ThrowNotFound(nil, "UPLOAD-adf4f", "file not found"))
return
}
reader, getInfo, err := s.Storage().GetObject(ctx, bucketName, objectName)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
data, err := ioutil.ReadAll(reader)
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
info, err := getInfo()
if err != nil {
s.ErrorHandler()(w, r, err)
return
}
w.Header().Set("content-length", strconv.FormatInt(info.Size, 10))
w.Header().Set("content-type", info.ContentType)
w.Header().Set("ETag", info.ETag)
w.Write(data)
}
}

View File

@@ -0,0 +1,119 @@
Services:
IAM:
Prefix: "/iam"
Methods:
DefaultLabelPolicyLogo:
Path: "/policy/label/logo"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: iam.policy.write
- Name: Get
Comment:
Type: download
Permission: iam.policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: iam.policy.read
DefaultLabelPolicyIcon:
Path: "/policy/label/icon"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: iam.policy.write
- Name: Get
Comment:
Type: download
Permission: iam.policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: iam.policy.read
DefaultLabelPolicyFont:
Path: "/policy/label/font"
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: iam.policy.write
- Name: Get
Comment:
Type: download
Permission: iam.policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: iam.policy.read
Org:
Prefix: "/org"
Methods:
OrgLabelPolicyLogo:
Path: "/policy/label/logo"
Feature: "label_policy.private_label"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: policy.write
- Name: Get
Comment:
Type: download
Permission: policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: policy.read
OrgLabelPolicyIcon:
Path: "/policy/label/icon"
Feature: "label_policy.private_label"
HasDarkMode: true
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: policy.write
- Name: Get
Comment:
Type: download
Permission: policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: policy.read
OrgLabelPolicyFont:
Path: "/policy/label/font"
Feature: "label_policy.private_label"
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: policy.write
- Name: Get
Comment:
Type: download
Permission: policy.read
- Name: GetPreview
Comment:
Type: preview
Permission: policy.read
Users:
Prefix: "/users"
Methods:
MyUserAvatar:
Path: "/me/avatar"
Features: "label_policy.private_label"
Handlers:
- Name: Upload
Comment:
Type: upload
Permission: authenticated
- Name: Get
Comment:
Type: download
Permission: authenticated

View File

@@ -0,0 +1,200 @@
package main
import (
"flag"
"io"
"os"
"text/template"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/config"
)
var (
directory = flag.String("directory", "./", "working directory: asset.yaml must be in this directory, files will be generated into parent directory")
)
func main() {
flag.Parse()
configFile := *directory + "asset.yaml"
authz, err := os.OpenFile(*directory+"../authz.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-Gn31f").OnError(err).Fatal("cannot open authz file")
router, err := os.OpenFile(*directory+"../router.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-ABen3").OnError(err).Fatal("cannot open router file")
GenerateAssetHandler(configFile, authz, router)
}
type Method struct {
Path string
Feature string
HasDarkMode bool
Handlers []Handler
}
type Handler struct {
Name string
Comment string
Type HandlerType
Permission string
}
func (a Handler) Method() string {
if a.Type == MethodTypeUpload {
return "POST"
}
return "GET"
}
func (a Handler) PathSuffix() string {
if a.Type == MethodTypePreview {
return "/_preview"
}
return ""
}
func (a Handler) MethodReturn() string {
if a.Type == MethodTypeUpload {
return "Uploader"
}
if a.Type == MethodTypeDownload {
return "Downloader"
}
if a.Type == MethodTypePreview {
return "Downloader"
}
return ""
}
func (a Handler) HandlerType() string {
if a.Type == MethodTypeUpload {
return "UploadHandleFunc"
}
if a.Type == MethodTypeDownload {
return "DownloadHandleFunc"
}
if a.Type == MethodTypePreview {
return "DownloadHandleFunc"
}
return ""
}
type HandlerType string
const (
MethodTypeUpload = "upload"
MethodTypeDownload = "download"
MethodTypePreview = "preview"
)
type Services map[string]Service
type Service struct {
Prefix string
Methods map[string]Method
}
func GenerateAssetHandler(configFilePath string, output io.Writer, output2 io.Writer) {
conf := new(struct {
Services Services
})
err := config.Read(conf, configFilePath)
logging.Log("ASSETS-Dgbn4").OnError(err).Fatal("cannot read config")
tmplAuthz, err := template.New("").Parse(authzTmpl)
logging.Log("ASSETS-BGbbg").OnError(err).Fatal("cannot parse authz template")
tmplRouter, err := template.New("").Parse(routerTmpl)
logging.Log("ASSETS-gh4rq").OnError(err).Fatal("cannot parse router template")
data := &struct {
GoPkgName string
Name string
Prefix string
Services Services
}{
GoPkgName: "assets",
Name: "AssetsService",
Prefix: "/assets/v1",
Services: conf.Services,
}
err = tmplAuthz.Execute(output, data)
logging.Log("ASSETS-BHngj").OnError(err).Fatal("cannot generate authz")
err = tmplRouter.Execute(output2, data)
logging.Log("ASSETS-Bfd41").OnError(err).Fatal("cannot generate router")
}
const authzTmpl = `package {{.GoPkgName}}
import (
"github.com/caos/zitadel/internal/api/authz"
)
/**
* {{.Name}}
*/
{{ $prefix := .Prefix }}
var {{.Name}}_AuthMethods = authz.MethodMapping {
{{ range $service := .Services}}
{{ range $method := .Methods}}
{{ range $handler := .Handlers}}
{{ if (or $method.Feature $handler.Permission) }}
"{{$handler.Method}}:{{$prefix}}{{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}}": authz.Option{
Permission: "{{$handler.Permission}}",
Feature: "{{$method.Feature}}",
},
{{ if $method.HasDarkMode }}
"{{$handler.Method}}:{{$prefix}}{{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}}": authz.Option{
Permission: "{{$handler.Permission}}",
Feature: "{{$method.Feature}}",
},
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
}
`
const routerTmpl = `package {{.GoPkgName}}
import (
"github.com/gorilla/mux"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/static"
)
type {{.Name}} interface {
AuthInterceptor() *http_mw.AuthInterceptor
Commands() *command.Commands
ErrorHandler() ErrorHandler
Storage() static.Storage
{{ range $service := .Services}}
{{ range $methodName, $method := .Methods}}
{{ range $handler := .Handlers}}
{{$handler.Name}}{{$methodName}}() {{if $handler.MethodReturn}}{{$handler.MethodReturn}}{{end}}
{{ if $method.HasDarkMode }}
{{$handler.Name}}{{$methodName}}Dark() {{if $handler.MethodReturn}}{{$handler.MethodReturn}}{{end}}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
}
func RegisterRoutes(router *mux.Router, s {{.Name}}) {
router.Use(s.AuthInterceptor().Handler)
{{ range $service := .Services}}
{{ range $methodName, $method := .Methods}}
{{ range $handler := .Handlers}}
router.Path("{{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}}").Methods("{{$handler.Method}}").HandlerFunc({{if $handler.HandlerType}}{{$handler.HandlerType}}(s, {{end}}s.{{$handler.Name}}{{$methodName}}(){{if $handler.HandlerType}}){{end}})
{{ if $method.HasDarkMode }}
router.Path("{{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}}").Methods("{{$handler.Method}}").HandlerFunc({{if $handler.HandlerType}}{{$handler.HandlerType}}(s, {{end}}s.{{$handler.Name}}{{$methodName}}Dark(){{if $handler.HandlerType}}){{end}})
{{ end }}
{{ end }}
{{ end }}
{{ end }}
}
`

View File

@@ -0,0 +1,377 @@
package assets
import (
"context"
"strings"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/management/repository"
)
func (h *Handler) UploadDefaultLabelPolicyLogo() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, false, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadDefaultLabelPolicyLogoDark() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, true, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyLogo() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, false, false, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyLogoDark() Uploader {
return &labelPolicyLogoUploader{h.idGenerator, true, false, []string{"image/"}, 1 << 19}
}
type labelPolicyLogoUploader struct {
idGenerator id.Generator
darkMode bool
defaultPolicy bool
contentTypes []string
maxSize int64
}
func (l *labelPolicyLogoUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *labelPolicyLogoUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *labelPolicyLogoUploader) ObjectName(_ authz.CtxData) (string, error) {
suffixID, err := l.idGenerator.Next()
if err != nil {
return "", err
}
prefix := domain.LabelPolicyLogoPath
if l.darkMode {
return prefix + "-" + domain.Dark + "-" + suffixID, nil
}
return prefix + "-" + suffixID, nil
}
func (l *labelPolicyLogoUploader) BucketName(ctxData authz.CtxData) string {
if l.defaultPolicy {
return domain.IAMID
}
return ctxData.OrgID
}
func (l *labelPolicyLogoUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
if l.defaultPolicy {
if l.darkMode {
_, err := commands.AddLogoDarkDefaultLabelPolicy(ctx, info.Key)
return err
}
_, err := commands.AddLogoDefaultLabelPolicy(ctx, info.Key)
return err
}
if l.darkMode {
_, err := commands.AddLogoDarkLabelPolicy(ctx, orgID, info.Key)
return err
}
_, err := commands.AddLogoLabelPolicy(ctx, orgID, info.Key)
return err
}
func (h *Handler) GetDefaultLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: false}
}
func (h *Handler) GetDefaultLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: false}
}
func (h *Handler) GetPreviewDefaultLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: true}
}
func (h *Handler) GetPreviewDefaultLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: true}
}
func (h *Handler) GetOrgLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: false}
}
func (h *Handler) GetOrgLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: false}
}
func (h *Handler) GetPreviewOrgLabelPolicyLogo() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: true}
}
func (h *Handler) GetPreviewOrgLabelPolicyLogoDark() Downloader {
return &labelPolicyLogoDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: true}
}
type labelPolicyLogoDownloader struct {
org repository.OrgRepository
darkMode bool
defaultPolicy bool
preview bool
}
func (l *labelPolicyLogoDownloader) ObjectName(ctx context.Context, path string) (string, error) {
policy, err := getLabelPolicy(ctx, l.defaultPolicy, l.preview, l.org)
if err != nil {
return "", nil
}
if l.darkMode {
return policy.LogoDarkURL, nil
}
return policy.LogoURL, nil
}
func (l *labelPolicyLogoDownloader) BucketName(ctx context.Context, id string) string {
if l.defaultPolicy {
return domain.IAMID
}
return authz.GetCtxData(ctx).OrgID
}
func (h *Handler) UploadDefaultLabelPolicyIcon() Uploader {
return &labelPolicyIconUploader{h.idGenerator, false, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadDefaultLabelPolicyIconDark() Uploader {
return &labelPolicyIconUploader{h.idGenerator, true, true, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyIcon() Uploader {
return &labelPolicyIconUploader{h.idGenerator, false, false, []string{"image/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyIconDark() Uploader {
return &labelPolicyIconUploader{h.idGenerator, true, false, []string{"image/"}, 1 << 19}
}
type labelPolicyIconUploader struct {
idGenerator id.Generator
darkMode bool
defaultPolicy bool
contentTypes []string
maxSize int64
}
func (l *labelPolicyIconUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *labelPolicyIconUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *labelPolicyIconUploader) ObjectName(_ authz.CtxData) (string, error) {
suffixID, err := l.idGenerator.Next()
if err != nil {
return "", err
}
prefix := domain.LabelPolicyIconPath
if l.darkMode {
return prefix + "-" + domain.Dark + "-" + suffixID, nil
}
return prefix + "-" + suffixID, nil
}
func (l *labelPolicyIconUploader) BucketName(ctxData authz.CtxData) string {
if l.defaultPolicy {
return domain.IAMID
}
return ctxData.OrgID
}
func (l *labelPolicyIconUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
if l.defaultPolicy {
if l.darkMode {
_, err := commands.AddIconDarkDefaultLabelPolicy(ctx, info.Key)
return err
}
_, err := commands.AddIconDefaultLabelPolicy(ctx, info.Key)
return err
}
if l.darkMode {
_, err := commands.AddIconDarkLabelPolicy(ctx, orgID, info.Key)
return err
}
_, err := commands.AddIconLabelPolicy(ctx, orgID, info.Key)
return err
}
func (h *Handler) GetDefaultLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: false}
}
func (h *Handler) GetDefaultLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: false}
}
func (h *Handler) GetPreviewDefaultLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: true, preview: true}
}
func (h *Handler) GetPreviewDefaultLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: true, preview: true}
}
func (h *Handler) GetOrgLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: false}
}
func (h *Handler) GetOrgLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: false}
}
func (h *Handler) GetPreviewOrgLabelPolicyIcon() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: false, defaultPolicy: false, preview: true}
}
func (h *Handler) GetPreviewOrgLabelPolicyIconDark() Downloader {
return &labelPolicyIconDownloader{org: h.orgRepo, darkMode: true, defaultPolicy: false, preview: true}
}
type labelPolicyIconDownloader struct {
org repository.OrgRepository
darkMode bool
defaultPolicy bool
preview bool
}
func (l *labelPolicyIconDownloader) ObjectName(ctx context.Context, path string) (string, error) {
policy, err := getLabelPolicy(ctx, l.defaultPolicy, l.preview, l.org)
if err != nil {
return "", nil
}
if l.darkMode {
return policy.IconDarkURL, nil
}
return policy.IconURL, nil
}
func (l *labelPolicyIconDownloader) BucketName(ctx context.Context, id string) string {
if l.defaultPolicy {
return domain.IAMID
}
return authz.GetCtxData(ctx).OrgID
}
func (h *Handler) UploadDefaultLabelPolicyFont() Uploader {
return &labelPolicyFontUploader{h.idGenerator, true, []string{"font/"}, 1 << 19}
}
func (h *Handler) UploadOrgLabelPolicyFont() Uploader {
return &labelPolicyFontUploader{h.idGenerator, false, []string{"font/"}, 1 << 19}
}
type labelPolicyFontUploader struct {
idGenerator id.Generator
defaultPolicy bool
contentTypes []string
maxSize int64
}
func (l *labelPolicyFontUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *labelPolicyFontUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *labelPolicyFontUploader) ObjectName(_ authz.CtxData) (string, error) {
suffixID, err := l.idGenerator.Next()
if err != nil {
return "", err
}
prefix := domain.LabelPolicyFontPath
return prefix + "-" + suffixID, nil
}
func (l *labelPolicyFontUploader) BucketName(ctxData authz.CtxData) string {
if l.defaultPolicy {
return domain.IAMID
}
return ctxData.OrgID
}
func (l *labelPolicyFontUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
if l.defaultPolicy {
_, err := commands.AddFontDefaultLabelPolicy(ctx, info.Key)
return err
}
_, err := commands.AddFontLabelPolicy(ctx, orgID, info.Key)
return err
}
func (h *Handler) GetDefaultLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: true, preview: false}
}
func (h *Handler) GetPreviewDefaultLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: true, preview: true}
}
func (h *Handler) GetOrgLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: false, preview: false}
}
func (h *Handler) GetPreviewOrgLabelPolicyFont() Downloader {
return &labelPolicyFontDownloader{org: h.orgRepo, defaultPolicy: true, preview: true}
}
type labelPolicyFontDownloader struct {
org repository.OrgRepository
defaultPolicy bool
preview bool
}
func (l *labelPolicyFontDownloader) ObjectName(ctx context.Context, path string) (string, error) {
policy, err := getLabelPolicy(ctx, l.defaultPolicy, l.preview, l.org)
if err != nil {
return "", nil
}
return policy.FontURL, nil
}
func (l *labelPolicyFontDownloader) BucketName(ctx context.Context, id string) string {
if l.defaultPolicy {
return domain.IAMID
}
return authz.GetCtxData(ctx).OrgID
}
func getLabelPolicy(ctx context.Context, defaultPolicy, preview bool, orgRepo repository.OrgRepository) (*model.LabelPolicyView, error) {
if defaultPolicy {
if preview {
return orgRepo.GetPreviewDefaultLabelPolicy(ctx)
}
return orgRepo.GetDefaultLabelPolicy(ctx)
}
if preview {
return orgRepo.GetPreviewLabelPolicy(ctx)
}
return orgRepo.GetLabelPolicy(ctx)
}

View File

@@ -0,0 +1,59 @@
package assets
import (
"context"
"strings"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
)
func (h *Handler) UploadMyUserAvatar() Uploader {
return &myHumanAvatarUploader{[]string{"image/"}, 1 << 19}
}
type myHumanAvatarUploader struct {
contentTypes []string
maxSize int64
}
func (l *myHumanAvatarUploader) ContentTypeAllowed(contentType string) bool {
for _, ct := range l.contentTypes {
if strings.HasPrefix(contentType, ct) {
return true
}
}
return false
}
func (l *myHumanAvatarUploader) MaxFileSize() int64 {
return l.maxSize
}
func (l *myHumanAvatarUploader) ObjectName(ctxData authz.CtxData) (string, error) {
return domain.GetHumanAvatarAssetPath(ctxData.UserID), nil
}
func (l *myHumanAvatarUploader) BucketName(ctxData authz.CtxData) string {
return ctxData.OrgID
}
func (l *myHumanAvatarUploader) Callback(ctx context.Context, info *domain.AssetInfo, orgID string, commands *command.Commands) error {
_, err := commands.AddHumanAvatar(ctx, orgID, authz.GetCtxData(ctx).UserID, info.Key)
return err
}
func (h *Handler) GetMyUserAvatar() Downloader {
return &myHumanAvatarDownloader{}
}
type myHumanAvatarDownloader struct{}
func (l *myHumanAvatarDownloader) ObjectName(ctx context.Context, path string) (string, error) {
return domain.GetHumanAvatarAssetPath(authz.GetCtxData(ctx).UserID), nil
}
func (l *myHumanAvatarDownloader) BucketName(ctx context.Context, id string) string {
return authz.GetCtxData(ctx).OrgID
}

View File

@@ -71,7 +71,8 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
LoginPolicyUsernameLogin: req.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: req.LoginPolicyPasswordReset,
PasswordComplexityPolicy: req.PasswordComplexityPolicy,
LabelPolicy: req.LabelPolicy,
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
}
}
@@ -90,7 +91,8 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
LoginPolicyUsernameLogin: req.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: req.LoginPolicyPasswordReset,
PasswordComplexityPolicy: req.PasswordComplexityPolicy,
LabelPolicy: req.LabelPolicy,
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
}
}

View File

@@ -16,6 +16,14 @@ func (s *Server) GetLabelPolicy(ctx context.Context, req *admin_pb.GetLabelPolic
return &admin_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) GetPreviewLabelPolicy(ctx context.Context, req *admin_pb.GetPreviewLabelPolicyRequest) (*admin_pb.GetPreviewLabelPolicyResponse, error) {
policy, err := s.iam.GetDefaultPreviewLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.GetPreviewLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) UpdateLabelPolicy(ctx context.Context, req *admin_pb.UpdateLabelPolicyRequest) (*admin_pb.UpdateLabelPolicyResponse, error) {
policy, err := s.command.ChangeDefaultLabelPolicy(ctx, updateLabelPolicyToDomain(req))
if err != nil {
@@ -29,3 +37,87 @@ func (s *Server) UpdateLabelPolicy(ctx context.Context, req *admin_pb.UpdateLabe
),
}, nil
}
func (s *Server) ActivateLabelPolicy(ctx context.Context, req *admin_pb.ActivateLabelPolicyRequest) (*admin_pb.ActivateLabelPolicyResponse, error) {
policy, err := s.command.ActivateDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.ActivateLabelPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyLogo(ctx context.Context, req *admin_pb.RemoveLabelPolicyLogoRequest) (*admin_pb.RemoveLabelPolicyLogoResponse, error) {
policy, err := s.command.RemoveLogoDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyLogoResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyLogoDark(ctx context.Context, req *admin_pb.RemoveLabelPolicyLogoDarkRequest) (*admin_pb.RemoveLabelPolicyLogoDarkResponse, error) {
policy, err := s.command.RemoveLogoDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyLogoDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyIcon(ctx context.Context, req *admin_pb.RemoveLabelPolicyIconRequest) (*admin_pb.RemoveLabelPolicyIconResponse, error) {
policy, err := s.command.RemoveIconDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyIconResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyIconDark(ctx context.Context, req *admin_pb.RemoveLabelPolicyIconDarkRequest) (*admin_pb.RemoveLabelPolicyIconDarkResponse, error) {
policy, err := s.command.RemoveIconDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyIconDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveLabelPolicyFont(ctx context.Context, req *admin_pb.RemoveLabelPolicyFontRequest) (*admin_pb.RemoveLabelPolicyFontResponse, error) {
policy, err := s.command.RemoveFontDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &admin_pb.RemoveLabelPolicyFontResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}

View File

@@ -8,7 +8,14 @@ import (
func updateLabelPolicyToDomain(policy *admin_pb.UpdateLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
BackgroundColor: policy.BackgroundColor,
WarnColor: policy.WarnColor,
FontColor: policy.FontColor,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
DisableWatermark: policy.DisableWatermark,
}
}

View File

@@ -0,0 +1,20 @@
package auth
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
)
func (s *Server) RemoveMyAvatar(ctx context.Context, req *auth_pb.RemoveMyAvatarRequest) (*auth_pb.RemoveMyAvatarResponse, error) {
ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.RemoveHumanAvatar(ctx, ctxData.ResourceOwner, ctxData.UserID)
if err != nil {
return nil, err
}
return &auth_pb.RemoveMyAvatarResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}

View File

@@ -1,6 +1,8 @@
package auth
import (
"google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/server"
"github.com/caos/zitadel/internal/auth/repository"
@@ -8,7 +10,6 @@ import (
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/auth"
"google.golang.org/grpc"
)
var _ auth.AuthServiceServer = (*Server)(nil)

View File

@@ -23,8 +23,10 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: features.LoginPolicyPasswordReset,
PasswordComplexityPolicy: features.PasswordComplexityPolicy,
LabelPolicy: features.LabelPolicy,
LabelPolicy: features.LabelPolicyPrivateLabel,
CustomDomain: features.CustomDomain,
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
}
}

View File

@@ -14,7 +14,15 @@ func (s *Server) GetLabelPolicy(ctx context.Context, req *mgmt_pb.GetLabelPolicy
if err != nil {
return nil, err
}
return &mgmt_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
return &mgmt_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetPreviewLabelPolicy(ctx context.Context, req *mgmt_pb.GetPreviewLabelPolicyRequest) (*mgmt_pb.GetPreviewLabelPolicyResponse, error) {
policy, err := s.org.GetPreviewLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetPreviewLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) GetDefaultLabelPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLabelPolicyRequest) (*mgmt_pb.GetDefaultLabelPolicyResponse, error) {
@@ -53,6 +61,20 @@ func (s *Server) UpdateCustomLabelPolicy(ctx context.Context, req *mgmt_pb.Updat
}, nil
}
func (s *Server) ActivateCustomLabelPolicy(ctx context.Context, req *mgmt_pb.ActivateCustomLabelPolicyRequest) (*mgmt_pb.ActivateCustomLabelPolicyResponse, error) {
policy, err := s.command.ActivateLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
return &mgmt_pb.ActivateCustomLabelPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) ResetLabelPolicyToDefault(ctx context.Context, req *mgmt_pb.ResetLabelPolicyToDefaultRequest) (*mgmt_pb.ResetLabelPolicyToDefaultResponse, error) {
objectDetails, err := s.command.RemoveLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
@@ -62,3 +84,73 @@ func (s *Server) ResetLabelPolicyToDefault(ctx context.Context, req *mgmt_pb.Res
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyLogo(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyLogoRequest) (*mgmt_pb.RemoveCustomLabelPolicyLogoResponse, error) {
policy, err := s.command.RemoveLogoDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyLogoResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyLogoDark(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyLogoDarkRequest) (*mgmt_pb.RemoveCustomLabelPolicyLogoDarkResponse, error) {
policy, err := s.command.RemoveLogoDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyLogoDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyIcon(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyIconRequest) (*mgmt_pb.RemoveCustomLabelPolicyIconResponse, error) {
policy, err := s.command.RemoveIconDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyIconResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyIconDark(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyIconDarkRequest) (*mgmt_pb.RemoveCustomLabelPolicyIconDarkResponse, error) {
policy, err := s.command.RemoveIconDarkDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyIconDarkResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) RemoveCustomLabelPolicyFont(ctx context.Context, req *mgmt_pb.RemoveCustomLabelPolicyFontRequest) (*mgmt_pb.RemoveCustomLabelPolicyFontResponse, error) {
policy, err := s.command.RemoveFontDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveCustomLabelPolicyFontResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.EventDate,
policy.ResourceOwner,
),
}, nil
}

View File

@@ -8,15 +8,27 @@ import (
func addLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
BackgroundColor: p.BackgroundColor,
WarnColor: p.WarnColor,
PrimaryColorDark: p.PrimaryColorDark,
BackgroundColorDark: p.BackgroundColorDark,
WarnColorDark: p.WarnColorDark,
HideLoginNameSuffix: p.HideLoginNameSuffix,
DisableWatermark: p.DisableWatermark,
}
}
func updateLabelPolicyToDomain(p *mgmt_pb.UpdateCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
BackgroundColor: p.BackgroundColor,
WarnColor: p.WarnColor,
FontColor: p.FontColor,
PrimaryColorDark: p.PrimaryColorDark,
BackgroundColorDark: p.BackgroundColorDark,
WarnColorDark: p.WarnColorDark,
FontColorDark: p.FontColorDark,
HideLoginNameSuffix: p.HideLoginNameSuffix,
DisableWatermark: p.DisableWatermark,
}
}

View File

@@ -18,7 +18,7 @@ func (s *Server) GetLoginPolicy(ctx context.Context, req *mgmt_pb.GetLoginPolicy
if err != nil {
return nil, err
}
return &mgmt_pb.GetLoginPolicyResponse{Policy: policy_grpc.ModelLoginPolicyToPb(policy)}, nil
return &mgmt_pb.GetLoginPolicyResponse{Policy: policy_grpc.ModelLoginPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultLoginPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLoginPolicyRequest) (*mgmt_pb.GetDefaultLoginPolicyResponse, error) {

View File

@@ -14,7 +14,8 @@ func (s *Server) GetPasswordAgePolicy(ctx context.Context, req *mgmt_pb.GetPassw
return nil, err
}
return &mgmt_pb.GetPasswordAgePolicyResponse{
Policy: policy_grpc.ModelPasswordAgePolicyToPb(policy),
Policy: policy_grpc.ModelPasswordAgePolicyToPb(policy),
IsDefault: policy.Default,
}, nil
}

View File

@@ -13,7 +13,7 @@ func (s *Server) GetPasswordComplexityPolicy(ctx context.Context, req *mgmt_pb.G
if err != nil {
return nil, err
}
return &mgmt_pb.GetPasswordComplexityPolicyResponse{Policy: policy_grpc.ModelPasswordComplexityPolicyToPb(policy)}, nil
return &mgmt_pb.GetPasswordComplexityPolicyResponse{Policy: policy_grpc.ModelPasswordComplexityPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultPasswordComplexityPolicy(ctx context.Context, req *mgmt_pb.GetDefaultPasswordComplexityPolicyRequest) (*mgmt_pb.GetDefaultPasswordComplexityPolicyResponse, error) {

View File

@@ -13,7 +13,7 @@ func (s *Server) GetPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.GetP
if err != nil {
return nil, err
}
return &mgmt_pb.GetPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy)}, nil
return &mgmt_pb.GetPasswordLockoutPolicyResponse{Policy: policy_grpc.ModelPasswordLockoutPolicyToPb(policy), IsDefault: policy.Default}, nil
}
func (s *Server) GetDefaultPasswordLockoutPolicy(ctx context.Context, req *mgmt_pb.GetDefaultPasswordLockoutPolicyRequest) (*mgmt_pb.GetDefaultPasswordLockoutPolicyResponse, error) {

View File

@@ -323,6 +323,17 @@ func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.
}, nil
}
func (s *Server) RemoveHumanAvatar(ctx context.Context, req *mgmt_pb.RemoveHumanAvatarRequest) (*mgmt_pb.RemoveHumanAvatarResponse, error) {
ctxData := authz.GetCtxData(ctx)
objectDetails, err := s.command.RemoveHumanAvatar(ctx, ctxData.OrgID, req.UserId)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveHumanAvatarResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}
func (s *Server) SetHumanInitialPassword(ctx context.Context, req *mgmt_pb.SetHumanInitialPasswordRequest) (*mgmt_pb.SetHumanInitialPasswordResponse, error) {
objectDetails, err := s.command.SetPassword(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.Password, true)
if err != nil {

View File

@@ -10,7 +10,20 @@ func ModelLabelPolicyToPb(policy *model.LabelPolicyView) *policy_pb.LabelPolicy
return &policy_pb.LabelPolicy{
IsDefault: policy.Default,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
BackgroundColor: policy.BackgroundColor,
FontColor: policy.FontColor,
WarnColor: policy.WarnColor,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
FontUrl: policy.FontURL,
LogoUrl: policy.LogoURL,
LogoUrlDark: policy.LogoDarkURL,
IconUrl: policy.IconURL,
IconUrlDark: policy.IconDarkURL,
DisableWatermark: policy.DisableWatermark,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
Details: object.ToViewDetailsPb(
policy.Sequence,

View File

@@ -80,6 +80,14 @@ func RemoteIPStringFromRequest(r *http.Request) string {
return r.RemoteAddr
}
func GetAuthorization(r *http.Request) string {
return r.Header.Get(Authorization)
}
func GetOrgID(r *http.Request) string {
return r.Header.Get(ZitadelOrgID)
}
func GetForwardedFor(headers http.Header) (string, bool) {
forwarded, ok := headers[ForwardedFor]
if ok {

View File

@@ -0,0 +1,71 @@
package middleware
import (
"context"
"errors"
"net/http"
"github.com/caos/zitadel/internal/api/authz"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
type AuthInterceptor struct {
verifier *authz.TokenVerifier
authConfig authz.Config
}
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) *AuthInterceptor {
return &AuthInterceptor{
verifier: verifier,
authConfig: authConfig,
}
}
func (a *AuthInterceptor) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, err := authorize(r, a.verifier, a.authConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func (a *AuthInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, err := authorize(r, a.verifier, a.authConfig)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
}
}
type httpReq struct{}
func authorize(r *http.Request, verifier *authz.TokenVerifier, authConfig authz.Config) (_ context.Context, err error) {
ctx := r.Context()
authOpt, needsToken := verifier.CheckAuthMethod(r.Method + ":" + r.RequestURI)
if !needsToken {
return ctx, nil
}
authCtx, span := tracing.NewServerInterceptorSpan(ctx)
defer func() { span.EndWithError(err) }()
authToken := http_util.GetAuthorization(r)
if authToken == "" {
return nil, errors.New("auth header missing")
}
ctxSetter, err := authz.CheckUserAuthorization(authCtx, &httpReq{}, authToken, http_util.GetOrgID(r), verifier, authConfig, authOpt, r.RequestURI) //TODO: permission
if err != nil {
return nil, err
}
span.End()
return ctxSetter(ctx), nil
}

View File

@@ -248,7 +248,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if request.RequestedOrgID == "" {
username = user.PreferredLoginName
}
request.SetUserInfo(user.ID, username, user.PreferredLoginName, user.DisplayName, user.ResourceOwner)
request.SetUserInfo(user.ID, username, user.PreferredLoginName, user.DisplayName, user.AvatarKey, user.ResourceOwner)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
@@ -466,7 +466,7 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
return err
}
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", user.ResourceOwner)
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", "", user.ResourceOwner)
return nil
}
@@ -509,7 +509,7 @@ func (repo *AuthRequestRepo) checkExternalUserLogin(request *domain.AuthRequest,
if err != nil {
return err
}
request.SetUserInfo(externalIDP.UserID, "", "", "", externalIDP.ResourceOwner)
request.SetUserInfo(externalIDP.UserID, "", "", "", "", externalIDP.ResourceOwner)
return nil
}
@@ -614,6 +614,7 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *domain.AuthRequest)
DisplayName: session.DisplayName,
UserName: session.UserName,
LoginName: session.LoginName,
AvatarKey: session.AvatarKey,
UserSessionState: auth_req_model.UserSessionStateToDomain(session.State),
SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner,
}
@@ -710,9 +711,9 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (
}
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*domain.LabelPolicy, error) {
policy, err := repo.View.LabelPolicyByAggregateID(orgID)
policy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
policy, err = repo.View.LabelPolicyByAggregateID(repo.IAMID)
policy, err = repo.View.LabelPolicyByAggregateIDAndState(repo.IAMID, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
@@ -103,3 +104,14 @@ func (repo *OrgRepository) GetMyPasswordComplexityPolicy(ctx context.Context) (*
}
return iam_view_model.PasswordComplexityViewToModel(policy), err
}
func (repo *OrgRepository) GetLabelPolicy(ctx context.Context, orgID string) (*iam_model.LabelPolicyView, error) {
orgPolicy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
orgPolicy, err = repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStateActive))
}
if err != nil {
return nil, err
}
return iam_view_model.LabelPolicyViewToModel(orgPolicy), nil
}

View File

@@ -2,6 +2,8 @@ package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
@@ -79,8 +81,19 @@ func (m *LabelPolicy) processLabelPolicy(event *models.Event) (err error) {
switch event.Type {
case iam_es_model.LabelPolicyAdded, model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged:
policy, err = m.view.LabelPolicyByAggregateID(event.AggregateID)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged,
iam_es_model.LabelPolicyActivated, model.LabelPolicyActivated,
iam_es_model.LabelPolicyLogoAdded, model.LabelPolicyLogoAdded,
iam_es_model.LabelPolicyLogoRemoved, model.LabelPolicyLogoRemoved,
iam_es_model.LabelPolicyIconAdded, model.LabelPolicyIconAdded,
iam_es_model.LabelPolicyIconRemoved, model.LabelPolicyIconRemoved,
iam_es_model.LabelPolicyLogoDarkAdded, model.LabelPolicyLogoDarkAdded,
iam_es_model.LabelPolicyLogoDarkRemoved, model.LabelPolicyLogoDarkRemoved,
iam_es_model.LabelPolicyIconDarkAdded, model.LabelPolicyIconDarkAdded,
iam_es_model.LabelPolicyIconDarkRemoved, model.LabelPolicyIconDarkRemoved,
iam_es_model.LabelPolicyFontAdded, model.LabelPolicyFontAdded,
iam_es_model.LabelPolicyFontRemoved, model.LabelPolicyFontRemoved:
policy, err = m.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}

View File

@@ -122,6 +122,8 @@ func (u *User) ProcessUser(event *es_models.Event) (err error) {
es_model.HumanProfileChanged,
es_model.HumanEmailChanged,
es_model.HumanEmailVerified,
es_model.HumanAvatarAdded,
es_model.HumanAvatarRemoved,
es_model.HumanPhoneChanged,
es_model.HumanPhoneVerified,
es_model.HumanPhoneRemoved,

View File

@@ -111,6 +111,8 @@ func (u *UserSession) Reduce(event *models.Event) (err error) {
es_model.HumanPasswordChanged,
es_model.HumanMFAOTPRemoved,
es_model.HumanProfileChanged,
es_model.HumanAvatarAdded,
es_model.HumanAvatarRemoved,
es_model.DomainClaimed,
es_model.UserUserNameChanged,
es_model.HumanExternalIDPRemoved,
@@ -167,5 +169,6 @@ func (u *UserSession) fillUserInfo(session *view_model.UserSessionView, id strin
session.UserName = user.UserName
session.LoginName = user.PreferredLoginName
session.DisplayName = user.DisplayName
session.AvatarKey = user.AvatarKey
return nil
}

View File

@@ -12,8 +12,8 @@ const (
labelPolicyTable = "auth.label_policies"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTable, aggregateID, state)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {

View File

@@ -12,4 +12,5 @@ type OrgRepository interface {
GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error)
GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error)
GetMyPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)
GetLabelPolicy(ctx context.Context, orgID string) (*iam_model.LabelPolicyView, error)
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
@@ -130,13 +131,21 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
if err := checkLoginPolicyFeatures(features, requiredFeature); err != nil {
return err
}
continue
}
if requiredFeature == domain.FeaturePasswordComplexityPolicy && !features.PasswordComplexityPolicy {
return MissingFeatureErr(requiredFeature)
if requiredFeature == domain.FeaturePasswordComplexityPolicy {
if !features.PasswordComplexityPolicy {
return MissingFeatureErr(requiredFeature)
}
continue
}
if requiredFeature == domain.FeatureLabelPolicy && !features.PasswordComplexityPolicy {
return MissingFeatureErr(requiredFeature)
if strings.HasPrefix(requiredFeature, domain.FeatureLabelPolicy) {
if err := checkLabelPolicyFeatures(features, requiredFeature); err != nil {
return err
}
continue
}
return MissingFeatureErr(requiredFeature)
}
return nil
}
@@ -175,6 +184,20 @@ func checkLoginPolicyFeatures(features *features_view_model.FeaturesView, requir
return nil
}
func checkLabelPolicyFeatures(features *features_view_model.FeaturesView, requiredFeature string) error {
switch requiredFeature {
case domain.FeatureLabelPolicyPrivateLabel:
if !features.LabelPolicyPrivateLabel {
return MissingFeatureErr(requiredFeature)
}
case domain.FeatureLabelPolicyWatermark:
if !features.LabelPolicyWatermark {
return MissingFeatureErr(requiredFeature)
}
}
return nil
}
func MissingFeatureErr(feature string) error {
return caos_errs.ThrowPermissionDeniedf(nil, "AUTH-Dvgsf", "missing feature %v", feature)
}

View File

@@ -20,12 +20,14 @@ import (
proj_repo "github.com/caos/zitadel/internal/repository/project"
usr_repo "github.com/caos/zitadel/internal/repository/user"
usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/telemetry/tracing"
webauthn_helper "github.com/caos/zitadel/internal/webauthn"
)
type Commands struct {
eventstore *eventstore.Eventstore
static static.Storage
idGenerator id.Generator
iamDomain string
zitadelRoles []authz.RoleMapping
@@ -58,9 +60,10 @@ type Config struct {
Eventstore types.SQLUser
}
func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, authZRepo *authz_repo.EsRepository) (repo *Commands, err error) {
func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, staticStore static.Storage, authZRepo *authz_repo.EsRepository) (repo *Commands, err error) {
repo = &Commands{
eventstore: eventstore,
static: staticStore,
idGenerator: id.SonyFlakeGenerator,
iamDomain: defaults.Domain,
zitadelRoles: authZConfig.RolePermissionMappings,

View File

@@ -0,0 +1,54 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
)
type ExistingLabelPoliciesReadModel struct {
eventstore.WriteModel
aggregateIDs []string
}
func NewExistingLabelPoliciesReadModel(ctx context.Context) *ExistingLabelPoliciesReadModel {
return &ExistingLabelPoliciesReadModel{}
}
func (rm *ExistingLabelPoliciesReadModel) AppendEvents(events ...eventstore.EventReader) {
rm.WriteModel.AppendEvents(events...)
}
func (rm *ExistingLabelPoliciesReadModel) Reduce() error {
for _, event := range rm.Events {
switch e := event.(type) {
case *iam.LabelPolicyAddedEvent,
*org.LabelPolicyAddedEvent:
rm.aggregateIDs = append(rm.aggregateIDs, e.Aggregate().ID)
case *org.LabelPolicyRemovedEvent:
for i := len(rm.aggregateIDs) - 1; i >= 0; i-- {
if rm.aggregateIDs[i] == e.Aggregate().ID {
copy(rm.aggregateIDs[i:], rm.aggregateIDs[i+1:])
rm.aggregateIDs[len(rm.aggregateIDs)-1] = ""
rm.aggregateIDs = rm.aggregateIDs[:len(rm.aggregateIDs)-1]
}
}
}
}
return nil
}
func (rm *ExistingLabelPoliciesReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(
eventstore.ColumnsEvent,
iam.AggregateType,
org.AggregateType).
EventTypes(
iam.LabelPolicyAddedEventType,
org.LabelPolicyAddedEventType,
org.LabelPolicyRemovedEventType,
)
}

View File

@@ -23,7 +23,8 @@ type FeaturesWriteModel struct {
LoginPolicyUsernameLogin bool
LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
}
@@ -69,7 +70,13 @@ func (wm *FeaturesWriteModel) Reduce() error {
wm.PasswordComplexityPolicy = *e.PasswordComplexityPolicy
}
if e.LabelPolicy != nil {
wm.LabelPolicy = *e.LabelPolicy
wm.LabelPolicyPrivateLabel = *e.LabelPolicy
}
if e.LabelPolicyPrivateLabel != nil {
wm.LabelPolicyPrivateLabel = *e.LabelPolicyPrivateLabel
}
if e.LabelPolicyWatermark != nil {
wm.LabelPolicyWatermark = *e.LabelPolicyWatermark
}
if e.CustomDomain != nil {
wm.CustomDomain = *e.CustomDomain

View File

@@ -49,8 +49,16 @@ func writeModelToLabelPolicy(wm *LabelPolicyWriteModel) *domain.LabelPolicy {
return &domain.LabelPolicy{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
PrimaryColor: wm.PrimaryColor,
SecondaryColor: wm.SecondaryColor,
BackgroundColor: wm.BackgroundColor,
WarnColor: wm.WarnColor,
FontColor: wm.FontColor,
PrimaryColorDark: wm.PrimaryColorDark,
BackgroundColorDark: wm.BackgroundColorDark,
WarnColorDark: wm.WarnColorDark,
FontColorDark: wm.FontColorDark,
HideLoginNameSuffix: wm.HideLoginNameSuffix,
ErrorMsgPopup: wm.ErrorMsgPopup,
DisableWatermark: wm.DisableWatermark,
}
}
@@ -176,6 +184,8 @@ func writeModelToFeatures(wm *FeaturesWriteModel) *domain.Features {
LoginPolicyRegistration: wm.LoginPolicyRegistration,
LoginPolicyUsernameLogin: wm.LoginPolicyUsernameLogin,
PasswordComplexityPolicy: wm.PasswordComplexityPolicy,
LabelPolicy: wm.LabelPolicy,
LabelPolicyPrivateLabel: wm.LabelPolicyPrivateLabel,
LabelPolicyWatermark: wm.LabelPolicyWatermark,
CustomDomain: wm.CustomDomain,
}
}

View File

@@ -46,7 +46,8 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
features.LoginPolicyRegistration,
features.LoginPolicyUsernameLogin,
features.PasswordComplexityPolicy,
features.LabelPolicy,
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
)
if !hasChanged {
@@ -61,5 +62,7 @@ func (c *Commands) getDefaultFeatures(ctx context.Context) (*domain.Features, er
if err != nil {
return nil, err
}
return writeModelToFeatures(&existingFeatures.FeaturesWriteModel), nil
features := writeModelToFeatures(&existingFeatures.FeaturesWriteModel)
features.IsDefault = true
return features, nil
}

View File

@@ -62,7 +62,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
loginPolicyRegistration,
loginPolicyUsernameLogin,
passwordComplexityPolicy,
labelPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain bool,
) (*iam.FeaturesSetEvent, bool) {
@@ -101,8 +102,11 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
if wm.PasswordComplexityPolicy != passwordComplexityPolicy {
changes = append(changes, features.ChangePasswordComplexityPolicy(passwordComplexityPolicy))
}
if wm.LabelPolicy != labelPolicy {
changes = append(changes, features.ChangeLabelPolicy(labelPolicy))
if wm.LabelPolicyPrivateLabel != labelPolicyPrivateLabel {
changes = append(changes, features.ChangeLabelPolicyPrivateLabel(labelPolicyPrivateLabel))
}
if wm.LabelPolicyWatermark != labelPolicyWatermark {
changes = append(changes, features.ChangeLabelPolicyWatermark(labelPolicyWatermark))
}
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))

View File

@@ -29,8 +29,8 @@ func (c *Commands) AddDefaultLabelPolicy(ctx context.Context, policy *domain.Lab
}
func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMLabelPolicyWriteModel, policy *domain.LabelPolicy) (eventstore.EventPusher, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m9fo", "Errors.IAM.LabelPolicy.Invalid")
if err := policy.IsValid(); err != nil {
return nil, err
}
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -40,13 +40,26 @@ func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LabelPolicy.AlreadyExists")
}
return iam_repo.NewLabelPolicyAddedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix), nil
return iam_repo.NewLabelPolicyAddedEvent(
ctx,
iamAgg,
policy.PrimaryColor,
policy.BackgroundColor,
policy.WarnColor,
policy.FontColor,
policy.PrimaryColorDark,
policy.BackgroundColorDark,
policy.WarnColorDark,
policy.FontColorDark,
policy.HideLoginNameSuffix,
policy.ErrorMsgPopup,
policy.DisableWatermark), nil
}
func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.LabelPolicy) (*domain.LabelPolicy, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-33m8f", "Errors.IAM.LabelPolicy.Invalid")
if err := policy.IsValid(); err != nil {
return nil, err
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
@@ -57,7 +70,20 @@ func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.
return nil, caos_errs.ThrowNotFound(nil, "IAM-0K9dq", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
ctx,
iamAgg,
policy.PrimaryColor,
policy.BackgroundColor,
policy.WarnColor,
policy.FontColor,
policy.PrimaryColorDark,
policy.BackgroundColorDark,
policy.WarnColorDark,
policy.FontColorDark,
policy.HideLoginNameSuffix,
policy.ErrorMsgPopup,
policy.DisableWatermark)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged")
}
@@ -73,6 +99,275 @@ func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.
return writeModelToLabelPolicy(&existingPolicy.LabelPolicyWriteModel), nil
}
func (c *Commands) ActivateDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-6M23e", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyActivatedEvent(ctx, iamAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddLogoDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m20c", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-Qw0pd", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLogoDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-Xc8Kf", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.LogoKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoRemovedEvent(ctx, iamAgg, existingPolicy.LogoKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddIconDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-yxE4f", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-1yMx0", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIconDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M0qw", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.IconKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconRemovedEvent(ctx, iamAgg, existingPolicy.IconKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddLogoDarkDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4fMs9", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-ZR9fs", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoDarkAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLogoDarkDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-3FGds", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.LogoDarkKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyLogoDarkRemovedEvent(ctx, iamAgg, existingPolicy.LogoDarkKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddIconDarkDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-1cxM3", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-vMsf9", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconDarkAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIconDarkDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-2nc7F", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.IconDarkKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyIconDarkRemovedEvent(ctx, iamAgg, existingPolicy.IconDarkKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddFontDefaultLabelPolicy(ctx context.Context, storageKey string) (*domain.ObjectDetails, error) {
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-1N8fs", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-1N8fE", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyFontAddedEvent(ctx, iamAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveFontDefaultLabelPolicy(ctx context.Context) (*domain.ObjectDetails, error) {
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-Tk0gw", "Errors.IAM.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, domain.IAMID, existingPolicy.FontKey)
if err != nil {
return nil, err
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewLabelPolicyFontRemovedEvent(ctx, iamAgg, existingPolicy.FontKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) defaultLabelPolicyWriteModelByID(ctx context.Context) (policy *IAMLabelPolicyWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -84,3 +379,13 @@ func (c *Commands) defaultLabelPolicyWriteModelByID(ctx context.Context) (policy
}
return writeModel, nil
}
func (c *Commands) getDefaultLabelPolicy(ctx context.Context) (*domain.LabelPolicy, error) {
policyWriteModel, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err
}
policy := writeModelToLabelPolicy(&policyWriteModel.LabelPolicyWriteModel)
policy.Default = true
return policy, nil
}

View File

@@ -2,9 +2,9 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
)
@@ -31,6 +31,28 @@ func (wm *IAMLabelPolicyWriteModel) AppendEvents(events ...eventstore.EventReade
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyAddedEvent)
case *iam.LabelPolicyChangedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyChangedEvent)
case *iam.LabelPolicyActivatedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyActivatedEvent)
case *iam.LabelPolicyLogoAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoAddedEvent)
case *iam.LabelPolicyLogoRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoRemovedEvent)
case *iam.LabelPolicyLogoDarkAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoDarkAddedEvent)
case *iam.LabelPolicyLogoDarkRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoDarkRemovedEvent)
case *iam.LabelPolicyIconAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconAddedEvent)
case *iam.LabelPolicyIconRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconRemovedEvent)
case *iam.LabelPolicyIconDarkAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconDarkAddedEvent)
case *iam.LabelPolicyIconDarkRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconDarkRemovedEvent)
case *iam.LabelPolicyFontAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyFontAddedEvent)
case *iam.LabelPolicyFontRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyFontRemovedEvent)
}
}
}
@@ -45,26 +67,69 @@ func (wm *IAMLabelPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
ResourceOwner(wm.ResourceOwner).
EventTypes(
iam.LabelPolicyAddedEventType,
iam.LabelPolicyChangedEventType)
iam.LabelPolicyChangedEventType,
iam.LabelPolicyLogoAddedEventType,
iam.LabelPolicyLogoRemovedEventType,
iam.LabelPolicyIconAddedEventType,
iam.LabelPolicyIconRemovedEventType,
iam.LabelPolicyLogoDarkAddedEventType,
iam.LabelPolicyLogoDarkRemovedEventType,
iam.LabelPolicyIconDarkAddedEventType,
iam.LabelPolicyIconDarkRemovedEventType,
iam.LabelPolicyFontAddedEventType,
iam.LabelPolicyFontRemovedEventType,
)
}
func (wm *IAMLabelPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
backgroundColor,
warnColor,
fontColor,
primaryColorDark,
backgroundColorDark,
warnColorDark,
fontColorDark string,
hideLoginNameSuffix,
errorMsgPopup,
disableWatermark bool,
) (*iam.LabelPolicyChangedEvent, bool) {
changes := make([]policy.LabelPolicyChanges, 0)
if wm.PrimaryColor != primaryColor {
changes = append(changes, policy.ChangePrimaryColor(primaryColor))
}
if wm.SecondaryColor != secondaryColor {
changes = append(changes, policy.ChangeSecondaryColor(secondaryColor))
if wm.BackgroundColor != backgroundColor {
changes = append(changes, policy.ChangeBackgroundColor(backgroundColor))
}
if wm.WarnColor != warnColor {
changes = append(changes, policy.ChangeWarnColor(warnColor))
}
if wm.FontColor != fontColor {
changes = append(changes, policy.ChangeFontColor(fontColor))
}
if wm.PrimaryColorDark != primaryColorDark {
changes = append(changes, policy.ChangePrimaryColorDark(primaryColorDark))
}
if wm.BackgroundColorDark != backgroundColorDark {
changes = append(changes, policy.ChangeBackgroundColorDark(backgroundColorDark))
}
if wm.WarnColorDark != warnColorDark {
changes = append(changes, policy.ChangeWarnColorDark(warnColorDark))
}
if wm.FontColorDark != fontColorDark {
changes = append(changes, policy.ChangeFontColorDark(fontColorDark))
}
if wm.HideLoginNameSuffix != hideLoginNameSuffix {
changes = append(changes, policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix))
}
if wm.ErrorMsgPopup != errorMsgPopup {
changes = append(changes, policy.ChangeErrorMsgPopup(errorMsgPopup))
}
if wm.DisableWatermark != disableWatermark {
changes = append(changes, policy.ChangeDisableWatermark(disableWatermark))
}
if len(changes) == 0 {
return nil, false
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,8 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
features.LoginPolicyUsernameLogin,
features.LoginPolicyPasswordReset,
features.PasswordComplexityPolicy,
features.LabelPolicy,
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
)
if !hasChanged {
@@ -106,15 +107,12 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
events = append(events, removePasswordComplexityEvent)
}
}
if !features.LabelPolicy {
removeLabelPolicyEvent, err := c.removeLabelPolicyIfExists(ctx, orgID)
if err != nil {
return nil, err
}
if removeLabelPolicyEvent != nil {
events = append(events, removeLabelPolicyEvent)
}
labelPolicyEvents, err := c.setAllowedLabelPolicy(ctx, orgID, features)
if err != nil {
return nil, err
}
events = append(events, labelPolicyEvents...)
if !features.CustomDomain {
removeCustomDomainsEvents, err := c.removeCustomDomains(ctx, orgID)
if err != nil {
@@ -233,3 +231,56 @@ func (c *Commands) setDefaultAuthFactorsInCustomLoginPolicy(ctx context.Context,
}
return events, nil
}
func (c *Commands) setAllowedLabelPolicy(ctx context.Context, orgID string, features *domain.Features) ([]eventstore.EventPusher, error) {
events := make([]eventstore.EventPusher, 0)
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, nil
}
if !features.LabelPolicyPrivateLabel && !features.LabelPolicyWatermark {
removeEvent, err := c.removeLabelPolicy(ctx, existingPolicy)
if err != nil {
return nil, err
}
return append(events, removeEvent), nil
}
defaultPolicy, err := c.getDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
policy := *existingPolicy
if !features.LabelPolicyWatermark && defaultPolicy.DisableWatermark != existingPolicy.DisableWatermark {
policy.DisableWatermark = defaultPolicy.DisableWatermark
}
if !features.LabelPolicyPrivateLabel {
if defaultPolicy.HideLoginNameSuffix != existingPolicy.HideLoginNameSuffix {
policy.HideLoginNameSuffix = defaultPolicy.HideLoginNameSuffix
}
policy.PrimaryColor = ""
policy.BackgroundColor = ""
policy.WarnColor = ""
policy.FontColor = ""
policy.PrimaryColorDark = ""
policy.BackgroundColorDark = ""
policy.WarnColorDark = ""
policy.FontColorDark = ""
assetsEvent, err := c.removeLabelPolicyAssets(ctx, existingPolicy)
if err != nil {
return nil, err
}
events = append(events, assetsEvent)
}
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, OrgAggregateFromWriteModel(&existingPolicy.WriteModel),
policy.PrimaryColor, policy.BackgroundColor, policy.WarnColor, policy.FontColor,
policy.PrimaryColorDark, policy.BackgroundColorDark, policy.WarnColorDark, policy.FontColorDark,
policy.HideLoginNameSuffix, policy.ErrorMsgPopup, policy.HideLoginNameSuffix)
if hasChanged {
events = append(events, changedEvent)
}
return events, nil
}

View File

@@ -69,7 +69,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
loginPolicyUsernameLogin,
loginPolicyPasswordReset,
passwordComplexityPolicy,
labelPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain bool,
) (*org.FeaturesSetEvent, bool) {
@@ -111,8 +112,11 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
if wm.PasswordComplexityPolicy != passwordComplexityPolicy {
changes = append(changes, features.ChangePasswordComplexityPolicy(passwordComplexityPolicy))
}
if wm.LabelPolicy != labelPolicy {
changes = append(changes, features.ChangeLabelPolicy(labelPolicy))
if wm.LabelPolicyPrivateLabel != labelPolicyPrivateLabel {
changes = append(changes, features.ChangeLabelPolicyPrivateLabel(labelPolicyPrivateLabel))
}
if wm.LabelPolicyWatermark != labelPolicyWatermark {
changes = append(changes, features.ChangeLabelPolicyWatermark(labelPolicyWatermark))
}
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))

View File

@@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
@@ -14,12 +15,15 @@ import (
"github.com/caos/zitadel/internal/repository/features"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/static/mock"
)
func TestCommandSide_SetOrgFeatures(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
iamDomain string
static static.Storage
}
type args struct {
ctx context.Context
@@ -56,7 +60,8 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyUsernameLogin: false,
LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
@@ -90,7 +95,8 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyUsernameLogin: false,
LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
@@ -138,6 +144,14 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),
@@ -196,7 +210,8 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyUsernameLogin: false,
LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
@@ -246,6 +261,14 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),
@@ -332,7 +355,8 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyUsernameLogin: false,
LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
@@ -382,6 +406,14 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),
@@ -478,7 +510,8 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyUsernameLogin: false,
LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
@@ -528,6 +561,14 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),
@@ -634,7 +675,8 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyUsernameLogin: false,
LoginPolicyPasswordReset: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
@@ -740,6 +782,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
//begin setDefaultAuthFactorsInCustomLoginPolicy
//orgLabelPolicyWriteModelByID
expectFilter(
eventFromEventPusher(
@@ -748,6 +791,14 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),
@@ -757,10 +808,22 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"custom",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),
),
//removeLabelPolicy
expectFilter(),
//end setDefaultAuthFactorsInCustomLoginPolicy
//removeCustomDomains
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(
@@ -818,6 +881,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
iamDomain: "iam-domain",
static: mock.NewMockStorage(gomock.NewController(t)).ExpectRemoveObjectNoError(),
},
args: args{
ctx: context.Background(),
@@ -832,7 +896,9 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LoginPolicyRegistration: false,
LoginPolicyUsernameLogin: false,
PasswordComplexityPolicy: false,
LabelPolicy: false,
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
},
},
res: res{
@@ -847,6 +913,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
iamDomain: tt.fields.iamDomain,
static: tt.fields.static,
}
got, err := r.SetOrgFeatures(tt.args.ctx, tt.args.resourceOwner, tt.args.features)
if tt.res.err == nil {
@@ -958,6 +1025,14 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary",
"secondary",
"warn",
"font",
"primary-dark",
"secondary-dark",
"warn-dark",
"font-dark",
false,
false,
false,
),
),

View File

@@ -12,8 +12,8 @@ func (c *Commands) AddLabelPolicy(ctx context.Context, resourceOwner string, pol
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Fn8ds", "Errors.ResourceOwnerMissing")
}
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Md9sf", "Errors.Org.LabelPolicy.Invalid")
if err := policy.IsValid(); err != nil {
return nil, err
}
addedPolicy := NewOrgLabelPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
@@ -25,7 +25,20 @@ func (c *Commands) AddLabelPolicy(ctx context.Context, resourceOwner string, pol
}
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyAddedEvent(ctx, orgAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix))
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyAddedEvent(
ctx,
orgAgg,
policy.PrimaryColor,
policy.BackgroundColor,
policy.WarnColor,
policy.FontColor,
policy.PrimaryColorDark,
policy.BackgroundColorDark,
policy.WarnColorDark,
policy.FontColorDark,
policy.HideLoginNameSuffix,
policy.ErrorMsgPopup,
policy.DisableWatermark))
if err != nil {
return nil, err
}
@@ -40,8 +53,8 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string,
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3N9fs", "Errors.ResourceOwnerMissing")
}
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-dM9fs", "Errors.Org.LabelPolicy.Invalid")
if err := policy.IsValid(); err != nil {
return nil, err
}
existingPolicy := NewOrgLabelPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
@@ -53,7 +66,20 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string,
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
ctx,
orgAgg,
policy.PrimaryColor,
policy.BackgroundColor,
policy.WarnColor,
policy.FontColor,
policy.PrimaryColorDark,
policy.BackgroundColorDark,
policy.WarnColorDark,
policy.FontColorDark,
policy.HideLoginNameSuffix,
policy.ErrorMsgPopup,
policy.DisableWatermark)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4M9vs", "Errors.Org.LabelPolicy.NotChanged")
}
@@ -69,6 +95,306 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string,
return writeModelToLabelPolicy(&existingPolicy.LabelPolicyWriteModel), nil
}
func (c *Commands) ActivateLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-KKd4X", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-34mSE", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyActivatedEvent(ctx, orgAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddLogoLabelPolicy(ctx context.Context, orgID, storageKey string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-KKd4X", "Errors.ResourceOwnerMissing")
}
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4N3nf", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-23BMs", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyLogoAddedEvent(ctx, orgAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLogoLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-2FN8s", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-4MVsf", "Errors.Org.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, orgID, existingPolicy.LogoKey)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyLogoRemovedEvent(ctx, orgAgg, existingPolicy.LogoKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddIconLabelPolicy(ctx context.Context, orgID, storageKey string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-hMDs3", "Errors.ResourceOwnerMissing")
}
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4BS7f", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-4nq2f", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyIconAddedEvent(ctx, orgAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIconLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-1nd0d", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-1nd9f", "Errors.Org.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, orgID, existingPolicy.IconKey)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyIconRemovedEvent(ctx, orgAgg, existingPolicy.IconKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddLogoDarkLabelPolicy(ctx context.Context, orgID, storageKey string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-67Ms2", "Errors.ResourceOwnerMissing")
}
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3S7fN", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-QSqcd", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyLogoDarkAddedEvent(ctx, orgAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLogoDarkLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4NF0d", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-0peQw", "Errors.Org.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, orgID, existingPolicy.LogoDarkKey)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyLogoDarkRemovedEvent(ctx, orgAgg, existingPolicy.LogoDarkKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddIconDarkLabelPolicy(ctx context.Context, orgID, storageKey string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-tzBfs", "Errors.ResourceOwnerMissing")
}
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4B7cs", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-4Nf8s", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyIconDarkAddedEvent(ctx, orgAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIconDarkLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mv9ds", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-3NFos", "Errors.Org.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, orgID, existingPolicy.IconDarkKey)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyIconDarkRemovedEvent(ctx, orgAgg, existingPolicy.IconDarkKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) AddFontLabelPolicy(ctx context.Context, orgID, storageKey string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-1Nf9s", "Errors.ResourceOwnerMissing")
}
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2f9fw", "Errors.Assets.EmptyKey")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-2M9fs", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyFontAddedEvent(ctx, orgAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveFontLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-2n0fW", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgLabelPolicyWriteModelByID(ctx, orgID)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "ORG-4n9SD", "Errors.Org.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, orgID, existingPolicy.FontKey)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyFontRemovedEvent(ctx, orgAgg, existingPolicy.FontKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}
func (c *Commands) RemoveLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
@@ -97,6 +423,10 @@ func (c *Commands) removeLabelPolicy(ctx context.Context, existingPolicy *OrgLab
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9df", "Errors.Org.LabelPolicy.NotFound")
}
err = c.RemoveAsset(ctx, existingPolicy.AggregateID, domain.LabelPolicyPrefix)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewLabelPolicyRemovedEvent(ctx, orgAgg), nil
}
@@ -109,10 +439,23 @@ func (c *Commands) removeLabelPolicyIfExists(ctx context.Context, orgID string)
if existingPolicy.State != domain.PolicyStateActive {
return nil, nil
}
err = c.RemoveAsset(ctx, orgID, domain.LabelPolicyPrefix)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewLabelPolicyRemovedEvent(ctx, orgAgg), nil
}
func (c *Commands) removeLabelPolicyAssets(ctx context.Context, existingPolicy *OrgLabelPolicyWriteModel) (*org.LabelPolicyAssetsRemovedEvent, error) {
err := c.RemoveAsset(ctx, existingPolicy.AggregateID, domain.LabelPolicyPrefix)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
return org.NewLabelPolicyAssetsRemovedEvent(ctx, orgAgg), nil
}
func (c *Commands) orgLabelPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgLabelPolicyWriteModel, error) {
policy := NewOrgLabelPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, policy)

View File

@@ -30,6 +30,26 @@ func (wm *OrgLabelPolicyWriteModel) AppendEvents(events ...eventstore.EventReade
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyAddedEvent)
case *org.LabelPolicyChangedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyChangedEvent)
case *org.LabelPolicyLogoAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoAddedEvent)
case *org.LabelPolicyLogoRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoRemovedEvent)
case *org.LabelPolicyLogoDarkAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoDarkAddedEvent)
case *org.LabelPolicyLogoDarkRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyLogoDarkRemovedEvent)
case *org.LabelPolicyIconAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconAddedEvent)
case *org.LabelPolicyIconRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconRemovedEvent)
case *org.LabelPolicyIconDarkAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconDarkAddedEvent)
case *org.LabelPolicyIconDarkRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyIconDarkRemovedEvent)
case *org.LabelPolicyFontAddedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyFontAddedEvent)
case *org.LabelPolicyFontRemovedEvent:
wm.LabelPolicyWriteModel.AppendEvents(&e.LabelPolicyFontRemovedEvent)
}
}
}
@@ -44,26 +64,69 @@ func (wm *OrgLabelPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
ResourceOwner(wm.ResourceOwner).
EventTypes(
org.LabelPolicyAddedEventType,
org.LabelPolicyChangedEventType)
org.LabelPolicyChangedEventType,
org.LabelPolicyLogoAddedEventType,
org.LabelPolicyLogoRemovedEventType,
org.LabelPolicyIconAddedEventType,
org.LabelPolicyIconRemovedEventType,
org.LabelPolicyLogoDarkAddedEventType,
org.LabelPolicyLogoDarkRemovedEventType,
org.LabelPolicyIconDarkAddedEventType,
org.LabelPolicyIconDarkRemovedEventType,
org.LabelPolicyFontAddedEventType,
org.LabelPolicyFontRemovedEventType,
)
}
func (wm *OrgLabelPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
backgroundColor,
warnColor,
fontColor,
primaryColorDark,
backgroundColorDark,
warnColorDark,
fontColorDark string,
hideLoginNameSuffix,
errorMsgPopup,
disableWatermark bool,
) (*org.LabelPolicyChangedEvent, bool) {
changes := make([]policy.LabelPolicyChanges, 0)
if wm.PrimaryColor != primaryColor {
changes = append(changes, policy.ChangePrimaryColor(primaryColor))
}
if wm.SecondaryColor != secondaryColor {
changes = append(changes, policy.ChangeSecondaryColor(secondaryColor))
if wm.BackgroundColor != backgroundColor {
changes = append(changes, policy.ChangeBackgroundColor(backgroundColor))
}
if wm.WarnColor != warnColor {
changes = append(changes, policy.ChangeWarnColor(warnColor))
}
if wm.FontColor != fontColor {
changes = append(changes, policy.ChangeFontColor(fontColor))
}
if wm.PrimaryColorDark != primaryColorDark {
changes = append(changes, policy.ChangePrimaryColorDark(primaryColorDark))
}
if wm.BackgroundColorDark != backgroundColorDark {
changes = append(changes, policy.ChangeBackgroundColorDark(backgroundColorDark))
}
if wm.WarnColorDark != warnColorDark {
changes = append(changes, policy.ChangeWarnColorDark(warnColorDark))
}
if wm.FontColorDark != fontColorDark {
changes = append(changes, policy.ChangeFontColorDark(fontColorDark))
}
if wm.HideLoginNameSuffix != hideLoginNameSuffix {
changes = append(changes, policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix))
}
if wm.ErrorMsgPopup != errorMsgPopup {
changes = append(changes, policy.ChangeErrorMsgPopup(errorMsgPopup))
}
if wm.DisableWatermark != disableWatermark {
changes = append(changes, policy.ChangeDisableWatermark(disableWatermark))
}
if len(changes) == 0 {
return nil, false
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,25 @@ import (
type LabelPolicyWriteModel struct {
eventstore.WriteModel
PrimaryColor string
SecondaryColor string
PrimaryColor string
BackgroundColor string
WarnColor string
FontColor string
LogoKey string
IconKey string
PrimaryColorDark string
BackgroundColorDark string
WarnColorDark string
FontColorDark string
LogoDarkKey string
IconDarkKey string
FontKey string
HideLoginNameSuffix bool
ErrorMsgPopup bool
DisableWatermark bool
State domain.PolicyState
}
@@ -21,19 +37,71 @@ func (wm *LabelPolicyWriteModel) Reduce() error {
switch e := event.(type) {
case *policy.LabelPolicyAddedEvent:
wm.PrimaryColor = e.PrimaryColor
wm.SecondaryColor = e.SecondaryColor
wm.BackgroundColor = e.BackgroundColor
wm.WarnColor = e.WarnColor
wm.FontColor = e.FontColor
wm.PrimaryColorDark = e.PrimaryColorDark
wm.BackgroundColorDark = e.BackgroundColorDark
wm.WarnColorDark = e.WarnColorDark
wm.FontColorDark = e.FontColorDark
wm.HideLoginNameSuffix = e.HideLoginNameSuffix
wm.ErrorMsgPopup = e.ErrorMsgPopup
wm.DisableWatermark = e.DisableWatermark
wm.State = domain.PolicyStateActive
case *policy.LabelPolicyChangedEvent:
if e.PrimaryColor != nil {
wm.PrimaryColor = *e.PrimaryColor
}
if e.SecondaryColor != nil {
wm.SecondaryColor = *e.SecondaryColor
if e.BackgroundColor != nil {
wm.BackgroundColor = *e.BackgroundColor
}
if e.WarnColor != nil {
wm.WarnColor = *e.WarnColor
}
if e.FontColor != nil {
wm.FontColor = *e.FontColor
}
if e.PrimaryColorDark != nil {
wm.PrimaryColorDark = *e.PrimaryColorDark
}
if e.BackgroundColorDark != nil {
wm.BackgroundColorDark = *e.BackgroundColorDark
}
if e.WarnColorDark != nil {
wm.WarnColorDark = *e.WarnColorDark
}
if e.FontColorDark != nil {
wm.FontColorDark = *e.FontColorDark
}
if e.HideLoginNameSuffix != nil {
wm.HideLoginNameSuffix = *e.HideLoginNameSuffix
}
if e.ErrorMsgPopup != nil {
wm.ErrorMsgPopup = *e.ErrorMsgPopup
}
if e.DisableWatermark != nil {
wm.DisableWatermark = *e.DisableWatermark
}
case *policy.LabelPolicyLogoAddedEvent:
wm.LogoKey = e.StoreKey
case *policy.LabelPolicyLogoRemovedEvent:
wm.LogoKey = ""
case *policy.LabelPolicyLogoDarkAddedEvent:
wm.LogoDarkKey = e.StoreKey
case *policy.LabelPolicyLogoDarkRemovedEvent:
wm.LogoDarkKey = ""
case *policy.LabelPolicyIconAddedEvent:
wm.IconKey = e.StoreKey
case *policy.LabelPolicyIconRemovedEvent:
wm.IconKey = ""
case *policy.LabelPolicyIconDarkAddedEvent:
wm.IconDarkKey = e.StoreKey
case *policy.LabelPolicyIconDarkRemovedEvent:
wm.IconDarkKey = ""
case *policy.LabelPolicyFontAddedEvent:
wm.FontKey = e.StoreKey
case *policy.LabelPolicyFontRemovedEvent:
wm.FontKey = ""
case *policy.LabelPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved
}

View File

@@ -44,7 +44,7 @@ func (c *Commands) SetupStep12(ctx context.Context, step *Step12) error {
LoginPolicyRegistration: step.LoginPolicyRegistration,
LoginPolicyUsernameLogin: step.LoginPolicyUsernameLogin,
PasswordComplexityPolicy: step.PasswordComplexityPolicy,
LabelPolicy: step.LabelPolicy,
LabelPolicyPrivateLabel: step.LabelPolicy,
CustomDomain: step.CustomDomain,
})
if err != nil {

View File

@@ -0,0 +1,48 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
iam_repo "github.com/caos/zitadel/internal/repository/iam"
org_repo "github.com/caos/zitadel/internal/repository/org"
)
type Step14 struct {
ActivateExistingLabelPolicies bool
}
func (s *Step14) Step() domain.Step {
return domain.Step14
}
func (s *Step14) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep14(ctx, s)
}
func (c *Commands) SetupStep14(ctx context.Context, step *Step14) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
var events []eventstore.EventPusher
if step.ActivateExistingLabelPolicies {
existingPolicies := NewExistingLabelPoliciesReadModel(ctx)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicies)
if err != nil {
return nil, err
}
for _, aggID := range existingPolicies.aggregateIDs {
if iamAgg.ID == aggID {
events = append(events, iam_repo.NewLabelPolicyActivatedEvent(ctx, iamAgg))
continue
}
events = append(events, org_repo.NewLabelPolicyActivatedEvent(ctx, &org_repo.NewAggregate(aggID, aggID).Aggregate))
}
}
logging.Log("SETUP-M9fsd").Info("activate login policies")
return events, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -0,0 +1,32 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step15 struct {
DefaultMailTemplate domain.MailTemplate
}
func (s *Step15) Step() domain.Step {
return domain.Step15
}
func (s *Step15) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep15(ctx, s)
}
func (c *Commands) SetupStep15(ctx context.Context, step *Step15) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
_, mailTemplateEvent, err := c.changeDefaultMailTemplate(ctx, &step.DefaultMailTemplate)
if err != nil {
return nil, err
}
logging.Log("SETUP-2nfsd").Info("default mail template/text set up")
return []eventstore.EventPusher{mailTemplateEvent}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -2,15 +2,16 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type Step2 struct {
DefaultPasswordComplexityPolicy iam_model.PasswordComplexityPolicy
DefaultPasswordComplexityPolicy domain.PasswordComplexityPolicy
}
func (s *Step2) Step() domain.Step {

View File

@@ -2,15 +2,16 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type Step3 struct {
DefaultPasswordAgePolicy iam_model.PasswordAgePolicy
DefaultPasswordAgePolicy domain.PasswordAgePolicy
}
func (s *Step3) Step() domain.Step {

View File

@@ -2,15 +2,16 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type Step4 struct {
DefaultPasswordLockoutPolicy iam_model.PasswordLockoutPolicy
DefaultPasswordLockoutPolicy domain.PasswordLockoutPolicy
}
func (s *Step4) Step() domain.Step {

View File

@@ -2,15 +2,16 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type Step5 struct {
DefaultOrgIAMPolicy iam_model.OrgIAMPolicy
DefaultOrgIAMPolicy domain.OrgIAMPolicy
}
func (s *Step5) Step() domain.Step {

View File

@@ -2,15 +2,16 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type Step6 struct {
DefaultLabelPolicy iam_model.LabelPolicy
DefaultLabelPolicy domain.LabelPolicy
}
func (s *Step6) Step() domain.Step {
@@ -24,10 +25,7 @@ func (s *Step6) execute(ctx context.Context, commandSide *Commands) error {
func (c *Commands) SetupStep6(ctx context.Context, step *Step6) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
event, err := c.addDefaultLabelPolicy(ctx, iamAgg, NewIAMLabelPolicyWriteModel(), &domain.LabelPolicy{
PrimaryColor: step.DefaultLabelPolicy.PrimaryColor,
SecondaryColor: step.DefaultLabelPolicy.SecondaryColor,
})
event, err := c.addDefaultLabelPolicy(ctx, iamAgg, NewIAMLabelPolicyWriteModel(), &step.DefaultLabelPolicy)
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,27 @@
package command
import (
"context"
"io"
"github.com/caos/zitadel/internal/domain"
caos_errors "github.com/caos/zitadel/internal/errors"
)
func (c *Commands) UploadAsset(ctx context.Context, bucketName, objectName, contentType string, file io.Reader, size int64) (*domain.AssetInfo, error) {
if c.static == nil {
return nil, caos_errors.ThrowPreconditionFailed(nil, "STATIC-Fm92f", "Errors.Assets.Store.NotConfigured")
}
return c.static.PutObject(ctx,
bucketName,
objectName,
contentType,
file,
size,
true,
)
}
func (c *Commands) RemoveAsset(ctx context.Context, bucketName, storeKey string) error {
return c.static.RemoveObject(ctx, bucketName, storeKey)
}

View File

@@ -23,7 +23,7 @@ func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) (
func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Human) (*domain.Human, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.ResourceOwnerMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing")
}
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, orgID)
if err != nil {
@@ -81,7 +81,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid")
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = true
@@ -91,7 +91,7 @@ func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Hum
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid")
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
}
return c.createHuman(ctx, orgID, human, nil, false, orgIAMPolicy, pwPolicy)
}

View File

@@ -0,0 +1,63 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/user"
)
func (c *Commands) AddHumanAvatar(ctx context.Context, orgID, userID, storageKey string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "USER-Ba5Ds", "Errors.IDMissing")
}
if storageKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "USER-1Xyud", "Errors.Assets.EmptyKey")
}
existingUser, err := c.userWriteModelByID(ctx, userID, orgID)
if err != nil {
return nil, err
}
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "USER-vJ3fS", "Errors.Users.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, user.NewHumanAvatarAddedEvent(ctx, userAgg, storageKey))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingUser, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingUser.WriteModel), nil
}
func (c *Commands) RemoveHumanAvatar(ctx context.Context, orgID, userID string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "USER-1B8sd", "Errors.IDMissing")
}
existingUser, err := c.getHumanWriteModelByID(ctx, userID, orgID)
if err != nil {
return nil, err
}
if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "USER-35N8f", "Errors.Users.NotFound")
}
err = c.RemoveAsset(ctx, orgID, existingUser.Avatar)
if err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, user.NewHumanAvatarRemovedEvent(ctx, userAgg, existingUser.Avatar))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingUser, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingUser.WriteModel), nil
}

View File

@@ -0,0 +1,319 @@
package command
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/static/mock"
)
func TestCommandSide_AddHumanAvatar(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
userID string
storageKey string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userID empty, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
storageKey: "key",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "storage key empty, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
storageKey: "key",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "logo added, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanAvatarAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
storageKey: "key",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddHumanAvatar(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.storageKey)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveHumanAvatar(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
storage static.Storage
}
type args struct {
ctx context.Context
orgID string
userID string
storageKey string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userID empty, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
storageKey: "key",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
storageKey: "key",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "file remove error, not found error",
fields: fields{
storage: mock.NewMockStorage(gomock.NewController(t)).ExpectRemoveObjectError(),
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanAvatarAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
storageKey: "key",
},
res: res{
err: caos_errs.IsInternal,
},
},
{
name: "logo removed, ok",
fields: fields{
storage: mock.NewMockStorage(gomock.NewController(t)).ExpectRemoveObjectNoError(),
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanAvatarAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanAvatarRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
storageKey: "key",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
static: tt.fields.storage,
}
got, err := r.RemoveHumanAvatar(tt.args.ctx, tt.args.orgID, tt.args.userID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}

View File

@@ -1,11 +1,12 @@
package command
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
"golang.org/x/text/language"
)
type HumanWriteModel struct {
@@ -19,6 +20,7 @@ type HumanWriteModel struct {
DisplayName string
PreferredLanguage language.Tag
Gender domain.Gender
Avatar string
Email string
IsEmailVerified bool
@@ -74,6 +76,10 @@ func (wm *HumanWriteModel) Reduce() error {
wm.reduceHumanPhoneRemovedEvent()
case *user.HumanPasswordChangedEvent:
wm.reduceHumanPasswordChangedEvent(e)
case *user.HumanAvatarAddedEvent:
wm.Avatar = e.StoreKey
case *user.HumanAvatarRemovedEvent:
wm.Avatar = ""
case *user.UserLockedEvent:
if wm.UserState != domain.UserStateDeleted {
wm.UserState = domain.UserStateLocked
@@ -112,6 +118,8 @@ func (wm *HumanWriteModel) Query() *eventstore.SearchQueryBuilder {
user.HumanPhoneChangedType,
user.HumanPhoneVerifiedType,
user.HumanPhoneRemovedType,
user.HumanAvatarAddedType,
user.HumanAvatarRemovedType,
user.HumanPasswordChangedType,
user.UserLockedType,
user.UserUnlockedType,

View File

@@ -828,70 +828,70 @@ func TestCommandSide_RemoveHumanPhone(t *testing.T) {
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "phone not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
//{
// name: "userid missing, invalid argument error",
// fields: fields{
// eventstore: eventstoreExpect(
// t,
// ),
// },
// args: args{
// ctx: context.Background(),
// resourceOwner: "org1",
// },
// res: res{
// err: caos_errs.IsErrorInvalidArgument,
// },
//},
//{
// name: "user not existing, precondition error",
// fields: fields{
// eventstore: eventstoreExpect(
// t,
// expectFilter(),
// ),
// },
// args: args{
// ctx: context.Background(),
// userID: "user1",
// resourceOwner: "org1",
// },
// res: res{
// err: caos_errs.IsPreconditionFailed,
// },
//},
//{
// name: "phone not existing, precondition error",
// fields: fields{
// eventstore: eventstoreExpect(
// t,
// expectFilter(
// eventFromEventPusher(
// user.NewHumanAddedEvent(context.Background(),
// &user.NewAggregate("user1", "org1").Aggregate,
// "username",
// "firstname",
// "lastname",
// "nickname",
// "displayname",
// language.German,
// domain.GenderUnspecified,
// "email@test.ch",
// true,
// ),
// ),
// ),
// ),
// },
// args: args{
// ctx: context.Background(),
// userID: "user1",
// resourceOwner: "org1",
// },
// res: res{
// err: caos_errs.IsNotFound,
// },
//},
{
name: "remove phone, ok",
fields: fields{

View File

@@ -2,6 +2,25 @@ package domain
import "time"
const (
UsersAssetPath = "users"
AvatarAssetPath = "/avatar"
policyPrefix = "policy"
LabelPolicyPrefix = policyPrefix + "/label"
labelPolicyLogoPrefix = LabelPolicyPrefix + "/logo"
labelPolicyIconPrefix = LabelPolicyPrefix + "/icon"
labelPolicyFontPrefix = LabelPolicyPrefix + "/font"
Dark = "dark"
CssPath = LabelPolicyPrefix + "/css"
CssVariablesFileName = "variables.css"
LabelPolicyLogoPath = labelPolicyLogoPrefix
LabelPolicyIconPath = labelPolicyIconPrefix
LabelPolicyFontPath = labelPolicyFontPrefix
)
type AssetInfo struct {
Bucket string
Key string
@@ -12,4 +31,9 @@ type AssetInfo struct {
VersionID string
Expiration time.Time
AutheticatedURL string
ContentType string
}
func GetHumanAvatarAssetPath(userID string) string {
return UsersAssetPath + "/" + userID + AvatarAssetPath
}

View File

@@ -30,6 +30,8 @@ type AuthRequest struct {
UserName string
LoginName string
DisplayName string
AvatarKey string
PresignedAvatar string
UserOrgID string
RequestedOrgID string
RequestedOrgName string
@@ -108,11 +110,12 @@ func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
return a
}
func (a *AuthRequest) SetUserInfo(userID, userName, loginName, displayName, userOrgID string) {
func (a *AuthRequest) SetUserInfo(userID, userName, loginName, displayName, avatar, userOrgID string) {
a.UserID = userID
a.UserName = userName
a.LoginName = loginName
a.DisplayName = displayName
a.AvatarKey = avatar
a.UserOrgID = userOrgID
}

View File

@@ -16,6 +16,8 @@ const (
FeatureLoginPolicyPasswordReset = FeatureLoginPolicy + ".password_reset"
FeaturePasswordComplexityPolicy = "password_complexity_policy"
FeatureLabelPolicy = "label_policy"
FeatureLabelPolicyPrivateLabel = FeatureLabelPolicy + ".private_label"
FeatureLabelPolicyWatermark = FeatureLabelPolicy + ".watermark"
FeatureCustomDomain = "custom_domain"
)
@@ -36,7 +38,8 @@ type Features struct {
LoginPolicyUsernameLogin bool
LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
}

View File

@@ -47,6 +47,7 @@ type UserSelection struct {
LoginName string
UserSessionState UserSessionState
SelectionPossible bool
AvatarKey string
}
type UserSessionState int32

View File

@@ -1,18 +1,84 @@
package domain
import (
"regexp"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
var colorRegex = regexp.MustCompile("^$|^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
type LabelPolicy struct {
models.ObjectRoot
Default bool
PrimaryColor string
SecondaryColor string
State LabelPolicyState
Default bool
PrimaryColor string
BackgroundColor string
WarnColor string
FontColor string
LogoURL string
IconURL string
PrimaryColorDark string
BackgroundColorDark string
WarnColorDark string
FontColorDark string
LogoDarkURL string
IconDarkURL string
Font string
HideLoginNameSuffix bool
ErrorMsgPopup bool
DisableWatermark bool
}
func (p *LabelPolicy) IsValid() bool {
return p.PrimaryColor != "" && p.SecondaryColor != ""
type LabelPolicyState int32
const (
LabelPolicyStateUnspecified LabelPolicyState = iota
LabelPolicyStateActive
LabelPolicyStateRemoved
LabelPolicyStatePreview
labelPolicyStateCount
)
func (f LabelPolicy) IsValid() error {
if !colorRegex.MatchString(f.PrimaryColor) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-391dG", "Errors.Policy.Label.Invalid.PrimaryColor")
}
if !colorRegex.MatchString(f.BackgroundColor) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-502F1", "Errors.Policy.Label.Invalid.BackgroundColor")
}
if !colorRegex.MatchString(f.WarnColor) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-nvw33", "Errors.Policy.Label.Invalid.WarnColor")
}
if !colorRegex.MatchString(f.FontColor) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-93mSf", "Errors.Policy.Label.Invalid.FontColor")
}
if !colorRegex.MatchString(f.PrimaryColorDark) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-391dG", "Errors.Policy.Label.Invalid.PrimaryColorDark")
}
if !colorRegex.MatchString(f.BackgroundColorDark) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-llsp2", "Errors.Policy.Label.Invalid.BackgroundColorDark")
}
if !colorRegex.MatchString(f.WarnColorDark) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-2b6sf", "Errors.Policy.Label.Invalid.WarnColorDark")
}
if !colorRegex.MatchString(f.FontColorDark) {
return caos_errs.ThrowInvalidArgument(nil, "POLICY-3M0fs", "Errors.Policy.Label.Invalid.FontColorDark")
}
return nil
}
func (f LabelPolicyState) Valid() bool {
return f >= 0 && f < labelPolicyStateCount
}
func (s LabelPolicyState) Exists() bool {
return s != LabelPolicyStateUnspecified && s != LabelPolicyStateRemoved
}

View File

@@ -0,0 +1,505 @@
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestLabelPolicyPrimaryColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty primary, valid",
args: args{
policy: &LabelPolicy{PrimaryColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{PrimaryColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyBackgroundColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty background, valid",
args: args{
policy: &LabelPolicy{BackgroundColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{BackgroundColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyWarnColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty warn, valid",
args: args{
policy: &LabelPolicy{WarnColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{WarnColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{WarnColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{WarnColor: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{WarnColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{WarnColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyFontColorValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty font, valid",
args: args{
policy: &LabelPolicy{FontColor: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{FontColor: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{FontColor: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{FontColor: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{FontColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{FontColor: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyPrimaryColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty primary dark, valid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{PrimaryColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyBackgroundColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty background dark, valid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{BackgroundColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyWarnColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty warn dark, valid",
args: args{
policy: &LabelPolicy{WarnColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{WarnColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestLabelPolicyFontColorDarkValid(t *testing.T) {
type args struct {
policy *LabelPolicy
}
tests := []struct {
name string
args args
err func(error) bool
}{
{
name: "empty font dark, valid",
args: args{
policy: &LabelPolicy{FontColorDark: ""},
},
},
{
name: "color code with 6 characters, valid",
args: args{
policy: &LabelPolicy{FontColorDark: "#ffffff"},
},
},
{
name: "color code with 3 characters, valid",
args: args{
policy: &LabelPolicy{FontColorDark: "#000"},
},
},
{
name: "color code with wrong characters, invalid",
args: args{
policy: &LabelPolicy{FontColorDark: "#0f9wfm"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with wrong count of characters, invalid",
args: args{
policy: &LabelPolicy{FontColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
{
name: "color code with no #, invalid",
args: args{
policy: &LabelPolicy{FontColorDark: "#00"},
},
err: caos_errs.IsErrorInvalidArgument,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.policy.IsValid()
if tt.err == nil {
assert.NoError(t, err)
}
if tt.err != nil && !tt.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -16,6 +16,8 @@ const (
Step11
Step12
Step13
Step14
Step15
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
StepCount
)

View File

@@ -25,12 +25,13 @@ type FeaturesView struct {
LoginPolicyUsernameLogin bool
LoginPolicyPasswordReset bool
PasswordComplexityPolicy bool
LabelPolicy bool
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
}
func (f *FeaturesView) FeatureList() []string {
list := make([]string, 0, 6)
list := make([]string, 0)
if f.LoginPolicyFactors {
list = append(list, domain.FeatureLoginPolicyFactors)
}
@@ -52,8 +53,11 @@ func (f *FeaturesView) FeatureList() []string {
if f.PasswordComplexityPolicy {
list = append(list, domain.FeaturePasswordComplexityPolicy)
}
if f.LabelPolicy {
list = append(list, domain.FeatureLabelPolicy)
if f.LabelPolicyPrivateLabel {
list = append(list, domain.FeatureLabelPolicyPrivateLabel)
}
if f.LabelPolicyWatermark {
list = append(list, domain.FeatureLabelPolicyWatermark)
}
if f.CustomDomain {
list = append(list, domain.FeatureCustomDomain)

View File

@@ -38,7 +38,9 @@ type FeaturesView struct {
LoginPolicyUsernameLogin bool `json:"loginPolicyUsernameLogin" gorm:"column:login_policy_username_login"`
LoginPolicyPasswordReset bool `json:"loginPolicyPasswordReset" gorm:"column:login_policy_password_reset"`
PasswordComplexityPolicy bool `json:"passwordComplexityPolicy" gorm:"column:password_complexity_policy"`
LabelPolicy bool `json:"labelPolicy" gorm:"column:label_policy"`
LabelPolicy *bool `json:"labelPolicy" gorm:"-"`
LabelPolicyPrivateLabel bool `json:"labelPolicyPrivateLabel" gorm:"column:label_policy_private_label"`
LabelPolicyWatermark bool `json:"labelPolicyWatermark" gorm:"column:label_policy_watermark"`
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
}
@@ -61,7 +63,8 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
LoginPolicyUsernameLogin: features.LoginPolicyUsernameLogin,
LoginPolicyPasswordReset: features.LoginPolicyPasswordReset,
PasswordComplexityPolicy: features.PasswordComplexityPolicy,
LabelPolicy: features.LabelPolicy,
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
CustomDomain: features.CustomDomain,
}
}
@@ -95,5 +98,8 @@ func (f *FeaturesView) SetData(event *models.Event) error {
logging.Log("EVEN-DVsf2").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-Bfg31", "Could not unmarshal data")
}
if f.LabelPolicy != nil {
f.LabelPolicyPrivateLabel = *f.LabelPolicy
}
return nil
}

View File

@@ -10,7 +10,13 @@ type LabelPolicy struct {
State PolicyState
Default bool
PrimaryColor string
SecondaryColor string
BackgroundColor string
FontColor string
WarnColor string
PrimaryColorDark string
BackgroundColorDark string
FontColorDark string
WarnColorDark string
HideLoginNameSuffix bool
}

View File

@@ -1,16 +1,33 @@
package model
import (
"github.com/caos/zitadel/internal/domain"
"time"
"github.com/caos/zitadel/internal/domain"
)
type LabelPolicyView struct {
AggregateID string
PrimaryColor string
SecondaryColor string
AggregateID string
PrimaryColor string
BackgroundColor string
WarnColor string
FontColor string
LogoURL string
IconURL string
PrimaryColorDark string
BackgroundColorDark string
WarnColorDark string
FontColorDark string
LogoDarkURL string
IconDarkURL string
FontURL string
HideLoginNameSuffix bool
Default bool
ErrorMsgPopup bool
DisableWatermark bool
Default bool
CreationDate time.Time
ChangeDate time.Time
@@ -30,6 +47,7 @@ type LabelPolicySearchKey int32
const (
LabelPolicySearchKeyUnspecified LabelPolicySearchKey = iota
LabelPolicySearchKeyAggregateID
LabelPolicySearchKeyState
)
type LabelPolicySearchQuery struct {

View File

@@ -12,7 +12,13 @@ type LabelPolicy struct {
es_models.ObjectRoot
State int32 `json:"-"`
PrimaryColor string `json:"primaryColor"`
SecondaryColor string `json:"secondaryColor"`
BackgroundColor string `json:"backgroundColor"`
FontColor string `json:"fontColor"`
WarnColor string `json:"warnColor"`
PrimaryColorDark string `json:"primaryColorDark"`
BackgroundColorDark string `json:"backgroundColorDark"`
FontColorDark string `json:"fontColorDark"`
WarnColorDark string `json:"warnColorDark"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix"`
}
@@ -21,7 +27,13 @@ func LabelPolicyToModel(policy *LabelPolicy) *iam_model.LabelPolicy {
ObjectRoot: policy.ObjectRoot,
State: iam_model.PolicyState(policy.State),
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
BackgroundColor: policy.BackgroundColor,
WarnColor: policy.WarnColor,
FontColor: policy.FontColor,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
}
}
@@ -31,24 +43,17 @@ func LabelPolicyFromModel(policy *iam_model.LabelPolicy) *LabelPolicy {
ObjectRoot: policy.ObjectRoot,
State: int32(policy.State),
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
BackgroundColor: policy.BackgroundColor,
WarnColor: policy.WarnColor,
FontColor: policy.FontColor,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
}
}
func (p *LabelPolicy) Changes(changed *LabelPolicy) map[string]interface{} {
changes := make(map[string]interface{}, 2)
if changed.PrimaryColor != p.PrimaryColor {
changes["primaryColor"] = changed.PrimaryColor
}
if changed.SecondaryColor != p.SecondaryColor {
changes["secondaryColor"] = changed.SecondaryColor
}
return changes
}
func (i *IAM) appendAddLabelPolicyEvent(event *es_models.Event) error {
i.DefaultLabelPolicy = new(LabelPolicy)
err := i.DefaultLabelPolicy.SetDataLabel(event)

View File

@@ -7,50 +7,6 @@ import (
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
)
func TestLabelPolicyChanges(t *testing.T) {
type args struct {
existing *LabelPolicy
new *LabelPolicy
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "labelpolicy all attributes change",
args: args{
existing: &LabelPolicy{PrimaryColor: "000001", SecondaryColor: "FFFFFA"},
new: &LabelPolicy{PrimaryColor: "000000", SecondaryColor: "FFFFFF"},
},
res: res{
changesLen: 2,
},
},
{
name: "no changes",
args: args{
existing: &LabelPolicy{PrimaryColor: "000000", SecondaryColor: "FFFFFF"},
new: &LabelPolicy{PrimaryColor: "000000", SecondaryColor: "FFFFFF"},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existing.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestAppendAddLabelPolicyEvent(t *testing.T) {
type args struct {
iam *IAM
@@ -66,10 +22,10 @@ func TestAppendAddLabelPolicyEvent(t *testing.T) {
name: "append add label policy event",
args: args{
iam: new(IAM),
policy: &LabelPolicy{PrimaryColor: "000000", SecondaryColor: "FFFFFF"},
policy: &LabelPolicy{PrimaryColor: "000000", BackgroundColor: "FFFFFF"},
event: new(es_models.Event),
},
result: &IAM{DefaultLabelPolicy: &LabelPolicy{PrimaryColor: "000000", SecondaryColor: "FFFFFF"}},
result: &IAM{DefaultLabelPolicy: &LabelPolicy{PrimaryColor: "000000", BackgroundColor: "FFFFFF"}},
},
}
for _, tt := range tests {
@@ -82,8 +38,8 @@ func TestAppendAddLabelPolicyEvent(t *testing.T) {
if tt.result.DefaultLabelPolicy.PrimaryColor != tt.args.iam.DefaultLabelPolicy.PrimaryColor {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLabelPolicy.PrimaryColor, tt.args.iam.DefaultLabelPolicy.PrimaryColor)
}
if tt.result.DefaultLabelPolicy.SecondaryColor != tt.args.iam.DefaultLabelPolicy.SecondaryColor {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLabelPolicy.SecondaryColor, tt.args.iam.DefaultLabelPolicy.SecondaryColor)
if tt.result.DefaultLabelPolicy.BackgroundColor != tt.args.iam.DefaultLabelPolicy.BackgroundColor {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLabelPolicy.BackgroundColor, tt.args.iam.DefaultLabelPolicy.BackgroundColor)
}
})
}
@@ -104,13 +60,13 @@ func TestAppendChangeLabelPolicyEvent(t *testing.T) {
name: "append change label policy event",
args: args{
iam: &IAM{DefaultLabelPolicy: &LabelPolicy{
PrimaryColor: "000001", SecondaryColor: "FFFFF0",
PrimaryColor: "000001", BackgroundColor: "FFFFF0",
}},
policy: &LabelPolicy{PrimaryColor: "000000", SecondaryColor: "FFFFFF"},
policy: &LabelPolicy{PrimaryColor: "000000", BackgroundColor: "FFFFFF"},
event: &es_models.Event{},
},
result: &IAM{DefaultLabelPolicy: &LabelPolicy{
PrimaryColor: "000000", SecondaryColor: "FFFFFF",
PrimaryColor: "000000", BackgroundColor: "FFFFFF",
}},
},
}
@@ -124,8 +80,8 @@ func TestAppendChangeLabelPolicyEvent(t *testing.T) {
if tt.result.DefaultLabelPolicy.PrimaryColor != tt.args.iam.DefaultLabelPolicy.PrimaryColor {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLabelPolicy.PrimaryColor, tt.args.iam.DefaultLabelPolicy.PrimaryColor)
}
if tt.result.DefaultLabelPolicy.SecondaryColor != tt.args.iam.DefaultLabelPolicy.SecondaryColor {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLabelPolicy.SecondaryColor, tt.args.iam.DefaultLabelPolicy.SecondaryColor)
if tt.result.DefaultLabelPolicy.BackgroundColor != tt.args.iam.DefaultLabelPolicy.BackgroundColor {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultLabelPolicy.BackgroundColor, tt.args.iam.DefaultLabelPolicy.BackgroundColor)
}
})
}

View File

@@ -35,8 +35,21 @@ const (
LoginPolicyMultiFactorAdded models.EventType = "iam.policy.login.multifactor.added"
LoginPolicyMultiFactorRemoved models.EventType = "iam.policy.login.multifactor.removed"
LabelPolicyAdded models.EventType = "iam.policy.label.added"
LabelPolicyChanged models.EventType = "iam.policy.label.changed"
LabelPolicyAdded models.EventType = "iam.policy.label.added"
LabelPolicyChanged models.EventType = "iam.policy.label.changed"
LabelPolicyActivated models.EventType = "iam.policy.label.activated"
LabelPolicyLogoAdded models.EventType = "iam.policy.label.logo.added"
LabelPolicyLogoRemoved models.EventType = "iam.policy.label.logo.removed"
LabelPolicyIconAdded models.EventType = "iam.policy.label.icon.added"
LabelPolicyIconRemoved models.EventType = "iam.policy.label.icon.removed"
LabelPolicyLogoDarkAdded models.EventType = "iam.policy.label.logo.dark.added"
LabelPolicyLogoDarkRemoved models.EventType = "iam.policy.label.logo.dark.removed"
LabelPolicyIconDarkAdded models.EventType = "iam.policy.label.icon.dark.added"
LabelPolicyIconDarkRemoved models.EventType = "iam.policy.label.icon.dark.removed"
LabelPolicyFontAdded models.EventType = "iam.policy.label.font.added"
LabelPolicyFontRemoved models.EventType = "iam.policy.label.font.removed"
LabelPolicyAssetsRemoved models.EventType = "iam.policy.label.assets.removed"
MailTemplateAdded models.EventType = "iam.mail.template.added"
MailTemplateChanged models.EventType = "iam.mail.template.changed"

View File

@@ -6,13 +6,15 @@ import (
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
"github.com/jinzhu/gorm"
)
func GetLabelPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LabelPolicyView, error) {
func GetLabelPolicyByAggregateIDAndState(db *gorm.DB, table, aggregateID string, state int32) (*model.LabelPolicyView, error) {
policy := new(model.LabelPolicyView)
aggregateIDQuery := &model.LabelPolicySearchQuery{Key: iam_model.LabelPolicySearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery)
stateQuery := &model.LabelPolicySearchQuery{Key: iam_model.LabelPolicySearchKeyState, Value: state, Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery, stateQuery)
err := query(db, policy)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-68G11", "Errors.IAM.LabelPolicy.NotExisting")

View File

@@ -10,6 +10,7 @@ import (
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/model"
@@ -17,22 +18,40 @@ import (
const (
LabelPolicyKeyAggregateID = "aggregate_id"
LabelPolicyKeyState = "label_policy_state"
)
type LabelPolicyView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
State int32 `json:"-" gorm:"column:label_policy_state;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:label_policy_state"`
PrimaryColor string `json:"primaryColor" gorm:"column:primary_color"`
SecondaryColor string `json:"secondaryColor" gorm:"column:secondary_color"`
BackgroundColor string `json:"backgroundColor" gorm:"column:background_color"`
WarnColor string `json:"warnColor" gorm:"column:warn_color"`
FontColor string `json:"fontColor" gorm:"column:font_color"`
PrimaryColorDark string `json:"primaryColorDark" gorm:"column:primary_color_dark"`
BackgroundColorDark string `json:"backgroundColorDark" gorm:"column:background_color_dark"`
WarnColorDark string `json:"warnColorDark" gorm:"column:warn_color_dark"`
FontColorDark string `json:"fontColorDark" gorm:"column:font_color_dark"`
LogoURL string `json:"-" gorm:"column:logo_url"`
IconURL string `json:"-" gorm:"column:icon_url"`
LogoDarkURL string `json:"-" gorm:"column:logo_dark_url"`
IconDarkURL string `json:"-" gorm:"column:icon_dark_url"`
FontURL string `json:"-" gorm:"column:font_url"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix" gorm:"column:hide_login_name_suffix"`
ErrorMsgPopup bool `json:"errorMsgPopup" gorm:"column:err_msg_popup"`
DisableWatermark bool `json:"disableWatermark" gorm:"column:disable_watermark"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
type AssetView struct {
AssetURL string `json:"storeKey"`
}
func (p *LabelPolicyView) ToDomain() *domain.LabelPolicy {
return &domain.LabelPolicy{
ObjectRoot: models.ObjectRoot{
@@ -41,36 +60,129 @@ func (p *LabelPolicyView) ToDomain() *domain.LabelPolicy {
ChangeDate: p.ChangeDate,
Sequence: p.Sequence,
},
Default: p.Default,
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
Default: p.Default,
PrimaryColor: p.PrimaryColor,
BackgroundColor: p.BackgroundColor,
WarnColor: p.WarnColor,
FontColor: p.FontColor,
LogoURL: p.LogoURL,
IconURL: p.IconURL,
PrimaryColorDark: p.PrimaryColorDark,
BackgroundColorDark: p.BackgroundColorDark,
WarnColorDark: p.WarnColorDark,
FontColorDark: p.FontColorDark,
LogoDarkURL: p.LogoDarkURL,
IconDarkURL: p.IconDarkURL,
Font: p.FontURL,
HideLoginNameSuffix: p.HideLoginNameSuffix,
ErrorMsgPopup: p.ErrorMsgPopup,
DisableWatermark: p.DisableWatermark,
}
}
func LabelPolicyViewToModel(policy *LabelPolicyView) *model.LabelPolicyView {
return &model.LabelPolicyView{
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor,
BackgroundColor: policy.BackgroundColor,
WarnColor: policy.WarnColor,
FontColor: policy.FontColor,
LogoURL: policy.LogoURL,
IconURL: policy.IconURL,
PrimaryColorDark: policy.PrimaryColorDark,
BackgroundColorDark: policy.BackgroundColorDark,
WarnColorDark: policy.WarnColorDark,
FontColorDark: policy.FontColorDark,
LogoDarkURL: policy.LogoDarkURL,
IconDarkURL: policy.IconDarkURL,
FontURL: policy.FontURL,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
ErrorMsgPopup: policy.ErrorMsgPopup,
DisableWatermark: policy.DisableWatermark,
Default: policy.Default,
}
}
func (i *LabelPolicyView) AppendEvent(event *models.Event) (err error) {
asset := &AssetView{}
i.Sequence = event.Sequence
i.ChangeDate = event.CreationDate
switch event.Type {
case es_model.LabelPolicyAdded, org_es_model.LabelPolicyAdded:
i.setRootData(event)
i.CreationDate = event.CreationDate
i.State = int32(domain.LabelPolicyStatePreview)
err = i.SetData(event)
case es_model.LabelPolicyChanged, org_es_model.LabelPolicyChanged:
err = i.SetData(event)
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyLogoAdded, org_es_model.LabelPolicyLogoAdded:
err = asset.SetData(event)
if err != nil {
return err
}
i.LogoURL = asset.AssetURL
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyLogoRemoved, org_es_model.LabelPolicyLogoRemoved:
i.LogoURL = ""
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyIconAdded, org_es_model.LabelPolicyIconAdded:
err = asset.SetData(event)
if err != nil {
return err
}
i.IconURL = asset.AssetURL
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyIconRemoved, org_es_model.LabelPolicyIconRemoved:
i.IconURL = ""
case es_model.LabelPolicyLogoDarkAdded, org_es_model.LabelPolicyLogoDarkAdded:
err = asset.SetData(event)
if err != nil {
return err
}
i.LogoDarkURL = asset.AssetURL
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyLogoDarkRemoved, org_es_model.LabelPolicyLogoDarkRemoved:
i.LogoDarkURL = ""
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyIconDarkAdded, org_es_model.LabelPolicyIconDarkAdded:
err = asset.SetData(event)
if err != nil {
return err
}
i.IconDarkURL = asset.AssetURL
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyIconDarkRemoved, org_es_model.LabelPolicyIconDarkRemoved:
i.IconDarkURL = ""
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyFontAdded, org_es_model.LabelPolicyFontAdded:
err = asset.SetData(event)
if err != nil {
return err
}
i.FontURL = asset.AssetURL
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyFontRemoved, org_es_model.LabelPolicyFontRemoved:
i.FontURL = ""
i.State = int32(domain.LabelPolicyStatePreview)
case es_model.LabelPolicyActivated, org_es_model.LabelPolicyActivated:
i.State = int32(domain.LabelPolicyStateActive)
case es_model.LabelPolicyAssetsRemoved, org_es_model.LabelPolicyAssetsRemoved:
i.LogoURL = ""
i.IconURL = ""
i.LogoDarkURL = ""
i.IconDarkURL = ""
i.FontURL = ""
i.State = int32(domain.LabelPolicyStatePreview)
}
return err
}
@@ -86,3 +198,11 @@ func (r *LabelPolicyView) SetData(event *models.Event) error {
}
return nil
}
func (r *AssetView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("MODEL-Ms8f2").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-Hs8uf", "Could not unmarshal data")
}
return nil
}

View File

@@ -53,6 +53,8 @@ func (key LabelPolicySearchKey) ToColumnName() string {
switch iam_model.LabelPolicySearchKey(key) {
case iam_model.LabelPolicySearchKeyAggregateID:
return LabelPolicyKeyAggregateID
case iam_model.LabelPolicySearchKeyState:
return LabelPolicyKeyState
default:
return ""
}

View File

@@ -195,9 +195,24 @@ func (repo *OrgRepository) SearchIDPConfigs(ctx context.Context, request *iam_mo
}
func (repo *OrgRepository) GetLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
policy, err := repo.View.LabelPolicyByAggregateIDAndState(authz.GetCtxData(ctx).OrgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
policy, err = repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
policy, err = repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}
policy.Default = true
}
if err != nil {
return nil, err
}
return iam_es_model.LabelPolicyViewToModel(policy), err
}
func (repo *OrgRepository) GetPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateIDAndState(authz.GetCtxData(ctx).OrgID, int32(domain.LabelPolicyStatePreview))
if errors.IsNotFound(err) {
policy, err = repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return nil, err
}
@@ -210,7 +225,15 @@ func (repo *OrgRepository) GetLabelPolicy(ctx context.Context) (*iam_model.Label
}
func (repo *OrgRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, viewErr := repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
return repo.getDefaultLabelPolicy(ctx, domain.LabelPolicyStateActive)
}
func (repo *OrgRepository) GetPreviewDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
return repo.getDefaultLabelPolicy(ctx, domain.LabelPolicyStatePreview)
}
func (repo *OrgRepository) getDefaultLabelPolicy(ctx context.Context, state domain.LabelPolicyState) (*iam_model.LabelPolicyView, error) {
policy, viewErr := repo.View.LabelPolicyByAggregateIDAndState(repo.SystemDefaults.IamID, int32(state))
if viewErr != nil && !errors.IsNotFound(viewErr) {
return nil, viewErr
}

View File

@@ -4,6 +4,7 @@ import (
"time"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
@@ -30,7 +31,7 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults) []query.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, staticStorage static.Storage) []query.Handler {
return []query.Handler{
newProject(
handler{view, bulkLimit, configs.cycleDuration("Project"), errorCount, es}),
@@ -58,7 +59,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newLoginPolicy(
handler{view, bulkLimit, configs.cycleDuration("LoginPolicy"), errorCount, es}),
newLabelPolicy(
handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}),
handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es},
staticStorage),
newIDPProvider(
handler{view, bulkLimit, configs.cycleDuration("IDPProvider"), errorCount, es},
defaults),

View File

@@ -1,7 +1,10 @@
package handler
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
@@ -9,6 +12,7 @@ import (
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/static"
)
const (
@@ -18,11 +22,13 @@ const (
type LabelPolicy struct {
handler
subscription *v1.Subscription
static static.Storage
}
func newLabelPolicy(handler handler) *LabelPolicy {
func newLabelPolicy(handler handler, static static.Storage) *LabelPolicy {
h := &LabelPolicy{
handler: handler,
static: static,
}
h.subscribe()
@@ -78,12 +84,29 @@ func (m *LabelPolicy) processLabelPolicy(event *es_models.Event) (err error) {
switch event.Type {
case iam_es_model.LabelPolicyAdded, model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged:
policy, err = m.view.LabelPolicyByAggregateID(event.AggregateID)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged,
iam_es_model.LabelPolicyLogoAdded, model.LabelPolicyLogoAdded,
iam_es_model.LabelPolicyLogoRemoved, model.LabelPolicyLogoRemoved,
iam_es_model.LabelPolicyIconAdded, model.LabelPolicyIconAdded,
iam_es_model.LabelPolicyIconRemoved, model.LabelPolicyIconRemoved,
iam_es_model.LabelPolicyLogoDarkAdded, model.LabelPolicyLogoDarkAdded,
iam_es_model.LabelPolicyLogoDarkRemoved, model.LabelPolicyLogoDarkRemoved,
iam_es_model.LabelPolicyIconDarkAdded, model.LabelPolicyIconDarkAdded,
iam_es_model.LabelPolicyIconDarkRemoved, model.LabelPolicyIconDarkRemoved,
iam_es_model.LabelPolicyFontAdded, model.LabelPolicyFontAdded,
iam_es_model.LabelPolicyFontRemoved, model.LabelPolicyFontRemoved:
policy, err = m.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyActivated, model.LabelPolicyActivated:
policy, err = m.view.LabelPolicyByAggregateIDAndState(event.AggregateID, int32(domain.LabelPolicyStatePreview))
if err != nil {
return err
}
go m.CleanUpBucket(policy)
err = policy.AppendEvent(event)
default:
return m.view.ProcessedLabelPolicySequence(event)
}
@@ -101,3 +124,27 @@ func (m *LabelPolicy) OnError(event *es_models.Event, err error) error {
func (m *LabelPolicy) OnSuccess() error {
return spooler.HandleSuccess(m.view.UpdateLabelPolicySpoolerRunTimestamp)
}
func (p *LabelPolicy) CleanUpBucket(policy *iam_model.LabelPolicyView) {
if p.static == nil {
return
}
ctx := context.Background()
objects, err := p.static.ListObjectInfos(ctx, policy.AggregateID, domain.LabelPolicyPrefix+"/", false)
if err != nil {
return
}
for _, object := range objects {
if !deleteableObject(object, policy) {
continue
}
p.static.RemoveObject(ctx, policy.AggregateID, object.Key)
}
}
func deleteableObject(object *domain.AssetInfo, policy *iam_model.LabelPolicyView) bool {
if object.Key == policy.LogoURL || object.Key == policy.LogoDarkURL || object.Key == policy.IconURL || object.Key == policy.IconDarkURL || object.Key == policy.FontURL {
return false
}
return true
}

View File

@@ -120,6 +120,8 @@ func (u *User) ProcessUser(event *es_models.Event) (err error) {
es_model.HumanProfileChanged,
es_model.HumanEmailChanged,
es_model.HumanEmailVerified,
es_model.HumanAvatarAdded,
es_model.HumanAvatarRemoved,
es_model.HumanPhoneChanged,
es_model.HumanPhoneVerified,
es_model.HumanPhoneRemoved,

View File

@@ -3,6 +3,7 @@ package eventsourcing
import (
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/static"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
@@ -31,7 +32,7 @@ type EsRepository struct {
view *mgmt_view.View
}
func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, queries *query.Queries) (*EsRepository, error) {
func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, queries *query.Queries, staticStorage static.Storage) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
if err != nil {
@@ -47,7 +48,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, querie
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, staticStorage)
return &EsRepository{
spooler: spool,

View File

@@ -4,6 +4,7 @@ import (
"database/sql"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/management/repository/eventsourcing/handler"
@@ -17,12 +18,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults, staticStorage static.Storage) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults, staticStorage),
}
spool := spoolerConfig.New()
spool.Start()

View File

@@ -12,8 +12,8 @@ const (
labelPolicyTable = "management.label_policies"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTable, aggregateID, state)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {

View File

@@ -48,5 +48,7 @@ type OrgRepository interface {
GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetPreviewDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
}

View File

@@ -12,13 +12,18 @@ import (
)
type Config struct {
APIDomain string
Repository eventsourcing.Config
}
func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.Commands) {
func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, command *command.Commands, hasStatics bool) {
statikFS, err := fs.NewWithNamespace("notification")
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start listener")
_, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command)
apiDomain := config.APIDomain
if !hasStatics {
apiDomain = ""
}
_, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, apiDomain)
logging.Log("MAIN-9uBxp").OnError(err).Panic("unable to start app")
}

View File

@@ -34,7 +34,7 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) []query.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem, apiDomain string) []query.Handler {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey)
if err != nil {
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
@@ -51,6 +51,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
aesCrypto,
i18n,
dir,
apiDomain,
),
}
}

View File

@@ -7,12 +7,15 @@ import (
"time"
"github.com/caos/logging"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
@@ -23,7 +26,6 @@ import (
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
"golang.org/x/text/language"
)
const (
@@ -50,6 +52,7 @@ type Notification struct {
i18n *i18n.Translator
statikDir http.FileSystem
subscription *v1.Subscription
apiDomain string
}
func newNotification(
@@ -59,6 +62,7 @@ func newNotification(
aesCrypto crypto.EncryptionAlgorithm,
translator *i18n.Translator,
statikDir http.FileSystem,
apiDomain string,
) *Notification {
h := &Notification{
handler: handler,
@@ -67,6 +71,7 @@ func newNotification(
i18n: translator,
statikDir: statikDir,
AesCrypto: aesCrypto,
apiDomain: apiDomain,
}
h.subscribe()
@@ -142,12 +147,13 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
@@ -157,16 +163,16 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeInitCode, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypeInitCode, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendUserInitCode(string(template.Template), text, user, initCode, n.systemDefaults, n.AesCrypto, colors)
err = types.SendUserInitCode(string(template.Template), text, user, initCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.HumanInitCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.HumanInitCodeSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
@@ -180,13 +186,13 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
if err != nil || alreadyHandled {
return err
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
@@ -196,15 +202,15 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypePasswordReset, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypePasswordReset, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendPasswordCode(string(template.Template), text, user, pwCode, n.systemDefaults, n.AesCrypto, colors)
err = types.SendPasswordCode(string(template.Template), text, user, pwCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.PasswordCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.PasswordCodeSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) handleEmailVerificationCode(event *models.Event) (err error) {
@@ -219,12 +225,13 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return nil
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
@@ -234,16 +241,16 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeVerifyEmail, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypeVerifyEmail, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendEmailVerificationCode(string(template.Template), text, user, emailCode, n.systemDefaults, n.AesCrypto, colors)
err = types.SendEmailVerificationCode(string(template.Template), text, user, emailCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.HumanEmailVerificationCodeSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.HumanEmailVerificationCodeSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err error) {
@@ -285,25 +292,26 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
if user.LastEmail == "" {
return nil
}
colors, err := n.getLabelPolicy(context.Background())
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
template, err := n.getMailTemplate(ctx)
if err != nil {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeDomainClaimed, user.PreferredLanguage)
text, err := n.getMailText(ctx, mailTextTypeDomainClaimed, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendDomainClaimed(string(template.Template), text, user, data["userName"], n.systemDefaults, colors)
err = types.SendDomainClaimed(string(template.Template), text, user, data["userName"], n.systemDefaults, colors, n.apiDomain)
if err != nil {
return err
}
return n.command.UserDomainClaimedSent(getSetNotifyContextData(event.ResourceOwner), event.ResourceOwner, event.AggregateID)
return n.command.UserDomainClaimedSent(ctx, event.ResourceOwner, event.AggregateID)
}
func (n *Notification) checkIfCodeAlreadyHandledOrExpired(event *models.Event, expiry time.Duration, eventTypes ...models.EventType) (bool, error) {
@@ -353,10 +361,10 @@ func getSetNotifyContextData(orgID string) context.Context {
// Read organization specific colors
func (n *Notification) getLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
// read from Org
policy, err := n.view.LabelPolicyByAggregateID(authz.GetCtxData(ctx).OrgID, labelPolicyTableOrg)
policy, err := n.view.LabelPolicyByAggregateIDAndState(authz.GetCtxData(ctx).OrgID, labelPolicyTableOrg, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
// read from default
policy, err = n.view.LabelPolicyByAggregateID(n.systemDefaults.IamID, labelPolicyTableDef)
policy, err = n.view.LabelPolicyByAggregateIDAndState(n.systemDefaults.IamID, labelPolicyTableDef, int32(domain.LabelPolicyStateActive))
if err != nil {
return nil, err
}

View File

@@ -26,7 +26,7 @@ type EsRepository struct {
spooler *es_spol.Spooler
}
func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands) (*EsRepository, error) {
func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, apiDomain string) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
if err != nil {
return nil, err
@@ -45,7 +45,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c
if err != nil {
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, translator, dir)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, translator, dir, apiDomain)
return &EsRepository{
spool,

View File

@@ -20,12 +20,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem, apiDomain string) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, i18n, dir),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, i18n, dir, apiDomain),
}
spool := spoolerConfig.New()
spool.Start()

View File

@@ -5,6 +5,6 @@ import (
"github.com/caos/zitadel/internal/iam/repository/view/model"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string, labelPolicyTableVar string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTableVar, aggregateID)
func (v *View) LabelPolicyByAggregateIDAndState(aggregateID, labelPolicyTableVar string, state int32) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateIDAndState(v.Db, labelPolicyTableVar, aggregateID, state)
}

View File

@@ -1,8 +1,9 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>
</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -10,241 +11,362 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
#outlook a { padding:0; }
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
p { display:block;margin:13px 0; }
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,500,700" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Lato:300,400,500,700);
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
.mj-column-per-60 {
width: 60% !important;
max-width: 60%;
}
.mj-column-per-100 { width:100% !important; max-width: 100%; }
.mj-column-per-60 { width:60% !important; max-width: 60%; }
}
</style>
<style type="text/css">
@media only screen and (max-width:480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
table.mj-full-width-mobile { width: 100% !important; }
td.mj-full-width-mobile { width: auto !important; }
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
<style type="text/css">
.shadow a {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
</style>
<style type="text/css">.shadow a {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}</style>
{{if .FontURL}}
<style>
@font-face {
font-family: '{{.FontFamily}}';
font-style: normal;
font-display: swap;
src: url({{.FontURL}});
}
</style>
{{end}}
</head>
<body style="word-spacing:normal;">
<div style="">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#fafafa;background-color:#fafafa;width:100%;border-radius:16px;">
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;border-radius:16px;max-width:800px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;border-radius:16px;">
<tbody>
<div
style=""
>
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:{{.BackgroundColor}};background-color:{{.BackgroundColor}};width:100%;border-radius:16px;"
>
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;border-radius:16px;max-width:800px;">
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;border-radius:16px;"
>
<tbody>
<tr>
<td
style="direction:ltr;font-size:0px;padding:20px 0;padding-left:0;text-align:center;"
>
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="800px" ><![endif]-->
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-left:0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="800px" ><![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<td
style="direction:ltr;font-size:0px;padding:0;text-align:center;"
>
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="width:800px;" ><![endif]-->
<div
class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;"
>
<!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td style="vertical-align:top;width:800px;" ><![endif]-->
<div
class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"
>
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="width:800px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;">
<!--[if mso | IE]><table border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td style="vertical-align:top;width:800px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<tbody>
<td style="vertical-align:top;padding:0;">
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%"
>
<tbody>
<tr>
<td
align="center" style="font-size:0px;padding:50px 0 30px 0;word-break:break-word;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;"
>
<tbody>
<tr>
<td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:50px 0 30px 0;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:180px;">
<img height="auto" src="https://static.zitadel.ch/zitadel-logo-dark@3x.png" style="border:0;border-radius:8px;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="180" />
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<td style="width:180px;">
<img
height="auto" src="{{.LogoURL}}" style="border:0;border-radius:8px;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="180"
/>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
<!--[if mso | IE]></td></tr><tr><td class="" width="800px" ><![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:480px;" ><![endif]-->
<div class="mj-column-per-60 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<tbody>
<tr>
<td style="vertical-align:top;padding:0;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif;font-size:24px;font-weight:500;line-height:1;text-align:center;color:#22292f;">{{.Greeting}}</div>
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif;font-size:16px;font-weight:light;line-height:1.5;text-align:center;color:#22292f;">{{.Text}}</div>
</td>
</tr>
<tr>
<td align="center" vertical-align="middle" class="shadow" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#5282C1" role="presentation" style="border:none;border-radius:6px;cursor:auto;mso-padding-alt:10px 25px;background:#5282C1;" valign="middle">
<a href="{{.URL}}" rel="noopener noreferrer" style="display:inline-block;background:#5282C1;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:6px;" target="_blank">
{{.ButtonText}}
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;padding-top:20px;padding-right:20px;padding-bottom:20px;padding-left:20px;word-break:break-word;">
<p style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:100%;">
</p>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:440px;" role="presentation" width="440px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:16px;word-break:break-word;">
<div style="font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Lato, Arial, Helvetica, sans-serif;font-size:13px;line-height:1;text-align:center;color:#9ca299;"><a href="http://www.caos.ch" style="color:#e91e63; text-decoration: none;" target="_blank"> CAOS AG </a> <span style="color: #000000; margin: 0 .5rem;">|</span> Teufener Strasse 19 <span style="color: #000000; margin: 0 .5rem;">|</span> CH-9000 St. Gallen</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</body>
</tbody>
</table>
</html>
<!--[if mso | IE]></td></tr><tr><td class="" width="800px" ><![endif]-->
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:800px;" width="800" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:800px;">
<table
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;"
>
<tbody>
<tr>
<td
style="direction:ltr;font-size:0px;padding:0;text-align:center;"
>
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:480px;" ><![endif]-->
<div
class="mj-column-per-60 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"
>
<tbody>
<tr>
<td style="vertical-align:top;padding:0;">
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%"
>
<tbody>
<tr>
<td
align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"
>
<div
style="font-family:{{.FontFamily}};font-size:24px;font-weight:500;line-height:1;text-align:center;color:{{.FontColor}};"
>{{.Greeting}}</div>
</td>
</tr>
<tr>
<td
align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;"
>
<div
style="font-family:{{.FontFamily}};font-size:16px;font-weight:light;line-height:1.5;text-align:center;color:{{.FontColor}};"
>{{.Text}}</div>
</td>
</tr>
<tr>
<td
align="center" vertical-align="middle" class="shadow" style="font-size:0px;padding:10px 25px;word-break:break-word;"
>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;"
>
<tr>
<td
align="center" bgcolor="{{.PrimaryColor}}" role="presentation" style="border:none;border-radius:6px;cursor:auto;mso-padding-alt:10px 25px;background:{{.PrimaryColor}};" valign="middle"
>
<a
href="{{.URL}}" rel="noopener noreferrer" style="display:inline-block;background:{{.PrimaryColor}};color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;font-weight:500;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:6px;" target="_blank"
>
{{.ButtonText}}
</a>
</td>
</tr>
</table>
</td>
</tr>
{{if .IncludeFooter}}
<tr>
<td
align="center" style="font-size:0px;padding:10px 25px;padding-top:20px;padding-right:20px;padding-bottom:20px;padding-left:20px;word-break:break-word;"
>
<p
style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:100%;"
>
</p>
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 2px #dbdbdb;font-size:1px;margin:0px auto;width:440px;" role="presentation" width="440px" ><tr><td style="height:0;line-height:0;"> &nbsp;
</td></tr></table><![endif]-->
</td>
</tr>
<tr>
<td
align="center" style="font-size:0px;padding:16px;word-break:break-word;"
>
<div
style="font-family:{{.FontFamily}};font-size:13px;line-height:1;text-align:center;color:{{.FontColor}};"
>{{.FooterText}}</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More