mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-05 22:52:46 +00:00
feat: Privacy policy (#1957)
* feat: command side privacy policy * feat: add privacy policy to api * feat: add privacy policy query side * fix: add privacy policy to mgmt api * fix: add privacy policy to auth and base data * feat: use privacyPolicy in login gui * feat: use privacyPolicy in login gui * feat: test org fatures * feat: typos * feat: tos in register
This commit is contained in:
parent
91f1c88d4e
commit
beb1c1604a
@ -188,4 +188,8 @@ SetUp:
|
||||
Subject: Domain has been claimed
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.UserName}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login.
|
||||
ButtonText: Login
|
||||
ButtonText: Login
|
||||
Step17:
|
||||
PrivacyPolicy:
|
||||
TOSLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
@ -528,6 +528,27 @@ it impacts all organisations without a customised policy
|
||||
|
||||
|
||||
|
||||
### GetPrivacyPolicy
|
||||
|
||||
> **rpc** GetPrivacyPolicy([GetPrivacyPolicyRequest](#getprivacypolicyrequest))
|
||||
[GetPrivacyPolicyResponse](#getprivacypolicyresponse)
|
||||
|
||||
Returns the privacy policy defined by the administrators of ZITADEL
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdatePrivacyPolicy
|
||||
|
||||
> **rpc** UpdatePrivacyPolicy([UpdatePrivacyPolicyRequest](#updateprivacypolicyrequest))
|
||||
[UpdatePrivacyPolicyResponse](#updateprivacypolicyresponse)
|
||||
|
||||
Updates the default privacy policy of ZITADEL
|
||||
it impacts all organisations without a customised policy
|
||||
|
||||
|
||||
|
||||
|
||||
### GetDefaultInitMessageText
|
||||
|
||||
> **rpc** GetDefaultInitMessageText([GetDefaultInitMessageTextRequest](#getdefaultinitmessagetextrequest))
|
||||
@ -1304,6 +1325,23 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetPrivacyPolicyRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### GetPrivacyPolicyResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| policy | zitadel.policy.v1.PrivacyPolicy | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### HealthzRequest
|
||||
This is an empty request
|
||||
|
||||
@ -1868,6 +1906,7 @@ This is an empty request
|
||||
| label_policy_private_label | bool | - | |
|
||||
| label_policy_watermark | bool | - | |
|
||||
| custom_text | bool | - | |
|
||||
| privacy_policy | bool | - | |
|
||||
|
||||
|
||||
|
||||
@ -2023,6 +2062,7 @@ This is an empty request
|
||||
| label_policy_private_label | bool | - | |
|
||||
| label_policy_watermark | bool | - | |
|
||||
| custom_text | bool | - | |
|
||||
| privacy_policy | bool | - | |
|
||||
|
||||
|
||||
|
||||
@ -2380,6 +2420,29 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### UpdatePrivacyPolicyRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| tos_link | string | - | |
|
||||
| privacy_link | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdatePrivacyPolicyResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### View
|
||||
|
||||
|
||||
|
@ -1586,6 +1586,61 @@ The password lockout policy is not used at the moment
|
||||
|
||||
|
||||
|
||||
### GetPrivacyPolicy
|
||||
|
||||
> **rpc** GetPrivacyPolicy([GetPrivacyPolicyRequest](#getprivacypolicyrequest))
|
||||
[GetPrivacyPolicyResponse](#getprivacypolicyresponse)
|
||||
|
||||
Returns the privacy policy of the organisation
|
||||
With this policy privacy relevant things can be configured (e.g. tos link)
|
||||
|
||||
|
||||
|
||||
|
||||
### GetDefaultPrivacyPolicy
|
||||
|
||||
> **rpc** GetDefaultPrivacyPolicy([GetDefaultPrivacyPolicyRequest](#getdefaultprivacypolicyrequest))
|
||||
[GetDefaultPrivacyPolicyResponse](#getdefaultprivacypolicyresponse)
|
||||
|
||||
Returns the default privacy policy of the IAM
|
||||
With this policy the privacy relevant things can be configured (e.g tos link)
|
||||
|
||||
|
||||
|
||||
|
||||
### AddCustomPrivacyPolicy
|
||||
|
||||
> **rpc** AddCustomPrivacyPolicy([AddCustomPrivacyPolicyRequest](#addcustomprivacypolicyrequest))
|
||||
[AddCustomPrivacyPolicyResponse](#addcustomprivacypolicyresponse)
|
||||
|
||||
Add a custom privacy policy for the organisation
|
||||
With this policy privacy relevant things can be configured (e.g. tos link)
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateCustomPrivacyPolicy
|
||||
|
||||
> **rpc** UpdateCustomPrivacyPolicy([UpdateCustomPrivacyPolicyRequest](#updatecustomprivacypolicyrequest))
|
||||
[UpdateCustomPrivacyPolicyResponse](#updatecustomprivacypolicyresponse)
|
||||
|
||||
Update the privacy complexity policy for the organisation
|
||||
With this policy privacy relevant things can be configured (e.g. tos link)
|
||||
|
||||
|
||||
|
||||
|
||||
### ResetPrivacyPolicyToDefault
|
||||
|
||||
> **rpc** ResetPrivacyPolicyToDefault([ResetPrivacyPolicyToDefaultRequest](#resetprivacypolicytodefaultrequest))
|
||||
[ResetPrivacyPolicyToDefaultResponse](#resetprivacypolicytodefaultresponse)
|
||||
|
||||
Removes the privacy policy of the organisation
|
||||
The default policy of the IAM will trigger after
|
||||
|
||||
|
||||
|
||||
|
||||
### GetLabelPolicy
|
||||
|
||||
> **rpc** GetLabelPolicy([GetLabelPolicyRequest](#getlabelpolicyrequest))
|
||||
@ -2227,6 +2282,29 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### AddCustomPrivacyPolicyRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| tos_link | string | - | |
|
||||
| privacy_link | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### AddCustomPrivacyPolicyResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### AddHumanUserRequest
|
||||
|
||||
|
||||
@ -3239,6 +3317,23 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetDefaultPrivacyPolicyRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### GetDefaultPrivacyPolicyResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| policy | zitadel.policy.v1.PrivacyPolicy | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetDefaultVerifyEmailMessageTextRequest
|
||||
|
||||
|
||||
@ -3637,6 +3732,23 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetPrivacyPolicyRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### GetPrivacyPolicyResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| policy | zitadel.policy.v1.PrivacyPolicy | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetProjectByIDRequest
|
||||
|
||||
|
||||
@ -5607,6 +5719,23 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### ResetPrivacyPolicyToDefaultRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### ResetPrivacyPolicyToDefaultResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### SendHumanResetPasswordNotificationRequest
|
||||
|
||||
|
||||
@ -6044,6 +6173,29 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### UpdateCustomPrivacyPolicyRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| tos_link | string | - | |
|
||||
| privacy_link | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateCustomPrivacyPolicyResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateHumanEmailRequest
|
||||
|
||||
|
||||
|
@ -112,6 +112,20 @@ title: zitadel/policy.proto
|
||||
|
||||
|
||||
|
||||
### PrivacyPolicy
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| tos_link | string | - | |
|
||||
| privacy_link | string | - | |
|
||||
| is_default | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Enums
|
||||
|
@ -372,6 +372,33 @@ func (repo *IAMRepository) GetDefaultMessageText(ctx context.Context, textType,
|
||||
return iam_es_model.MessageTextViewToModel(text), err
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) {
|
||||
policy, viewErr := repo.View.PrivacyPolicyByAggregateID(repo.SystemDefaults.IamID)
|
||||
if viewErr != nil && !caos_errs.IsNotFound(viewErr) {
|
||||
return nil, viewErr
|
||||
}
|
||||
if caos_errs.IsNotFound(viewErr) {
|
||||
policy = new(iam_es_model.PrivacyPolicyView)
|
||||
}
|
||||
events, esErr := repo.getIAMEvents(ctx, policy.Sequence)
|
||||
if caos_errs.IsNotFound(viewErr) && len(events) == 0 {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-84Nfs", "Errors.IAM.PrivacyPolicy.NotFound")
|
||||
}
|
||||
if esErr != nil {
|
||||
logging.Log("EVENT-0p3Fs").WithError(esErr).Debug("error retrieving new events")
|
||||
return iam_es_model.PrivacyViewToModel(policy), nil
|
||||
}
|
||||
policyCopy := *policy
|
||||
for _, event := range events {
|
||||
if err := policyCopy.AppendEvent(event); err != nil {
|
||||
return iam_es_model.PrivacyViewToModel(policy), nil
|
||||
}
|
||||
}
|
||||
result := iam_es_model.PrivacyViewToModel(policy)
|
||||
result.Default = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) {
|
||||
query, err := iam_view.IAMByIDQuery(domain.IAMID, sequence)
|
||||
if err != nil {
|
||||
|
@ -66,6 +66,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
|
||||
newFeatures(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
|
||||
newPrivacyPolicy(
|
||||
handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||
}
|
||||
if static != nil {
|
||||
handlers = append(handlers, newStyling(
|
||||
|
@ -0,0 +1,106 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
|
||||
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_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
const (
|
||||
privacyPolicyTable = "adminapi.privacy_policies"
|
||||
)
|
||||
|
||||
type PrivacyPolicy struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newPrivacyPolicy(handler handler) *PrivacyPolicy {
|
||||
h := &PrivacyPolicy{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) subscribe() {
|
||||
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range p.subscription.Events {
|
||||
query.ReduceEvent(p, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) ViewModel() string {
|
||||
return privacyPolicyTable
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) AggregateTypes() []es_models.AggregateType {
|
||||
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) CurrentSequence() (uint64, error) {
|
||||
sequence, err := p.view.GetLatestPrivacyPolicySequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := p.view.GetLatestPrivacyPolicySequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(p.AggregateTypes()...).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) Reduce(event *es_models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case model.OrgAggregate, iam_es_model.IAMAggregate:
|
||||
err = p.processPrivacyPolicy(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) processPrivacyPolicy(event *es_models.Event) (err error) {
|
||||
policy := new(iam_model.PrivacyPolicyView)
|
||||
switch event.Type {
|
||||
case iam_es_model.PrivacyPolicyAdded, model.PrivacyPolicyAdded:
|
||||
err = policy.AppendEvent(event)
|
||||
case iam_es_model.PrivacyPolicyChanged, model.PrivacyPolicyChanged:
|
||||
policy, err = p.view.PrivacyPolicyByAggregateID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = policy.AppendEvent(event)
|
||||
case model.PrivacyPolicyRemoved:
|
||||
return p.view.DeletePrivacyPolicy(event.AggregateID, event)
|
||||
default:
|
||||
return p.view.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.PutPrivacyPolicy(policy, event)
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) OnError(event *es_models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-4N8sw", "id", event.AggregateID).WithError(err).Warn("something went wrong in privacy policy handler")
|
||||
return spooler.HandleError(event, err, p.view.GetLatestPrivacyPolicyFailedEvent, p.view.ProcessedPrivacyPolicyFailedEvent, p.view.ProcessedPrivacyPolicySequence, p.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) OnSuccess() error {
|
||||
return spooler.HandleSuccess(p.view.UpdatePrivacyPolicySpoolerRunTimestamp)
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"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 (
|
||||
privacyPolicyTable = "adminapi.privacy_policies"
|
||||
)
|
||||
|
||||
func (v *View) PrivacyPolicyByAggregateID(aggregateID string) (*model.PrivacyPolicyView, error) {
|
||||
return view.GetPrivacyPolicyByAggregateID(v.Db, privacyPolicyTable, aggregateID)
|
||||
}
|
||||
|
||||
func (v *View) PutPrivacyPolicy(policy *model.PrivacyPolicyView, event *models.Event) error {
|
||||
err := view.PutPrivacyPolicy(v.Db, privacyPolicyTable, policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeletePrivacyPolicy(aggregateID string, event *models.Event) error {
|
||||
err := view.DeletePrivacyPolicy(v.Db, privacyPolicyTable, aggregateID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestPrivacyPolicySequence() (*global_view.CurrentSequence, error) {
|
||||
return v.latestSequence(privacyPolicyTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedPrivacyPolicySequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(privacyPolicyTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdatePrivacyPolicySpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(privacyPolicyTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestPrivacyPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(privacyPolicyTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedPrivacyPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -37,5 +37,7 @@ type IAMRepository interface {
|
||||
|
||||
GetDefaultPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error)
|
||||
|
||||
GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)
|
||||
|
||||
GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error)
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
|
||||
LabelPolicyWatermark: req.LabelPolicyWatermark,
|
||||
CustomDomain: req.CustomDomain,
|
||||
CustomText: req.CustomText,
|
||||
PrivacyPolicy: req.PrivacyPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,5 +97,6 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
|
||||
LabelPolicyWatermark: req.LabelPolicyWatermark,
|
||||
CustomDomain: req.CustomDomain,
|
||||
CustomText: req.CustomText,
|
||||
PrivacyPolicy: req.PrivacyPolicy,
|
||||
}
|
||||
}
|
||||
|
31
internal/api/grpc/admin/privacy_policy.go
Normal file
31
internal/api/grpc/admin/privacy_policy.go
Normal file
@ -0,0 +1,31 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
|
||||
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
func (s *Server) GetPrivacyPolicy(ctx context.Context, _ *admin_pb.GetPrivacyPolicyRequest) (*admin_pb.GetPrivacyPolicyResponse, error) {
|
||||
policy, err := s.iam.GetDefaultPrivacyPolicy(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetPrivacyPolicyResponse{Policy: policy_grpc.ModelPrivacyPolicyToPb(policy)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdatePrivacyPolicy(ctx context.Context, req *admin_pb.UpdatePrivacyPolicyRequest) (*admin_pb.UpdatePrivacyPolicyResponse, error) {
|
||||
result, err := s.command.ChangeDefaultPrivacyPolicy(ctx, UpdatePrivacyPolicyToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.UpdatePrivacyPolicyResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.ChangeDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
13
internal/api/grpc/admin/privacy_policy_converter.go
Normal file
13
internal/api/grpc/admin/privacy_policy_converter.go
Normal file
@ -0,0 +1,13 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
func UpdatePrivacyPolicyToDomain(req *admin_pb.UpdatePrivacyPolicyRequest) *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
TOSLink: req.TosLink,
|
||||
PrivacyLink: req.PrivacyLink,
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
|
||||
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
|
||||
LabelPolicyWatermark: features.LabelPolicyWatermark,
|
||||
CustomText: features.CustomText,
|
||||
PrivacyPolicy: features.PrivacyPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
|
64
internal/api/grpc/management/policy_privacy.go
Normal file
64
internal/api/grpc/management/policy_privacy.go
Normal file
@ -0,0 +1,64 @@
|
||||
package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func (s *Server) GetPrivacyPolicy(ctx context.Context, _ *mgmt_pb.GetPrivacyPolicyRequest) (*mgmt_pb.GetPrivacyPolicyResponse, error) {
|
||||
policy, err := s.org.GetPrivacyPolicy(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetPrivacyPolicyResponse{Policy: policy_grpc.ModelPrivacyPolicyToPb(policy)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetDefaultPrivacyPolicy(ctx context.Context, _ *mgmt_pb.GetDefaultPrivacyPolicyRequest) (*mgmt_pb.GetDefaultPrivacyPolicyResponse, error) {
|
||||
policy, err := s.org.GetDefaultPrivacyPolicy(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultPrivacyPolicyResponse{Policy: policy_grpc.ModelPrivacyPolicyToPb(policy)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddCustomPrivacyPolicy(ctx context.Context, req *mgmt_pb.AddCustomPrivacyPolicyRequest) (*mgmt_pb.AddCustomPrivacyPolicyResponse, error) {
|
||||
result, err := s.command.AddPrivacyPolicy(ctx, authz.GetCtxData(ctx).OrgID, AddPrivacyPolicyToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddCustomPrivacyPolicyResponse{
|
||||
Details: object.AddToDetailsPb(
|
||||
result.Sequence,
|
||||
result.ChangeDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateCustomPrivacyPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomPrivacyPolicyRequest) (*mgmt_pb.UpdateCustomPrivacyPolicyResponse, error) {
|
||||
result, err := s.command.ChangePrivacyPolicy(ctx, authz.GetCtxData(ctx).OrgID, UpdatePrivacyPolicyToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateCustomPrivacyPolicyResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.ChangeDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResetPrivacyPolicyToDefault(ctx context.Context, _ *mgmt_pb.ResetPrivacyPolicyToDefaultRequest) (*mgmt_pb.ResetPrivacyPolicyToDefaultResponse, error) {
|
||||
objectDetails, err := s.command.RemovePrivacyPolicy(ctx, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ResetPrivacyPolicyToDefaultResponse{
|
||||
Details: object.DomainToChangeDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}
|
20
internal/api/grpc/management/policy_privacy_converter.go
Normal file
20
internal/api/grpc/management/policy_privacy_converter.go
Normal file
@ -0,0 +1,20 @@
|
||||
package management
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func AddPrivacyPolicyToDomain(req *mgmt_pb.AddCustomPrivacyPolicyRequest) *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
TOSLink: req.TosLink,
|
||||
PrivacyLink: req.PrivacyLink,
|
||||
}
|
||||
}
|
||||
|
||||
func UpdatePrivacyPolicyToDomain(req *mgmt_pb.UpdateCustomPrivacyPolicyRequest) *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
TOSLink: req.TosLink,
|
||||
PrivacyLink: req.PrivacyLink,
|
||||
}
|
||||
}
|
21
internal/api/grpc/policy/privacy_policy.go
Normal file
21
internal/api/grpc/policy/privacy_policy.go
Normal file
@ -0,0 +1,21 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/iam/model"
|
||||
policy_pb "github.com/caos/zitadel/pkg/grpc/policy"
|
||||
)
|
||||
|
||||
func ModelPrivacyPolicyToPb(policy *model.PrivacyPolicyView) *policy_pb.PrivacyPolicy {
|
||||
return &policy_pb.PrivacyPolicy{
|
||||
IsDefault: policy.Default,
|
||||
TosLink: policy.TOSLink,
|
||||
PrivacyLink: policy.PrivacyLink,
|
||||
Details: object.ToViewDetailsPb(
|
||||
policy.Sequence,
|
||||
policy.CreationDate,
|
||||
policy.ChangeDate,
|
||||
"", //TODO: resourceowner
|
||||
),
|
||||
}
|
||||
}
|
@ -440,6 +440,11 @@ func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *domai
|
||||
if idpProviders != nil {
|
||||
request.AllowedExternalIDPs = idpProviders
|
||||
}
|
||||
privacyPolicy, err := repo.getPrivacyPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.PrivacyPolicy = privacyPolicy
|
||||
labelPolicy, err := repo.getLabelPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -719,6 +724,21 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (
|
||||
return iam_es_model.LoginPolicyViewToModel(policy), err
|
||||
}
|
||||
|
||||
func (repo *AuthRequestRepo) getPrivacyPolicy(ctx context.Context, orgID string) (*domain.PrivacyPolicy, error) {
|
||||
policy, err := repo.View.PrivacyPolicyByAggregateID(orgID)
|
||||
if errors.IsNotFound(err) {
|
||||
policy, err = repo.View.PrivacyPolicyByAggregateID(repo.IAMID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy.Default = true
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy.ToDomain(), err
|
||||
}
|
||||
|
||||
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*domain.LabelPolicy, error) {
|
||||
policy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
|
||||
if errors.IsNotFound(err) {
|
||||
|
@ -115,3 +115,11 @@ func (repo *OrgRepository) GetLabelPolicy(ctx context.Context, orgID string) (*i
|
||||
}
|
||||
return iam_view_model.LabelPolicyViewToModel(orgPolicy), nil
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) {
|
||||
policy, err := repo.View.PrivacyPolicyByAggregateID(repo.SystemDefaults.IamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_view_model.PrivacyViewToModel(policy), nil
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
newLabelPolicy(handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}),
|
||||
newFeatures(handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
|
||||
newRefreshToken(handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}),
|
||||
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||
}
|
||||
}
|
||||
|
||||
|
106
internal/auth/repository/eventsourcing/handler/privacy_policy.go
Normal file
106
internal/auth/repository/eventsourcing/handler/privacy_policy.go
Normal file
@ -0,0 +1,106 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
|
||||
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_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
const (
|
||||
privacyPolicyTable = "auth.privacy_policies"
|
||||
)
|
||||
|
||||
type PrivacyPolicy struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newPrivacyPolicy(handler handler) *PrivacyPolicy {
|
||||
h := &PrivacyPolicy{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) subscribe() {
|
||||
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range p.subscription.Events {
|
||||
query.ReduceEvent(p, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) ViewModel() string {
|
||||
return privacyPolicyTable
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) AggregateTypes() []es_models.AggregateType {
|
||||
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) CurrentSequence() (uint64, error) {
|
||||
sequence, err := p.view.GetLatestPrivacyPolicySequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := p.view.GetLatestPrivacyPolicySequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(p.AggregateTypes()...).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) Reduce(event *es_models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case model.OrgAggregate, iam_es_model.IAMAggregate:
|
||||
err = p.processPrivacyPolicy(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) processPrivacyPolicy(event *es_models.Event) (err error) {
|
||||
policy := new(iam_model.PrivacyPolicyView)
|
||||
switch event.Type {
|
||||
case iam_es_model.PrivacyPolicyAdded, model.PrivacyPolicyAdded:
|
||||
err = policy.AppendEvent(event)
|
||||
case iam_es_model.PrivacyPolicyChanged, model.PrivacyPolicyChanged:
|
||||
policy, err = p.view.PrivacyPolicyByAggregateID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = policy.AppendEvent(event)
|
||||
case model.PrivacyPolicyRemoved:
|
||||
return p.view.DeletePrivacyPolicy(event.AggregateID, event)
|
||||
default:
|
||||
return p.view.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.PutPrivacyPolicy(policy, event)
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) OnError(event *es_models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-4N8sw", "id", event.AggregateID).WithError(err).Warn("something went wrong in privacy policy handler")
|
||||
return spooler.HandleError(event, err, p.view.GetLatestPrivacyPolicyFailedEvent, p.view.ProcessedPrivacyPolicyFailedEvent, p.view.ProcessedPrivacyPolicySequence, p.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) OnSuccess() error {
|
||||
return spooler.HandleSuccess(p.view.UpdatePrivacyPolicySpoolerRunTimestamp)
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"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 (
|
||||
privacyPolicyTable = "auth.privacy_policies"
|
||||
)
|
||||
|
||||
func (v *View) PrivacyPolicyByAggregateID(aggregateID string) (*model.PrivacyPolicyView, error) {
|
||||
return view.GetPrivacyPolicyByAggregateID(v.Db, privacyPolicyTable, aggregateID)
|
||||
}
|
||||
|
||||
func (v *View) PutPrivacyPolicy(policy *model.PrivacyPolicyView, event *models.Event) error {
|
||||
err := view.PutPrivacyPolicy(v.Db, privacyPolicyTable, policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeletePrivacyPolicy(aggregateID string, event *models.Event) error {
|
||||
err := view.DeletePrivacyPolicy(v.Db, privacyPolicyTable, aggregateID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestPrivacyPolicySequence() (*global_view.CurrentSequence, error) {
|
||||
return v.latestSequence(privacyPolicyTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedPrivacyPolicySequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(privacyPolicyTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdatePrivacyPolicySpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(privacyPolicyTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestPrivacyPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(privacyPolicyTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedPrivacyPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -13,4 +13,5 @@ type OrgRepository interface {
|
||||
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)
|
||||
GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)
|
||||
}
|
||||
|
@ -157,6 +157,12 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
|
||||
}
|
||||
continue
|
||||
}
|
||||
if requiredFeature == domain.FeaturePrivacyPolicy {
|
||||
if !features.PrivacyPolicy {
|
||||
return MissingFeatureErr(requiredFeature)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return MissingFeatureErr(requiredFeature)
|
||||
}
|
||||
return nil
|
||||
|
@ -27,6 +27,7 @@ type FeaturesWriteModel struct {
|
||||
LabelPolicyWatermark bool
|
||||
CustomDomain bool
|
||||
CustomText bool
|
||||
PrivacyPolicy bool
|
||||
}
|
||||
|
||||
func (wm *FeaturesWriteModel) Reduce() error {
|
||||
|
@ -120,6 +120,14 @@ func writeModelToPasswordLockoutPolicy(wm *PasswordLockoutPolicyWriteModel) *dom
|
||||
}
|
||||
}
|
||||
|
||||
func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
TOSLink: wm.TOSLink,
|
||||
PrivacyLink: wm.PrivacyLink,
|
||||
}
|
||||
}
|
||||
|
||||
func writeModelToIDPConfig(wm *IDPConfigWriteModel) *domain.IDPConfig {
|
||||
return &domain.IDPConfig{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
|
@ -50,6 +50,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
|
||||
features.LabelPolicyWatermark,
|
||||
features.CustomDomain,
|
||||
features.CustomText,
|
||||
features.PrivacyPolicy,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||
|
@ -65,7 +65,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
||||
labelPolicyPrivateLabel,
|
||||
labelPolicyWatermark,
|
||||
customDomain,
|
||||
customText bool,
|
||||
customText,
|
||||
privacyPolicy bool,
|
||||
) (*iam.FeaturesSetEvent, bool) {
|
||||
|
||||
changes := make([]features.FeaturesChanges, 0)
|
||||
@ -115,7 +116,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
|
||||
if wm.CustomText != customText {
|
||||
changes = append(changes, features.ChangeCustomText(customText))
|
||||
}
|
||||
|
||||
if wm.PrivacyPolicy != privacyPolicy {
|
||||
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
92
internal/command/iam_policy_privacy.go
Normal file
92
internal/command/iam_policy_privacy.go
Normal file
@ -0,0 +1,92 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
iam_repo "github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *Commands) getDefaultPrivacyPolicy(ctx context.Context) (*domain.PrivacyPolicy, error) {
|
||||
policyWriteModel := NewIAMPrivacyPolicyWriteModel()
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, policyWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !policyWriteModel.State.Exists() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-559os", "Errors.IAM.OrgIAMPolicy.NotFound")
|
||||
}
|
||||
policy := writeModelToPrivacyPolicy(&policyWriteModel.PrivacyPolicyWriteModel)
|
||||
policy.Default = true
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
|
||||
addedPolicy := NewIAMPrivacyPolicyWriteModel()
|
||||
iamAgg := IAMAggregateFromWriteModel(&addedPolicy.WriteModel)
|
||||
events, err := c.addDefaultPrivacyPolicy(ctx, iamAgg, addedPolicy, policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(addedPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToPrivacyPolicy(&addedPolicy.PrivacyPolicyWriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addDefaultPrivacyPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMPrivacyPolicyWriteModel, policy *domain.PrivacyPolicy) (eventstore.EventPusher, error) {
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedPolicy.State == domain.PolicyStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-M00rJ", "Errors.IAM.PrivacyPolicy.AlreadyExists")
|
||||
}
|
||||
|
||||
return iam_repo.NewPrivacyPolicyAddedEvent(ctx, iamAgg, policy.TOSLink, policy.PrivacyLink), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeDefaultPrivacyPolicy(ctx context.Context, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
|
||||
existingPolicy, err := c.defaultPrivacyPolicyWriteModelByID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "IAM-0oPew", "Errors.IAM.PasswordAgePolicy.NotFound")
|
||||
}
|
||||
|
||||
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel)
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.TOSLink, policy.PrivacyLink)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged")
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToPrivacyPolicy(&existingPolicy.PrivacyPolicyWriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) defaultPrivacyPolicyWriteModelByID(ctx context.Context) (policy *IAMPrivacyPolicyWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewIAMPrivacyPolicyWriteModel()
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
73
internal/command/iam_policy_privacy_model.go
Normal file
73
internal/command/iam_policy_privacy_model.go
Normal file
@ -0,0 +1,73 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
type IAMPrivacyPolicyWriteModel struct {
|
||||
PrivacyPolicyWriteModel
|
||||
}
|
||||
|
||||
func NewIAMPrivacyPolicyWriteModel() *IAMPrivacyPolicyWriteModel {
|
||||
return &IAMPrivacyPolicyWriteModel{
|
||||
PrivacyPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: domain.IAMID,
|
||||
ResourceOwner: domain.IAMID,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IAMPrivacyPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *iam.PrivacyPolicyAddedEvent:
|
||||
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyAddedEvent)
|
||||
case *iam.PrivacyPolicyChangedEvent:
|
||||
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyChangedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IAMPrivacyPolicyWriteModel) Reduce() error {
|
||||
return wm.PrivacyPolicyWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *IAMPrivacyPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
|
||||
AggregateIDs(wm.PrivacyPolicyWriteModel.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
EventTypes(
|
||||
iam.PrivacyPolicyAddedEventType,
|
||||
iam.PrivacyPolicyChangedEventType)
|
||||
}
|
||||
|
||||
func (wm *IAMPrivacyPolicyWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
tosLink,
|
||||
privacyLink string,
|
||||
) (*iam.PrivacyPolicyChangedEvent, bool) {
|
||||
|
||||
changes := make([]policy.PrivacyPolicyChanges, 0)
|
||||
if wm.TOSLink != tosLink {
|
||||
changes = append(changes, policy.ChangeTOSLink(tosLink))
|
||||
}
|
||||
if wm.PrivacyLink != privacyLink {
|
||||
changes = append(changes, policy.ChangePrivacyLink(privacyLink))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
changedEvent, err := iam.NewPrivacyPolicyChangedEvent(ctx, aggregate, changes)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return changedEvent, true
|
||||
}
|
292
internal/command/iam_policy_privacy_test.go
Normal file
292
internal/command/iam_policy_privacy_test.go
Normal file
@ -0,0 +1,292 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
policy *domain.PrivacyPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.PrivacyPolicy
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "privacy policy already existing, already exists error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add policy,ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
},
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add empty policy,ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "",
|
||||
PrivacyLink: "",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
},
|
||||
TOSLink: "",
|
||||
PrivacyLink: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.AddDefaultPrivacyPolicy(tt.args.ctx, tt.args.policy)
|
||||
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_ChangeDefaultPrivacyPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
policy *domain.PrivacyPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.PrivacyPolicy
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "privacy policy not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newDefaultPrivacyPolicyChangedEvent(context.Background(),
|
||||
"TOSLinkChanged",
|
||||
"PrivacyLinkChanged",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLinkChanged",
|
||||
PrivacyLink: "PrivacyLinkChanged",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
},
|
||||
TOSLink: "TOSLinkChanged",
|
||||
PrivacyLink: "PrivacyLinkChanged",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.ChangeDefaultPrivacyPolicy(tt.args.ctx, tt.args.policy)
|
||||
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 newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLink string) *iam.PrivacyPolicyChangedEvent {
|
||||
event, _ := iam.NewPrivacyPolicyChangedEvent(ctx,
|
||||
&iam.NewAggregate().Aggregate,
|
||||
[]policy.PrivacyPolicyChanges{
|
||||
policy.ChangeTOSLink(tosLink),
|
||||
policy.ChangePrivacyLink(privacyLink),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}
|
@ -41,3 +41,11 @@ func orgDomainWriteModelToOrgDomain(wm *OrgDomainWriteModel) *domain.OrgDomain {
|
||||
ValidationCode: wm.ValidationCode,
|
||||
}
|
||||
}
|
||||
|
||||
func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.PrivacyPolicyWriteModel.WriteModel),
|
||||
TOSLink: wm.TOSLink,
|
||||
PrivacyLink: wm.PrivacyLink,
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
|
||||
features.LabelPolicyWatermark,
|
||||
features.CustomDomain,
|
||||
features.CustomText,
|
||||
features.PrivacyPolicy,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||
@ -136,6 +137,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
|
||||
events = append(events, removeCustomTextEvents...)
|
||||
}
|
||||
}
|
||||
if !features.PrivacyPolicy {
|
||||
removePrivacyPolicyEvent, err := c.removePrivacyPolicyIfExists(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if removePrivacyPolicyEvent != nil {
|
||||
events = append(events, removePrivacyPolicyEvent)
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
||||
labelPolicyPrivateLabel,
|
||||
labelPolicyWatermark,
|
||||
customDomain,
|
||||
customText bool,
|
||||
customText,
|
||||
privacyPolicy bool,
|
||||
) (*org.FeaturesSetEvent, bool) {
|
||||
|
||||
changes := make([]features.FeaturesChanges, 0)
|
||||
@ -125,6 +126,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
|
||||
if wm.CustomText != customText {
|
||||
changes = append(changes, features.ChangeCustomText(customText))
|
||||
}
|
||||
if wm.PrivacyPolicy != privacyPolicy {
|
||||
changes = append(changes, features.ChangePrivacyPolicy(privacyPolicy))
|
||||
}
|
||||
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
|
@ -239,6 +239,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"toslink",
|
||||
"privacylink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@ -267,6 +277,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
LabelPolicyWatermark: false,
|
||||
CustomDomain: false,
|
||||
CustomText: false,
|
||||
PrivacyPolicy: false,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@ -399,6 +410,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"toslink",
|
||||
"privacylink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@ -572,6 +593,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"toslink",
|
||||
"privacylink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@ -755,6 +786,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"toslink",
|
||||
"privacylink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@ -993,6 +1034,16 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"toslink",
|
||||
"privacylink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
@ -1016,6 +1067,9 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
|
||||
eventFromEventPusher(
|
||||
org.NewCustomTextTemplateRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.InitCodeMessageType, language.English),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
|
||||
),
|
||||
@ -1221,6 +1275,16 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewPrivacyPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"toslink",
|
||||
"privacylink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
|
135
internal/command/org_policy_privacy.go
Normal file
135
internal/command/org_policy_privacy.go
Normal file
@ -0,0 +1,135 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func (c *Commands) getOrgPrivacyPolicy(ctx context.Context, orgID string) (*domain.PrivacyPolicy, error) {
|
||||
policy, err := c.orgPrivacyPolicyWriteModelByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if policy.State == domain.PolicyStateActive {
|
||||
return orgWriteModelToPrivacyPolicy(policy), nil
|
||||
}
|
||||
return c.getDefaultPrivacyPolicy(ctx)
|
||||
}
|
||||
|
||||
func (c *Commands) orgPrivacyPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgPrivacyPolicyWriteModel, error) {
|
||||
policy := NewOrgPrivacyPolicyWriteModel(orgID)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddPrivacyPolicy(ctx context.Context, resourceOwner string, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MMk9fs", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
addedPolicy := NewOrgPrivacyPolicyWriteModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedPolicy.State == domain.PolicyStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-0oLpd", "Errors.Org.PrivacyPolicy.AlreadyExists")
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
|
||||
pushedEvents, err := c.eventstore.PushEvents(
|
||||
ctx,
|
||||
org.NewPrivacyPolicyAddedEvent(
|
||||
ctx,
|
||||
orgAgg,
|
||||
policy.TOSLink,
|
||||
policy.PrivacyLink))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(addedPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToPrivacyPolicy(&addedPolicy.PrivacyPolicyWriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangePrivacyPolicy(ctx context.Context, resourceOwner string, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-22N89f", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
|
||||
existingPolicy := NewOrgPrivacyPolicyWriteModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-Ng8sf", "Errors.Org.PrivacyPolicy.NotFound")
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel)
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.TOSLink, policy.PrivacyLink)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToPrivacyPolicy(&existingPolicy.PrivacyPolicyWriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemovePrivacyPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
|
||||
if orgID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Nf9sf", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
existingPolicy := NewOrgPrivacyPolicyWriteModel(orgID)
|
||||
event, err := c.removePrivacyPolicy(ctx, existingPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingPolicy.PrivacyPolicyWriteModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removePrivacyPolicy(ctx context.Context, existingPolicy *OrgPrivacyPolicyWriteModel) (*org.PrivacyPolicyRemovedEvent, error) {
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-Ze9gs", "Errors.Org.PrivacyPolicy.NotFound")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
|
||||
return org.NewPrivacyPolicyRemovedEvent(ctx, orgAgg), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removePrivacyPolicyIfExists(ctx context.Context, orgID string) (*org.PrivacyPolicyRemovedEvent, error) {
|
||||
existingPolicy, err := c.orgPrivacyPolicyWriteModelByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingPolicy.State != domain.PolicyStateActive {
|
||||
return nil, nil
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
|
||||
return org.NewPrivacyPolicyRemovedEvent(ctx, orgAgg), nil
|
||||
}
|
74
internal/command/org_policy_privacy_model.go
Normal file
74
internal/command/org_policy_privacy_model.go
Normal file
@ -0,0 +1,74 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
type OrgPrivacyPolicyWriteModel struct {
|
||||
PrivacyPolicyWriteModel
|
||||
}
|
||||
|
||||
func NewOrgPrivacyPolicyWriteModel(orgID string) *OrgPrivacyPolicyWriteModel {
|
||||
return &OrgPrivacyPolicyWriteModel{
|
||||
PrivacyPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgPrivacyPolicyWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.PrivacyPolicyAddedEvent:
|
||||
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyAddedEvent)
|
||||
case *org.PrivacyPolicyChangedEvent:
|
||||
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyChangedEvent)
|
||||
case *org.PrivacyPolicyRemovedEvent:
|
||||
wm.PrivacyPolicyWriteModel.AppendEvents(&e.PrivacyPolicyRemovedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgPrivacyPolicyWriteModel) Reduce() error {
|
||||
return wm.PrivacyPolicyWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *OrgPrivacyPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
|
||||
AggregateIDs(wm.PrivacyPolicyWriteModel.AggregateID).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
EventTypes(org.PrivacyPolicyAddedEventType,
|
||||
org.PrivacyPolicyChangedEventType,
|
||||
org.PrivacyPolicyRemovedEventType)
|
||||
}
|
||||
|
||||
func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
tosLink,
|
||||
privacyLink string,
|
||||
) (*org.PrivacyPolicyChangedEvent, bool) {
|
||||
|
||||
changes := make([]policy.PrivacyPolicyChanges, 0)
|
||||
if wm.TOSLink != tosLink {
|
||||
changes = append(changes, policy.ChangeTOSLink(tosLink))
|
||||
}
|
||||
if wm.PrivacyLink != privacyLink {
|
||||
changes = append(changes, policy.ChangePrivacyLink(privacyLink))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
changedEvent, err := org.NewPrivacyPolicyChangedEvent(ctx, aggregate, changes)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return changedEvent, true
|
||||
}
|
479
internal/command/org_policy_privacy_test.go
Normal file
479
internal/command/org_policy_privacy_test.go
Normal file
@ -0,0 +1,479 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"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/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
policy *domain.PrivacyPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.PrivacyPolicy
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "policy already existing, already exists error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add policy,ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add policy empty links, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "",
|
||||
PrivacyLink: "",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TOSLink: "",
|
||||
PrivacyLink: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.AddPrivacyPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
|
||||
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_ChangePrivacyPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
policy *domain.PrivacyPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.PrivacyPolicy
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "policy not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLink",
|
||||
PrivacyLink: "PrivacyLink",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange"),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "TOSLinkChange",
|
||||
PrivacyLink: "PrivacyLinkChange",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TOSLink: "TOSLinkChange",
|
||||
PrivacyLink: "PrivacyLinkChange",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change to empty links, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newPrivacyPolicyChangedEvent(context.Background(), "org1", "", ""),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.PrivacyPolicy{
|
||||
TOSLink: "",
|
||||
PrivacyLink: "",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
TOSLink: "",
|
||||
PrivacyLink: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.ChangePrivacyPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
|
||||
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_RemovePrivacyPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "policy not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"TOSLink",
|
||||
"PrivacyLink",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewPrivacyPolicyRemovedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
},
|
||||
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.RemovePrivacyPolicy(tt.args.ctx, tt.args.orgID)
|
||||
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 newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, privacyLink string) *org.PrivacyPolicyChangedEvent {
|
||||
event, _ := org.NewPrivacyPolicyChangedEvent(ctx,
|
||||
&org.NewAggregate(orgID, orgID).Aggregate,
|
||||
[]policy.PrivacyPolicyChanges{
|
||||
policy.ChangeTOSLink(tosLink),
|
||||
policy.ChangePrivacyLink(privacyLink),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}
|
36
internal/command/policy_privacy_model.go
Normal file
36
internal/command/policy_privacy_model.go
Normal file
@ -0,0 +1,36 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
type PrivacyPolicyWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
TOSLink string
|
||||
PrivacyLink string
|
||||
State domain.PolicyState
|
||||
}
|
||||
|
||||
func (wm *PrivacyPolicyWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *policy.PrivacyPolicyAddedEvent:
|
||||
wm.TOSLink = e.TOSLink
|
||||
wm.PrivacyLink = e.PrivacyLink
|
||||
wm.State = domain.PolicyStateActive
|
||||
case *policy.PrivacyPolicyChangedEvent:
|
||||
if e.PrivacyLink != nil {
|
||||
wm.PrivacyLink = *e.PrivacyLink
|
||||
}
|
||||
if e.TOSLink != nil {
|
||||
wm.TOSLink = *e.TOSLink
|
||||
}
|
||||
case *policy.PrivacyPolicyRemovedEvent:
|
||||
wm.State = domain.PolicyStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
35
internal/command/setup_step17.go
Normal file
35
internal/command/setup_step17.go
Normal file
@ -0,0 +1,35 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
type Step17 struct {
|
||||
PrivacyPolicy domain.PrivacyPolicy
|
||||
}
|
||||
|
||||
func (s *Step17) Step() domain.Step {
|
||||
return domain.Step17
|
||||
}
|
||||
|
||||
func (s *Step17) execute(ctx context.Context, commandSide *Commands) error {
|
||||
return commandSide.SetupStep17(ctx, s)
|
||||
}
|
||||
|
||||
func (c *Commands) SetupStep17(ctx context.Context, step *Step17) error {
|
||||
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
|
||||
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
|
||||
addedPolicy := NewIAMPrivacyPolicyWriteModel()
|
||||
events, err := c.addDefaultPrivacyPolicy(ctx, iamAgg, addedPolicy, &step.PrivacyPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logging.Log("SETUP-N9sq2").Info("default privacy policy set up")
|
||||
return []eventstore.EventPusher{events}, nil
|
||||
}
|
||||
return c.setup(ctx, step, fn)
|
||||
}
|
@ -47,6 +47,7 @@ type AuthRequest struct {
|
||||
LoginPolicy *LoginPolicy
|
||||
AllowedExternalIDPs []*IDPProvider
|
||||
LabelPolicy *LabelPolicy
|
||||
PrivacyPolicy *PrivacyPolicy
|
||||
}
|
||||
|
||||
type ExternalUser struct {
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
FeatureLabelPolicyWatermark = FeatureLabelPolicy + ".watermark"
|
||||
FeatureCustomText = "custom_text"
|
||||
FeatureCustomDomain = "custom_domain"
|
||||
FeaturePrivacyPolicy = "privacy_policy"
|
||||
)
|
||||
|
||||
type Features struct {
|
||||
@ -43,6 +44,7 @@ type Features struct {
|
||||
LabelPolicyWatermark bool
|
||||
CustomDomain bool
|
||||
CustomText bool
|
||||
PrivacyPolicy bool
|
||||
}
|
||||
|
||||
type FeaturesState int32
|
||||
|
15
internal/domain/policy_privacy.go
Normal file
15
internal/domain/policy_privacy.go
Normal file
@ -0,0 +1,15 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
)
|
||||
|
||||
type PrivacyPolicy struct {
|
||||
models.ObjectRoot
|
||||
|
||||
State PolicyState
|
||||
Default bool
|
||||
|
||||
TOSLink string
|
||||
PrivacyLink string
|
||||
}
|
@ -19,6 +19,7 @@ const (
|
||||
Step14
|
||||
Step15
|
||||
Step16
|
||||
Step17
|
||||
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
|
||||
StepCount
|
||||
)
|
||||
|
@ -29,6 +29,7 @@ type FeaturesView struct {
|
||||
LabelPolicyWatermark bool
|
||||
CustomDomain bool
|
||||
CustomText bool
|
||||
PrivacyPolicy bool
|
||||
}
|
||||
|
||||
func (f *FeaturesView) FeatureList() []string {
|
||||
@ -66,6 +67,9 @@ func (f *FeaturesView) FeatureList() []string {
|
||||
if f.CustomText {
|
||||
list = append(list, domain.FeatureCustomText)
|
||||
}
|
||||
if f.PrivacyPolicy {
|
||||
list = append(list, domain.FeaturePrivacyPolicy)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ type FeaturesView struct {
|
||||
LabelPolicyWatermark bool `json:"labelPolicyWatermark" gorm:"column:label_policy_watermark"`
|
||||
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
|
||||
CustomText bool `json:"customText" gorm:"column:custom_text"`
|
||||
PrivacyPolicy bool `json:"privacyPolicy" gorm:"column:privacy_policy"`
|
||||
}
|
||||
|
||||
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
||||
@ -68,6 +69,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
|
||||
LabelPolicyWatermark: features.LabelPolicyWatermark,
|
||||
CustomDomain: features.CustomDomain,
|
||||
CustomText: features.CustomText,
|
||||
PrivacyPolicy: features.PrivacyPolicy,
|
||||
}
|
||||
}
|
||||
|
||||
|
48
internal/iam/model/privacy_policy_view.go
Normal file
48
internal/iam/model/privacy_policy_view.go
Normal file
@ -0,0 +1,48 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
type PrivacyPolicyView struct {
|
||||
AggregateID string
|
||||
TOSLink string
|
||||
PrivacyLink string
|
||||
Default bool
|
||||
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
type PrivacyPolicySearchRequest struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
SortingColumn PrivacyPolicySearchKey
|
||||
Asc bool
|
||||
Queries []*PrivacyPolicySearchQuery
|
||||
}
|
||||
|
||||
type PrivacyPolicySearchKey int32
|
||||
|
||||
const (
|
||||
PrivacyPolicySearchKeyUnspecified PrivacyPolicySearchKey = iota
|
||||
PrivacyPolicySearchKeyAggregateID
|
||||
)
|
||||
|
||||
type PrivacyPolicySearchQuery struct {
|
||||
Key PrivacyPolicySearchKey
|
||||
Method domain.SearchMethod
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type PrivacyPolicySearchResponse struct {
|
||||
Offset uint64
|
||||
Limit uint64
|
||||
TotalResult uint64
|
||||
Result []*PrivacyPolicyView
|
||||
Sequence uint64
|
||||
Timestamp time.Time
|
||||
}
|
@ -67,6 +67,9 @@ const (
|
||||
PasswordLockoutPolicyAdded models.EventType = "iam.policy.password.lockout.added"
|
||||
PasswordLockoutPolicyChanged models.EventType = "iam.policy.password.lockout.changed"
|
||||
|
||||
PrivacyPolicyAdded models.EventType = "iam.policy.privacy.added"
|
||||
PrivacyPolicyChanged models.EventType = "iam.policy.privacy.changed"
|
||||
|
||||
OrgIAMPolicyAdded models.EventType = "iam.policy.org.iam.added"
|
||||
OrgIAMPolicyChanged models.EventType = "iam.policy.org.iam.changed"
|
||||
)
|
||||
|
98
internal/iam/repository/view/model/privacy_policy.go
Normal file
98
internal/iam/repository/view/model/privacy_policy.go
Normal file
@ -0,0 +1,98 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
PrivacyKeyAggregateID = "aggregate_id"
|
||||
)
|
||||
|
||||
type PrivacyPolicyView struct {
|
||||
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
|
||||
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||
State int32 `json:"-" gorm:"column:state"`
|
||||
|
||||
TOSLink string `json:"tosLink" gorm:"column:tos_link"`
|
||||
PrivacyLink string `json:"privacyLink" gorm:"column:privacy_link"`
|
||||
Default bool `json:"-" gorm:"-"`
|
||||
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
}
|
||||
|
||||
func PrivacyViewFromModel(policy *model.PrivacyPolicyView) *PrivacyPolicyView {
|
||||
return &PrivacyPolicyView{
|
||||
AggregateID: policy.AggregateID,
|
||||
Sequence: policy.Sequence,
|
||||
CreationDate: policy.CreationDate,
|
||||
ChangeDate: policy.ChangeDate,
|
||||
TOSLink: policy.TOSLink,
|
||||
PrivacyLink: policy.PrivacyLink,
|
||||
Default: policy.Default,
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyViewToModel(policy *PrivacyPolicyView) *model.PrivacyPolicyView {
|
||||
return &model.PrivacyPolicyView{
|
||||
AggregateID: policy.AggregateID,
|
||||
Sequence: policy.Sequence,
|
||||
CreationDate: policy.CreationDate,
|
||||
ChangeDate: policy.ChangeDate,
|
||||
TOSLink: policy.TOSLink,
|
||||
PrivacyLink: policy.PrivacyLink,
|
||||
Default: policy.Default,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicyView) ToDomain() *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: p.AggregateID,
|
||||
CreationDate: p.CreationDate,
|
||||
ChangeDate: p.ChangeDate,
|
||||
Sequence: p.Sequence,
|
||||
},
|
||||
Default: p.Default,
|
||||
TOSLink: p.TOSLink,
|
||||
PrivacyLink: p.PrivacyLink,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *PrivacyPolicyView) AppendEvent(event *models.Event) (err error) {
|
||||
i.Sequence = event.Sequence
|
||||
i.ChangeDate = event.CreationDate
|
||||
switch event.Type {
|
||||
case es_model.PrivacyPolicyAdded, org_es_model.PrivacyPolicyAdded:
|
||||
i.setRootData(event)
|
||||
i.CreationDate = event.CreationDate
|
||||
err = i.SetData(event)
|
||||
case es_model.PrivacyPolicyChanged, org_es_model.PrivacyPolicyChanged:
|
||||
err = i.SetData(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *PrivacyPolicyView) setRootData(event *models.Event) {
|
||||
r.AggregateID = event.AggregateID
|
||||
}
|
||||
|
||||
func (r *PrivacyPolicyView) SetData(event *models.Event) error {
|
||||
if err := json.Unmarshal(event.Data, r); err != nil {
|
||||
logging.Log("EVEN-gHls0").WithError(err).Error("could not unmarshal event data")
|
||||
return caos_errs.ThrowInternal(err, "MODEL-Hs8uf", "Could not unmarshal data")
|
||||
}
|
||||
return nil
|
||||
}
|
59
internal/iam/repository/view/model/privacy_policy_query.go
Normal file
59
internal/iam/repository/view/model/privacy_policy_query.go
Normal file
@ -0,0 +1,59 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
type PrivacyPolicySearchRequest iam_model.PrivacyPolicySearchRequest
|
||||
type PrivacyPolicySearchQuery iam_model.PrivacyPolicySearchQuery
|
||||
type PrivacyPolicySearchKey iam_model.PrivacyPolicySearchKey
|
||||
|
||||
func (req PrivacyPolicySearchRequest) GetLimit() uint64 {
|
||||
return req.Limit
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchRequest) GetOffset() uint64 {
|
||||
return req.Offset
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchRequest) GetSortingColumn() repository.ColumnKey {
|
||||
if req.SortingColumn == iam_model.PrivacyPolicySearchKeyUnspecified {
|
||||
return nil
|
||||
}
|
||||
return PrivacyPolicySearchKey(req.SortingColumn)
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchRequest) GetAsc() bool {
|
||||
return req.Asc
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchRequest) GetQueries() []repository.SearchQuery {
|
||||
result := make([]repository.SearchQuery, len(req.Queries))
|
||||
for i, q := range req.Queries {
|
||||
result[i] = PrivacyPolicySearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchQuery) GetKey() repository.ColumnKey {
|
||||
return PrivacyPolicySearchKey(req.Key)
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchQuery) GetMethod() domain.SearchMethod {
|
||||
return req.Method
|
||||
}
|
||||
|
||||
func (req PrivacyPolicySearchQuery) GetValue() interface{} {
|
||||
return req.Value
|
||||
}
|
||||
|
||||
func (key PrivacyPolicySearchKey) ToColumnName() string {
|
||||
switch iam_model.PrivacyPolicySearchKey(key) {
|
||||
case iam_model.PrivacyPolicySearchKeyAggregateID:
|
||||
return PrivacyKeyAggregateID
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
32
internal/iam/repository/view/privacy_policy_view.go
Normal file
32
internal/iam/repository/view/privacy_policy_view.go
Normal file
@ -0,0 +1,32 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
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 GetPrivacyPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PrivacyPolicyView, error) {
|
||||
policy := new(model.PrivacyPolicyView)
|
||||
aggregateIDQuery := &model.PrivacyPolicySearchQuery{Key: iam_model.PrivacyPolicySearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
|
||||
query := repository.PrepareGetByQuery(table, aggregateIDQuery)
|
||||
err := query(db, policy)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-2N9fs", "Errors.IAM.PrivacyPolicy.NotExisting")
|
||||
}
|
||||
return policy, err
|
||||
}
|
||||
|
||||
func PutPrivacyPolicy(db *gorm.DB, table string, policy *model.PrivacyPolicyView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, policy)
|
||||
}
|
||||
|
||||
func DeletePrivacyPolicy(db *gorm.DB, table, aggregateID string) error {
|
||||
delete := repository.PrepareDeleteByKey(table, model.PrivacyPolicySearchKey(iam_model.PrivacyPolicySearchKeyAggregateID), aggregateID)
|
||||
|
||||
return delete(db)
|
||||
}
|
@ -526,6 +526,23 @@ func (repo *OrgRepository) GetDefaultPasswordLockoutPolicy(ctx context.Context)
|
||||
return iam_es_model.PasswordLockoutViewToModel(policy), nil
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) {
|
||||
policy, err := repo.View.PrivacyPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
|
||||
if errors.IsNotFound(err) {
|
||||
return repo.GetDefaultPrivacyPolicy(ctx)
|
||||
}
|
||||
return iam_es_model.PrivacyViewToModel(policy), nil
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) {
|
||||
policy, err := repo.View.PrivacyPolicyByAggregateID(repo.SystemDefaults.IamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy.Default = true
|
||||
return iam_es_model.PrivacyViewToModel(policy), nil
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) {
|
||||
template, err := repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID)
|
||||
if err != nil {
|
||||
|
@ -81,6 +81,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
|
||||
newFeatures(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
|
||||
newPrivacyPolicy(
|
||||
handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,106 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
|
||||
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_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
const (
|
||||
privacyPolicyTable = "management.privacy_policies"
|
||||
)
|
||||
|
||||
type PrivacyPolicy struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newPrivacyPolicy(handler handler) *PrivacyPolicy {
|
||||
h := &PrivacyPolicy{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) subscribe() {
|
||||
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range p.subscription.Events {
|
||||
query.ReduceEvent(p, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) ViewModel() string {
|
||||
return privacyPolicyTable
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) AggregateTypes() []es_models.AggregateType {
|
||||
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) CurrentSequence() (uint64, error) {
|
||||
sequence, err := p.view.GetLatestPrivacyPolicySequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := p.view.GetLatestPrivacyPolicySequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(p.AggregateTypes()...).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) Reduce(event *es_models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case model.OrgAggregate, iam_es_model.IAMAggregate:
|
||||
err = p.processPrivacyPolicy(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) processPrivacyPolicy(event *es_models.Event) (err error) {
|
||||
policy := new(iam_model.PrivacyPolicyView)
|
||||
switch event.Type {
|
||||
case iam_es_model.PrivacyPolicyAdded, model.PrivacyPolicyAdded:
|
||||
err = policy.AppendEvent(event)
|
||||
case iam_es_model.PrivacyPolicyChanged, model.PrivacyPolicyChanged:
|
||||
policy, err = p.view.PrivacyPolicyByAggregateID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = policy.AppendEvent(event)
|
||||
case model.PrivacyPolicyRemoved:
|
||||
return p.view.DeletePrivacyPolicy(event.AggregateID, event)
|
||||
default:
|
||||
return p.view.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.PutPrivacyPolicy(policy, event)
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) OnError(event *es_models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-4N8sw", "id", event.AggregateID).WithError(err).Warn("something went wrong in privacy policy handler")
|
||||
return spooler.HandleError(event, err, p.view.GetLatestPrivacyPolicyFailedEvent, p.view.ProcessedPrivacyPolicyFailedEvent, p.view.ProcessedPrivacyPolicySequence, p.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (p *PrivacyPolicy) OnSuccess() error {
|
||||
return spooler.HandleSuccess(p.view.UpdatePrivacyPolicySpoolerRunTimestamp)
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"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 (
|
||||
privacyPolicyTable = "management.privacy_policies"
|
||||
)
|
||||
|
||||
func (v *View) PrivacyPolicyByAggregateID(aggregateID string) (*model.PrivacyPolicyView, error) {
|
||||
return view.GetPrivacyPolicyByAggregateID(v.Db, privacyPolicyTable, aggregateID)
|
||||
}
|
||||
|
||||
func (v *View) PutPrivacyPolicy(policy *model.PrivacyPolicyView, event *models.Event) error {
|
||||
err := view.PutPrivacyPolicy(v.Db, privacyPolicyTable, policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeletePrivacyPolicy(aggregateID string, event *models.Event) error {
|
||||
err := view.DeletePrivacyPolicy(v.Db, privacyPolicyTable, aggregateID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedPrivacyPolicySequence(event)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestPrivacyPolicySequence() (*global_view.CurrentSequence, error) {
|
||||
return v.latestSequence(privacyPolicyTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedPrivacyPolicySequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(privacyPolicyTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdatePrivacyPolicySpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(privacyPolicyTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestPrivacyPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(privacyPolicyTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedPrivacyPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -41,6 +41,9 @@ type OrgRepository interface {
|
||||
GetPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error)
|
||||
GetDefaultPasswordLockoutPolicy(ctx context.Context) (*iam_model.PasswordLockoutPolicyView, error)
|
||||
|
||||
GetPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)
|
||||
GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error)
|
||||
|
||||
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
|
||||
GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
|
||||
|
||||
|
@ -92,4 +92,8 @@ const (
|
||||
PasswordLockoutPolicyAdded models.EventType = "org.policy.password.lockout.added"
|
||||
PasswordLockoutPolicyChanged models.EventType = "org.policy.password.lockout.changed"
|
||||
PasswordLockoutPolicyRemoved models.EventType = "org.policy.password.lockout.removed"
|
||||
|
||||
PrivacyPolicyAdded models.EventType = "org.policy.privacy.added"
|
||||
PrivacyPolicyChanged models.EventType = "org.policy.privacy.changed"
|
||||
PrivacyPolicyRemoved models.EventType = "org.policy.privacy.removed"
|
||||
)
|
||||
|
@ -36,6 +36,7 @@ type FeaturesSetEvent struct {
|
||||
LabelPolicyWatermark *bool `json:"labelPolicyWatermark,omitempty"`
|
||||
CustomDomain *bool `json:"customDomain,omitempty"`
|
||||
CustomText *bool `json:"customText,omitempty"`
|
||||
PrivacyPolicy *bool `json:"privacyPolicy,omitempty"`
|
||||
}
|
||||
|
||||
func (e *FeaturesSetEvent) Data() interface{} {
|
||||
@ -159,6 +160,13 @@ func ChangeCustomText(customText bool) func(event *FeaturesSetEvent) {
|
||||
e.CustomText = &customText
|
||||
}
|
||||
}
|
||||
|
||||
func ChangePrivacyPolicy(privacyPolicy bool) func(event *FeaturesSetEvent) {
|
||||
return func(e *FeaturesSetEvent) {
|
||||
e.PrivacyPolicy = &privacyPolicy
|
||||
}
|
||||
}
|
||||
|
||||
func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &FeaturesSetEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
|
@ -34,6 +34,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(PasswordComplexityPolicyChangedEventType, PasswordComplexityPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(PasswordLockoutPolicyAddedEventType, PasswordLockoutPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(PasswordLockoutPolicyChangedEventType, PasswordLockoutPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(PrivacyPolicyAddedEventType, PrivacyPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(PrivacyPolicyChangedEventType, PrivacyPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(MemberAddedEventType, MemberAddedEventMapper).
|
||||
RegisterFilterEventMapper(MemberChangedEventType, MemberChangedEventMapper).
|
||||
RegisterFilterEventMapper(MemberRemovedEventType, MemberRemovedEventMapper).
|
||||
|
75
internal/repository/iam/policy_privacy.go
Normal file
75
internal/repository/iam/policy_privacy.go
Normal file
@ -0,0 +1,75 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
PrivacyPolicyAddedEventType = iamEventTypePrefix + policy.PrivacyPolicyAddedEventType
|
||||
PrivacyPolicyChangedEventType = iamEventTypePrefix + policy.PrivacyPolicyChangedEventType
|
||||
)
|
||||
|
||||
type PrivacyPolicyAddedEvent struct {
|
||||
policy.PrivacyPolicyAddedEvent
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
tosLink,
|
||||
privacyLink string,
|
||||
) *PrivacyPolicyAddedEvent {
|
||||
return &PrivacyPolicyAddedEvent{
|
||||
PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PrivacyPolicyAddedEventType),
|
||||
tosLink,
|
||||
privacyLink),
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := policy.PrivacyPolicyAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PrivacyPolicyAddedEvent{PrivacyPolicyAddedEvent: *e.(*policy.PrivacyPolicyAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type PrivacyPolicyChangedEvent struct {
|
||||
policy.PrivacyPolicyChangedEvent
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []policy.PrivacyPolicyChanges,
|
||||
) (*PrivacyPolicyChangedEvent, error) {
|
||||
changedEvent, err := policy.NewPrivacyPolicyChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PrivacyPolicyChangedEventType),
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PrivacyPolicyChangedEvent{PrivacyPolicyChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func PrivacyPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := policy.PrivacyPolicyChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PrivacyPolicyChangedEvent{PrivacyPolicyChangedEvent: *e.(*policy.PrivacyPolicyChangedEvent)}, nil
|
||||
}
|
@ -56,6 +56,9 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(PasswordLockoutPolicyAddedEventType, PasswordLockoutPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(PasswordLockoutPolicyChangedEventType, PasswordLockoutPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(PasswordLockoutPolicyRemovedEventType, PasswordLockoutPolicyRemovedEventMapper).
|
||||
RegisterFilterEventMapper(PrivacyPolicyAddedEventType, PrivacyPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(PrivacyPolicyChangedEventType, PrivacyPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(PrivacyPolicyRemovedEventType, PrivacyPolicyRemovedEventMapper).
|
||||
RegisterFilterEventMapper(MailTemplateAddedEventType, MailTemplateAddedEventMapper).
|
||||
RegisterFilterEventMapper(MailTemplateChangedEventType, MailTemplateChangedEventMapper).
|
||||
RegisterFilterEventMapper(MailTemplateRemovedEventType, MailTemplateRemovedEventMapper).
|
||||
|
103
internal/repository/org/policy_privacy.go
Normal file
103
internal/repository/org/policy_privacy.go
Normal file
@ -0,0 +1,103 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
var (
|
||||
PrivacyPolicyAddedEventType = orgEventTypePrefix + policy.PrivacyPolicyAddedEventType
|
||||
PrivacyPolicyChangedEventType = orgEventTypePrefix + policy.PrivacyPolicyChangedEventType
|
||||
PrivacyPolicyRemovedEventType = orgEventTypePrefix + policy.PrivacyPolicyRemovedEventType
|
||||
)
|
||||
|
||||
type PrivacyPolicyAddedEvent struct {
|
||||
policy.PrivacyPolicyAddedEvent
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
tosLink,
|
||||
privacyLink string,
|
||||
) *PrivacyPolicyAddedEvent {
|
||||
return &PrivacyPolicyAddedEvent{
|
||||
PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PrivacyPolicyAddedEventType),
|
||||
tosLink,
|
||||
privacyLink),
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := policy.PrivacyPolicyAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PrivacyPolicyAddedEvent{PrivacyPolicyAddedEvent: *e.(*policy.PrivacyPolicyAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type PrivacyPolicyChangedEvent struct {
|
||||
policy.PrivacyPolicyChangedEvent
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []policy.PrivacyPolicyChanges,
|
||||
) (*PrivacyPolicyChangedEvent, error) {
|
||||
changedEvent, err := policy.NewPrivacyPolicyChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PrivacyPolicyChangedEventType),
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PrivacyPolicyChangedEvent{PrivacyPolicyChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func PrivacyPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := policy.PrivacyPolicyChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PrivacyPolicyChangedEvent{PrivacyPolicyChangedEvent: *e.(*policy.PrivacyPolicyChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type PrivacyPolicyRemovedEvent struct {
|
||||
policy.PrivacyPolicyRemovedEvent
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyRemovedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *PrivacyPolicyRemovedEvent {
|
||||
return &PrivacyPolicyRemovedEvent{
|
||||
PrivacyPolicyRemovedEvent: *policy.NewPrivacyPolicyRemovedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PrivacyPolicyRemovedEventType),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyPolicyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := policy.PrivacyPolicyRemovedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PrivacyPolicyRemovedEvent{PrivacyPolicyRemovedEvent: *e.(*policy.PrivacyPolicyRemovedEvent)}, nil
|
||||
}
|
136
internal/repository/policy/policy_privacy.go
Normal file
136
internal/repository/policy/policy_privacy.go
Normal file
@ -0,0 +1,136 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
PrivacyPolicyAddedEventType = "policy.privacy.added"
|
||||
PrivacyPolicyChangedEventType = "policy.privacy.changed"
|
||||
PrivacyPolicyRemovedEventType = "policy.privacy.removed"
|
||||
)
|
||||
|
||||
type PrivacyPolicyAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
TOSLink string `json:"tosLink,omitempty"`
|
||||
PrivacyLink string `json:"privacyLink,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PrivacyPolicyAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PrivacyPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyAddedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
tosLink,
|
||||
privacyLink string,
|
||||
) *PrivacyPolicyAddedEvent {
|
||||
return &PrivacyPolicyAddedEvent{
|
||||
BaseEvent: *base,
|
||||
TOSLink: tosLink,
|
||||
PrivacyLink: privacyLink,
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &PrivacyPolicyAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "POLIC-2k0fs", "unable to unmarshal policy")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type PrivacyPolicyChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
TOSLink *string `json:"tosLink,omitempty"`
|
||||
PrivacyLink *string `json:"privacyLink,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PrivacyPolicyChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PrivacyPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyChangedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
changes []PrivacyPolicyChanges,
|
||||
) (*PrivacyPolicyChangedEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "POLICY-PPo0s", "Errors.NoChangesFound")
|
||||
}
|
||||
changeEvent := &PrivacyPolicyChangedEvent{
|
||||
BaseEvent: *base,
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(changeEvent)
|
||||
}
|
||||
return changeEvent, nil
|
||||
}
|
||||
|
||||
type PrivacyPolicyChanges func(*PrivacyPolicyChangedEvent)
|
||||
|
||||
func ChangeTOSLink(tosLink string) func(*PrivacyPolicyChangedEvent) {
|
||||
return func(e *PrivacyPolicyChangedEvent) {
|
||||
e.TOSLink = &tosLink
|
||||
}
|
||||
}
|
||||
|
||||
func ChangePrivacyLink(privacyLink string) func(*PrivacyPolicyChangedEvent) {
|
||||
return func(e *PrivacyPolicyChangedEvent) {
|
||||
e.PrivacyLink = &privacyLink
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &PrivacyPolicyChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "POLIC-22nf9", "unable to unmarshal policy")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type PrivacyPolicyRemovedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *PrivacyPolicyRemovedEvent) Data() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PrivacyPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPrivacyPolicyRemovedEvent(base *eventstore.BaseEvent) *PrivacyPolicyRemovedEvent {
|
||||
return &PrivacyPolicyRemovedEvent{
|
||||
BaseEvent: *base,
|
||||
}
|
||||
}
|
||||
|
||||
func PrivacyPolicyRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
return &PrivacyPolicyRemovedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
@ -22,6 +22,7 @@ type IAMSetUp struct {
|
||||
Step14 *command.Step14
|
||||
Step15 *command.Step15
|
||||
Step16 *command.Step16
|
||||
Step17 *command.Step17
|
||||
}
|
||||
|
||||
func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
|
||||
@ -44,6 +45,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
|
||||
setup.Step14,
|
||||
setup.Step15,
|
||||
setup.Step16,
|
||||
setup.Step17,
|
||||
} {
|
||||
if step.Step() <= currentDone {
|
||||
continue
|
||||
|
15
internal/ui/login/handler/privacy_policy_handler.go
Normal file
15
internal/ui/login/handler/privacy_policy_handler.go
Normal file
@ -0,0 +1,15 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
||||
func (l *Login) getDefaultPrivacyPolicy(r *http.Request) (*iam_model.PrivacyPolicyView, error) {
|
||||
policy, err := l.authRepo.GetDefaultPrivacyPolicy(r.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy, nil
|
||||
}
|
@ -321,8 +321,19 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, title
|
||||
baseData.LoginPolicy = authReq.LoginPolicy
|
||||
baseData.LabelPolicy = authReq.LabelPolicy
|
||||
baseData.IDPProviders = authReq.AllowedExternalIDPs
|
||||
if authReq.PrivacyPolicy != nil {
|
||||
baseData.TOSLink = authReq.PrivacyPolicy.TOSLink
|
||||
baseData.PrivacyLink = authReq.PrivacyPolicy.PrivacyLink
|
||||
}
|
||||
} else {
|
||||
//TODO: How to handle LabelPolicy if no auth req (eg Register)
|
||||
privacyPolicy, err := l.getDefaultPrivacyPolicy(r)
|
||||
if err != nil {
|
||||
return baseData
|
||||
}
|
||||
if privacyPolicy != nil {
|
||||
baseData.TOSLink = privacyPolicy.TOSLink
|
||||
baseData.PrivacyLink = privacyPolicy.PrivacyLink
|
||||
}
|
||||
}
|
||||
return baseData
|
||||
}
|
||||
@ -405,7 +416,6 @@ func (l *Login) isDisplayLoginNameSuffix(authReq *domain.AuthRequest) bool {
|
||||
}
|
||||
return authReq.LabelPolicy != nil && !authReq.LabelPolicy.HideLoginNameSuffix
|
||||
}
|
||||
|
||||
func getRequestID(authReq *domain.AuthRequest, r *http.Request) string {
|
||||
if authReq != nil {
|
||||
return authReq.ID
|
||||
@ -437,6 +447,8 @@ type baseData struct {
|
||||
OrgName string
|
||||
PrimaryDomain string
|
||||
DisplayLoginNameSuffix bool
|
||||
TOSLink string
|
||||
PrivacyLink string
|
||||
AuthReqID string
|
||||
CSRF template.HTML
|
||||
Nonce string
|
||||
|
@ -160,9 +160,7 @@ Registration:
|
||||
TosConfirm: Ich akzeptiere die
|
||||
TosLinkText: AGBs
|
||||
TosConfirmAnd: und die
|
||||
TosLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
PrivacyLinkText: Datenschutzerklärung
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
||||
ExternalLogin: oder registriere dich mit einem externen Benutzer
|
||||
|
||||
RegistrationOrg:
|
||||
@ -187,9 +185,7 @@ RegistrationOrg:
|
||||
TosConfirm: Ich akzeptiere die
|
||||
TosLinkText: AGBs
|
||||
TosConfirmAnd: und die
|
||||
TosLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
PrivacyLinkText: Datenschutzerklärung
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
||||
|
||||
LinkingUsersDone:
|
||||
Title: Benutzerlinking
|
||||
@ -228,9 +224,7 @@ Actions:
|
||||
Footer:
|
||||
PoweredBy: Powered By
|
||||
Tos: AGB
|
||||
TosLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
Privacy: Datenschutzerklärung
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
||||
Help: Hilfe
|
||||
|
||||
Errors:
|
||||
|
@ -160,9 +160,7 @@ Registration:
|
||||
TosConfirm: I accept the
|
||||
TosLinkText: TOS
|
||||
TosConfirmAnd: and the
|
||||
TosLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
PrivacyLinkText: privacy policy
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
||||
ExternalLogin: or register with an external user
|
||||
|
||||
RegistrationOrg:
|
||||
@ -187,9 +185,7 @@ RegistrationOrg:
|
||||
TosConfirm: I accept the
|
||||
TosLinkText: TOS
|
||||
TosConfirmAnd: and the
|
||||
TosLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
PrivacyLinkText: privacy policy
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
||||
|
||||
LoginSuccess:
|
||||
Title: Login successful
|
||||
@ -228,9 +224,7 @@ Actions:
|
||||
Footer:
|
||||
PoweredBy: Powered By
|
||||
Tos: TOS
|
||||
TosLink: https://docs.zitadel.ch/docs/legal/terms-of-service
|
||||
Privacy: Privacy policy
|
||||
PrivacyLink: https://docs.zitadel.ch/docs/legal/privacy-policy
|
||||
Help: Help
|
||||
|
||||
Errors:
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
{{define "footer"}}
|
||||
<footer>
|
||||
{{ if hasWatermark .LabelPolicy }}
|
||||
@ -7,8 +8,12 @@
|
||||
</span>
|
||||
{{end}}
|
||||
<span class="fill-space"></span>
|
||||
<a href="{{t "Footer.TosLink"}}" rel="noopener noreferrer" target="_blank" alt="TOS">{{t "Footer.Tos"}}</a>
|
||||
<a href="{{t "Footer.PrivacyLink"}}" rel="noopener noreferrer" target="_blank" alt="Privacy Policy">{{t "Footer.Privacy"}}</a>
|
||||
{{ if .TOSLink }}
|
||||
<a href="{{.TOSLink}}" rel="noopener noreferrer" target="_blank" alt="TOS">{{t "Footer.Tos"}}</a>
|
||||
{{ end }}
|
||||
{{ if .PrivacyLink }}
|
||||
<a href="{{.PrivacyLink}}" rel="noopener noreferrer" target="_blank" alt="Privacy Policy">{{t "Footer.Privacy"}}</a>
|
||||
{{end}}
|
||||
<a href="https://docs.zitadel.ch/docs/manuals/user-login" target="_black" alt="Help">{{t "Footer.Help"}}</a>
|
||||
</footer>
|
||||
{{end}}
|
||||
|
@ -91,23 +91,31 @@
|
||||
{{ .PasswordPolicyDescription }}
|
||||
</div>
|
||||
|
||||
{{ if or .TOSLink .PrivacyLink }}
|
||||
<div class="lgn-field">
|
||||
<label class="lgn-label">{{t "Registration.TosAndPrivacy"}}</label>
|
||||
<div class="lgn-checkbox">
|
||||
<input type="checkbox" id="register-term-confirmation"
|
||||
name="register-term-confirmation" required>
|
||||
<label for="register-term-confirmation">
|
||||
{{t "Registration.TosConfirm"}}
|
||||
<a class="tos-link" target="_blank" href="{{t "Registration.TosLink"}}" rel="noopener noreferrer">
|
||||
{{t "Registration.TosLinkText"}}
|
||||
</a>
|
||||
{{t "Registration.TosConfirmAnd"}}
|
||||
<a class="tos-link" target="_blank" href="{{t "Registration.PrivacyLink"}}" rel="noopener noreferrer">
|
||||
{{t "Registration.PrivacyLinkText"}}
|
||||
</a>
|
||||
{{t "Registration.TosConfirm"}}
|
||||
{{ if .TOSLink }}
|
||||
<a class="tos-link" target="_blank" href="{{ .TOSLink }}" rel="noopener noreferrer">
|
||||
{{t "Registration.TosLinkText"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{ if and .TOSLink .PrivacyLink }}
|
||||
{{t "Registration.TosConfirmAnd"}}
|
||||
{{ end }}
|
||||
{{ if .PrivacyLink }}
|
||||
<a class="tos-link" target="_blank" href="{{ .PrivacyLink}}" rel="noopener noreferrer">
|
||||
{{t "Registration.PrivacyLinkText"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{template "error-message" .}}
|
||||
|
@ -67,21 +67,32 @@
|
||||
{{ .PasswordPolicyDescription }}
|
||||
</div>
|
||||
|
||||
{{ if or .TOSLink .PrivacyLink }}
|
||||
<div class="lgn-field">
|
||||
<label class="lgn-label" for="register-term-confirmation">{{t "RegistrationOrg.TosAndPrivacy"}}</label>
|
||||
<div class="lgn-checkbox">
|
||||
<input class="lgn-input" type="checkbox" id="register-term-confirmation"
|
||||
name="register-term-confirmation" required>
|
||||
<label class="lgn-label" for="register-term-confirmation">
|
||||
{{t "RegistrationOrg.TosConfirm"}}
|
||||
<a class="tos-link" target="_blank" href="{{t "RegistrationOrg.TosLink"}}" rel="noopener noreferrer">{{t "RegistrationOrg.TosLinkText"}}</a>
|
||||
{{t "Registration.TosConfirmAnd"}}
|
||||
<a class="tos-link" target="_blank" href="{{t "Registration.PrivacyLink"}}" rel="noopener noreferrer">
|
||||
{{t "Registration.PrivacyLinkText"}}
|
||||
</a>
|
||||
|
||||
{{t "RegistrationOrg.TosConfirm"}}
|
||||
{{ if .TOSLink }}
|
||||
<a class="tos-link" target="_blank" href="{{.TOSLink}}" rel="noopener noreferrer">{{t "RegistrationOrg.TosLinkText"}}</a>
|
||||
{{end}}
|
||||
|
||||
{{ if and .TOSLink .PrivacyLink }}
|
||||
{{t "Registration.TosConfirmAnd"}}
|
||||
{{end}}
|
||||
|
||||
{{ if .PrivacyLink }}
|
||||
<a class="tos-link" target="_blank" href="{{.PrivacyLink}}" rel="noopener noreferrer">
|
||||
{{t "Registration.PrivacyLinkText"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{template "error-message" .}}
|
||||
|
46
migrations/cockroach/V1.50__privacy_policy.sql
Normal file
46
migrations/cockroach/V1.50__privacy_policy.sql
Normal file
@ -0,0 +1,46 @@
|
||||
ALTER TABLE adminapi.features ADD COLUMN privacy_policy BOOLEAN;
|
||||
ALTER TABLE auth.features ADD COLUMN privacy_policy BOOLEAN;
|
||||
ALTER TABLE authz.features ADD COLUMN privacy_policy BOOLEAN;
|
||||
ALTER TABLE management.features ADD COLUMN privacy_policy BOOLEAN;
|
||||
|
||||
CREATE TABLE auth.privacy_policies (
|
||||
aggregate_id TEXT,
|
||||
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
state SMALLINT,
|
||||
sequence BIGINT,
|
||||
|
||||
tos_link STRING,
|
||||
privacy_link STRING,
|
||||
|
||||
PRIMARY KEY (aggregate_id)
|
||||
);
|
||||
|
||||
CREATE TABLE adminapi.privacy_policies (
|
||||
aggregate_id TEXT,
|
||||
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
state SMALLINT,
|
||||
sequence BIGINT,
|
||||
|
||||
tos_link STRING,
|
||||
privacy_link STRING,
|
||||
|
||||
PRIMARY KEY (aggregate_id)
|
||||
);
|
||||
|
||||
CREATE TABLE management.privacy_policies (
|
||||
aggregate_id TEXT,
|
||||
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
state SMALLINT,
|
||||
sequence BIGINT,
|
||||
|
||||
tos_link STRING,
|
||||
privacy_link STRING,
|
||||
|
||||
PRIMARY KEY (aggregate_id)
|
||||
);
|
@ -1450,6 +1450,65 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the privacy policy defined by the administrators of ZITADEL
|
||||
rpc GetPrivacyPolicy(GetPrivacyPolicyRequest) returns (GetPrivacyPolicyResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/policies/privacy";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "policy";
|
||||
tags: "privacy policy";
|
||||
tags: "privacy";
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "default privacy policy";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
//Updates the default privacy policy of ZITADEL
|
||||
// it impacts all organisations without a customised policy
|
||||
rpc UpdatePrivacyPolicy(UpdatePrivacyPolicyRequest) returns (UpdatePrivacyPolicyResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/policies/privacy";
|
||||
body: "*";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.write";
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "policy";
|
||||
tags: "privacy policy";
|
||||
tags: "privacy";
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "default privacy policy updated";
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
key: "400";
|
||||
value: {
|
||||
description: "invalid argument";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/rpcStatus";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for initial message
|
||||
rpc GetDefaultInitMessageText(GetDefaultInitMessageTextRequest) returns (GetDefaultInitMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -2397,6 +2456,7 @@ message SetDefaultFeaturesRequest {
|
||||
bool label_policy_private_label = 15;
|
||||
bool label_policy_watermark = 16;
|
||||
bool custom_text = 17;
|
||||
bool privacy_policy = 18;
|
||||
}
|
||||
|
||||
message SetDefaultFeaturesResponse {
|
||||
@ -2431,6 +2491,7 @@ message SetOrgFeaturesRequest {
|
||||
bool label_policy_private_label = 16;
|
||||
bool label_policy_watermark = 17;
|
||||
bool custom_text = 18;
|
||||
bool privacy_policy = 19;
|
||||
}
|
||||
|
||||
message SetOrgFeaturesResponse {
|
||||
@ -2891,6 +2952,22 @@ message UpdatePasswordLockoutPolicyResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message GetPrivacyPolicyRequest {}
|
||||
|
||||
message GetPrivacyPolicyResponse {
|
||||
zitadel.policy.v1.PrivacyPolicy policy = 1;
|
||||
}
|
||||
|
||||
message UpdatePrivacyPolicyRequest {
|
||||
string tos_link = 1;
|
||||
string privacy_link = 2;
|
||||
}
|
||||
|
||||
message UpdatePrivacyPolicyResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message GetDefaultInitMessageTextRequest {
|
||||
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ message Features {
|
||||
bool label_policy_private_label = 14;
|
||||
bool label_policy_watermark = 15;
|
||||
bool custom_text = 16;
|
||||
bool privacy_policy = 17;
|
||||
}
|
||||
|
||||
message FeatureTier {
|
||||
|
@ -1927,6 +1927,70 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the privacy policy of the organisation
|
||||
// With this policy privacy relevant things can be configured (e.g. tos link)
|
||||
rpc GetPrivacyPolicy(GetPrivacyPolicyRequest) returns (GetPrivacyPolicyResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/policies/privacy"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "policy.read"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the default privacy policy of the IAM
|
||||
// With this policy the privacy relevant things can be configured (e.g tos link)
|
||||
rpc GetDefaultPrivacyPolicy(GetDefaultPrivacyPolicyRequest) returns (GetDefaultPrivacyPolicyResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/policies/default/privacy"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "policy.read"
|
||||
};
|
||||
}
|
||||
|
||||
// Add a custom privacy policy for the organisation
|
||||
// With this policy privacy relevant things can be configured (e.g. tos link)
|
||||
rpc AddCustomPrivacyPolicy(AddCustomPrivacyPolicyRequest) returns (AddCustomPrivacyPolicyResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/policies/privacy"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "policy.write"
|
||||
feature: "privacy_policy"
|
||||
};
|
||||
}
|
||||
|
||||
// Update the privacy complexity policy for the organisation
|
||||
// With this policy privacy relevant things can be configured (e.g. tos link)
|
||||
rpc UpdateCustomPrivacyPolicy(UpdateCustomPrivacyPolicyRequest) returns (UpdateCustomPrivacyPolicyResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/policies/privacy"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "policy.write"
|
||||
feature: "privacy_policy"
|
||||
};
|
||||
}
|
||||
|
||||
// Removes the privacy policy of the organisation
|
||||
// The default policy of the IAM will trigger after
|
||||
rpc ResetPrivacyPolicyToDefault(ResetPrivacyPolicyToDefaultRequest) returns (ResetPrivacyPolicyToDefaultResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/policies/privacy"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "policy.delete"
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the active label policy of the organisation
|
||||
// With this policy the private labeling can be configured (colors, etc.)
|
||||
rpc GetLabelPolicy(GetLabelPolicyRequest) returns (GetLabelPolicyResponse) {
|
||||
@ -3973,6 +4037,45 @@ message ResetPasswordLockoutPolicyToDefaultResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message GetPrivacyPolicyRequest {}
|
||||
|
||||
message GetPrivacyPolicyResponse {
|
||||
zitadel.policy.v1.PrivacyPolicy policy = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message GetDefaultPrivacyPolicyRequest {}
|
||||
|
||||
message GetDefaultPrivacyPolicyResponse {
|
||||
zitadel.policy.v1.PrivacyPolicy policy = 1;
|
||||
}
|
||||
|
||||
message AddCustomPrivacyPolicyRequest {
|
||||
string tos_link = 1;
|
||||
string privacy_link = 2;
|
||||
}
|
||||
|
||||
message AddCustomPrivacyPolicyResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message UpdateCustomPrivacyPolicyRequest {
|
||||
string tos_link = 1;
|
||||
string privacy_link = 2;
|
||||
}
|
||||
|
||||
message UpdateCustomPrivacyPolicyResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message ResetPrivacyPolicyToDefaultRequest {}
|
||||
|
||||
message ResetPrivacyPolicyToDefaultResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message GetLabelPolicyRequest {}
|
||||
|
||||
|
@ -220,4 +220,11 @@ message PasswordLockoutPolicy {
|
||||
description: "defines if the organisation's admin changed the policy"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message PrivacyPolicy {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
string tos_link = 2;
|
||||
string privacy_link = 3;
|
||||
bool is_default = 4;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user