feat: restrict languages (#6931)

* feat: return 404 or 409 if org reg disallowed

* fix: system limit permissions

* feat: add iam limits api

* feat: disallow public org registrations on default instance

* add integration test

* test: integration

* fix test

* docs: describe public org registrations

* avoid updating docs deps

* fix system limits integration test

* silence integration tests

* fix linting

* ignore strange linter complaints

* review

* improve reset properties naming

* redefine the api

* use restrictions aggregate

* test query

* simplify and test projection

* test commands

* fix unit tests

* move integration test

* support restrictions on default instance

* also test GetRestrictions

* self review

* lint

* abstract away resource owner

* fix tests

* configure supported languages

* fix allowed languages

* fix tests

* default lang must not be restricted

* preferred language must be allowed

* change preferred languages

* check languages everywhere

* lint

* test command side

* lint

* add integration test

* add integration test

* restrict supported ui locales

* lint

* lint

* cleanup

* lint

* allow undefined preferred language

* fix integration tests

* update main

* fix env var

* ignore linter

* ignore linter

* improve integration test config

* reduce cognitive complexity

* compile

* check for duplicates

* remove useless restriction checks

* review

* revert restriction renaming

* fix language restrictions

* lint

* generate

* allow custom texts for supported langs for now

* fix tests

* cleanup

* cleanup

* cleanup

* lint

* unsupported preferred lang is allowed

* fix integration test

* finish reverting to old property name

* finish reverting to old property name

* load languages

* refactor(i18n): centralize translators and fs

* lint

* amplify no validations on preferred languages

* fix integration test

* lint

* fix resetting allowed languages

* test unchanged restrictions
This commit is contained in:
Elio Bischof
2023-12-05 12:12:01 +01:00
committed by GitHub
parent 236930f109
commit dd33538c0a
123 changed files with 4133 additions and 2058 deletions

View File

@@ -85,6 +85,21 @@ func (mr *MockQueriesMockRecorder) GetDefaultLanguage(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLanguage", reflect.TypeOf((*MockQueries)(nil).GetDefaultLanguage), arg0)
}
// GetInstanceRestrictions mocks base method.
func (m *MockQueries) GetInstanceRestrictions(arg0 context.Context) (query.Restrictions, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInstanceRestrictions", arg0)
ret0, _ := ret[0].(query.Restrictions)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInstanceRestrictions indicates an expected call of GetInstanceRestrictions.
func (mr *MockQueriesMockRecorder) GetInstanceRestrictions(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceRestrictions", reflect.TypeOf((*MockQueries)(nil).GetInstanceRestrictions), arg0)
}
// GetNotifyUserByID mocks base method.
func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 string, arg3 ...query.SearchQuery) (*query.NotifyUser, error) {
m.ctrl.T.Helper()

View File

@@ -2,8 +2,6 @@ package handlers
import (
"context"
"net/http"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/crypto"
@@ -25,6 +23,7 @@ type Queries interface {
SMSProviderConfig(ctx context.Context, queries ...query.SearchQuery) (*query.SMSConfig, error)
SMTPConfigByAggregateID(ctx context.Context, aggregateID string) (*query.SMTPConfig, error)
GetDefaultLanguage(ctx context.Context) language.Tag
GetInstanceRestrictions(ctx context.Context) (restrictions query.Restrictions, err error)
}
type NotificationQueries struct {
@@ -37,7 +36,6 @@ type NotificationQueries struct {
UserDataCrypto crypto.EncryptionAlgorithm
SMTPPasswordCrypto crypto.EncryptionAlgorithm
SMSTokenCrypto crypto.EncryptionAlgorithm
statikDir http.FileSystem
}
func NewNotificationQueries(
@@ -50,7 +48,6 @@ func NewNotificationQueries(
userDataCrypto crypto.EncryptionAlgorithm,
smtpPasswordCrypto crypto.EncryptionAlgorithm,
smsTokenCrypto crypto.EncryptionAlgorithm,
statikDir http.FileSystem,
) *NotificationQueries {
return &NotificationQueries{
Queries: baseQueries,
@@ -62,6 +59,5 @@ func NewNotificationQueries(
UserDataCrypto: userDataCrypto,
SMTPPasswordCrypto: smtpPasswordCrypto,
SMSTokenCrypto: smsTokenCrypto,
statikDir: statikDir,
}
}

View File

@@ -10,7 +10,11 @@ import (
)
func (n *NotificationQueries) GetTranslatorWithOrgTexts(ctx context.Context, orgID, textType string) (*i18n.Translator, error) {
translator, err := i18n.NewTranslator(n.statikDir, n.GetDefaultLanguage(ctx), "")
restrictions, err := n.Queries.GetInstanceRestrictions(ctx)
if err != nil {
return nil, err
}
translator, err := i18n.NewNotificationTranslator(n.GetDefaultLanguage(ctx), restrictions.AllowedLanguages)
if err != nil {
return nil, err
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"fmt"
"net/http"
"testing"
"time"
@@ -12,7 +11,6 @@ import (
"github.com/zitadel/zitadel/internal/notification/messages"
statik_fs "github.com/rakyll/statik/fs"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"golang.org/x/text/language"
@@ -202,15 +200,13 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
},
}}
// TODO: Why don't we have an url template on user.HumanInitialCodeAddedEvent?
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
stmt, err := newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceInitCodeAdded(a.event)
stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceInitCodeAdded(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -423,15 +419,13 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
stmt, err := newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceEmailCodeAdded(a.event)
stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceEmailCodeAdded(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -644,15 +638,13 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
stmt, err := newUserNotifier(t, ctrl, queries, fs, f, a, w).reducePasswordCodeAdded(a.event)
stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reducePasswordCodeAdded(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -737,15 +729,13 @@ func Test_userNotifier_reduceDomainClaimed(t *testing.T) {
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
stmt, err := newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceDomainClaimed(a.event)
stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceDomainClaimed(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -963,15 +953,13 @@ func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) {
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
stmt, err := newUserNotifier(t, ctrl, queries, fs, f, a, w).reducePasswordlessCodeRequested(a.event)
stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reducePasswordlessCodeRequested(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -1062,15 +1050,13 @@ func Test_userNotifier_reducePasswordChanged(t *testing.T) {
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
stmt, err := newUserNotifier(t, ctrl, queries, fs, f, a, w).reducePasswordChanged(a.event)
stmt, err := newUserNotifier(t, ctrl, queries, f, a, w).reducePasswordChanged(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -1287,15 +1273,13 @@ func Test_userNotifier_reduceOTPEmailChallenged(t *testing.T) {
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
_, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceSessionOTPEmailChallenged(a.event)
_, err := newUserNotifier(t, ctrl, queries, f, a, w).reduceSessionOTPEmailChallenged(a.event)
if w.err != nil {
w.err(t, err)
} else {
@@ -1320,7 +1304,7 @@ type want struct {
err assert.ErrorAssertionFunc
}
func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQueries, fs http.FileSystem, f fields, a args, w want) *userNotifier {
func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQueries, f fields, a args, w want) *userNotifier {
queries.EXPECT().NotificationProviderByIDAndType(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(&query.DebugNotificationProvider{}, nil)
smtpAlg, _ := cryptoValue(t, ctrl, "smtppw")
channel := channel_mock.NewMockNotificationChannel(ctrl)
@@ -1340,7 +1324,6 @@ func newUserNotifier(t *testing.T, ctrl *gomock.Controller, queries *mock.MockQu
f.userDataCrypto,
smtpAlg,
f.SMSTokenCrypto,
fs,
),
otpEmailTmpl: defaultOTPEmailTemplate,
channels: &channels{Chain: *senders.ChainChannels(channel)},
@@ -1366,6 +1349,9 @@ func (c *channels) Webhook(context.Context, webhook.Config) (*senders.Chain, err
}
func expectTemplateQueries(queries *mock.MockQueries, template string) {
queries.EXPECT().GetInstanceRestrictions(gomock.Any()).Return(query.Restrictions{
AllowedLanguages: []language.Tag{language.English},
}, nil)
queries.EXPECT().ActiveLabelPolicyByOrg(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.LabelPolicy{
ID: policyID,
Light: query.Theme{