zitadel/internal/api/grpc/admin/restrictions_integration_allowed_languages_test.go
Elio Bischof 8c85318fbd
fix: restrict languages in console (#6964)
* 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

* fix(console): switch back to saved language

* feat(API): get allowed languages

* fix(console): only make allowed languages selectable

* warn when editing not allowed languages

* 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

* allow unsupported preferred languages

* lint

* load languages for tests

* cleanup

* lint

* cleanup

* get allowed only on admin

* cleanup

* reduce flakiness on very limited postgres

* simplify langSvc

* refactor according to suggestions in pr

* lint

* set first allowed language as default

* selectionchange for language in msg texts

* initialize login texts

* init message texts

* lint

---------

Co-authored-by: peintnermax <max@caos.ch>
2023-12-07 08:43:23 +00:00

260 lines
11 KiB
Go

//go:build integration
package admin_test
import (
"context"
"encoding/json"
"io"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/text/language"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/zitadel/zitadel/pkg/grpc/admin"
"github.com/zitadel/zitadel/pkg/grpc/management"
"github.com/zitadel/zitadel/pkg/grpc/text"
"github.com/zitadel/zitadel/pkg/grpc/user"
)
func TestServer_Restrictions_AllowedLanguages(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
defer cancel()
var (
defaultAndAllowedLanguage = language.German
supportedLanguagesStr = []string{language.German.String(), language.English.String(), language.Japanese.String()}
disallowedLanguage = language.Spanish
unsupportedLanguage = language.Afrikaans
)
domain, _, iamOwnerCtx := Tester.UseIsolatedInstance(t, ctx, SystemCTX)
t.Run("assumed defaults are correct", func(tt *testing.T) {
tt.Run("languages are not restricted by default", func(ttt *testing.T) {
restrictions, err := Tester.Client.Admin.GetRestrictions(iamOwnerCtx, &admin.GetRestrictionsRequest{})
require.NoError(ttt, err)
require.Len(ttt, restrictions.AllowedLanguages, 0)
})
tt.Run("default language is English by default", func(ttt *testing.T) {
defaultLang, err := Tester.Client.Admin.GetDefaultLanguage(iamOwnerCtx, &admin.GetDefaultLanguageRequest{})
require.NoError(ttt, err)
require.Equal(ttt, language.Make(defaultLang.Language), language.English)
})
tt.Run("the discovery endpoint returns all supported languages", func(ttt *testing.T) {
awaitDiscoveryEndpoint(ttt, domain, supportedLanguagesStr, nil)
})
})
t.Run("restricting the default language fails", func(tt *testing.T) {
_, err := Tester.Client.Admin.SetRestrictions(iamOwnerCtx, &admin.SetRestrictionsRequest{AllowedLanguages: &admin.SelectLanguages{List: []string{defaultAndAllowedLanguage.String()}}})
expectStatus, ok := status.FromError(err)
require.True(tt, ok)
require.Equal(tt, codes.FailedPrecondition, expectStatus.Code())
})
t.Run("not defining any restrictions throws an error", func(tt *testing.T) {
_, err := Tester.Client.Admin.SetRestrictions(iamOwnerCtx, &admin.SetRestrictionsRequest{})
expectStatus, ok := status.FromError(err)
require.True(tt, ok)
require.Equal(tt, codes.InvalidArgument, expectStatus.Code())
})
t.Run("setting the default language works", func(tt *testing.T) {
setAndAwaitDefaultLanguage(iamOwnerCtx, tt, defaultAndAllowedLanguage)
})
t.Run("restricting allowed languages works", func(tt *testing.T) {
setAndAwaitAllowedLanguages(iamOwnerCtx, tt, []string{defaultAndAllowedLanguage.String()})
})
t.Run("GetAllowedLanguage returns only the allowed languages", func(tt *testing.T) {
expectContains, expectNotContains := []string{defaultAndAllowedLanguage.String()}, []string{disallowedLanguage.String()}
adminResp, err := Tester.Client.Admin.GetAllowedLanguages(iamOwnerCtx, &admin.GetAllowedLanguagesRequest{})
require.NoError(t, err)
langs := adminResp.GetLanguages()
assert.Condition(t, contains(langs, expectContains))
assert.Condition(t, not(contains(langs, expectNotContains)))
})
t.Run("setting the default language to a disallowed language fails", func(tt *testing.T) {
_, err := Tester.Client.Admin.SetDefaultLanguage(iamOwnerCtx, &admin.SetDefaultLanguageRequest{Language: disallowedLanguage.String()})
expectStatus, ok := status.FromError(err)
require.True(tt, ok)
require.Equal(tt, codes.FailedPrecondition, expectStatus.Code())
})
t.Run("the list of supported languages includes the disallowed languages", func(tt *testing.T) {
supported, err := Tester.Client.Admin.GetSupportedLanguages(iamOwnerCtx, &admin.GetSupportedLanguagesRequest{})
require.NoError(tt, err)
require.Condition(tt, contains(supported.GetLanguages(), supportedLanguagesStr))
})
t.Run("the disallowed language is not listed in the discovery endpoint", func(tt *testing.T) {
awaitDiscoveryEndpoint(tt, domain, []string{defaultAndAllowedLanguage.String()}, []string{disallowedLanguage.String()})
})
t.Run("the login ui is rendered in the default language", func(tt *testing.T) {
awaitLoginUILanguage(tt, domain, disallowedLanguage, defaultAndAllowedLanguage, "Allgemeine Geschäftsbedingungen und Datenschutz")
})
t.Run("preferred languages are not restricted by the supported languages", func(tt *testing.T) {
tt.Run("change user profile", func(ttt *testing.T) {
resp, err := Tester.Client.Mgmt.ListUsers(iamOwnerCtx, &management.ListUsersRequest{Queries: []*user.SearchQuery{{Query: &user.SearchQuery_UserNameQuery{UserNameQuery: &user.UserNameQuery{
UserName: "zitadel-admin@zitadel.localhost"}},
}}})
require.NoError(ttt, err)
require.Len(ttt, resp.GetResult(), 1)
humanAdmin := resp.GetResult()[0]
profile := humanAdmin.GetHuman().GetProfile()
require.NotEqual(ttt, unsupportedLanguage.String(), profile.GetPreferredLanguage())
_, updateErr := Tester.Client.Mgmt.UpdateHumanProfile(iamOwnerCtx, &management.UpdateHumanProfileRequest{
PreferredLanguage: unsupportedLanguage.String(),
UserId: humanAdmin.GetId(),
FirstName: profile.GetFirstName(),
LastName: profile.GetLastName(),
NickName: profile.GetNickName(),
DisplayName: profile.GetDisplayName(),
Gender: profile.GetGender(),
})
require.NoError(ttt, updateErr)
})
})
t.Run("custom texts are only restricted by the supported languages", func(tt *testing.T) {
_, err := Tester.Client.Admin.SetCustomLoginText(iamOwnerCtx, &admin.SetCustomLoginTextsRequest{
Language: disallowedLanguage.String(),
EmailVerificationText: &text.EmailVerificationScreenText{
Description: "hodor",
},
})
assert.NoError(tt, err)
_, err = Tester.Client.Mgmt.SetCustomLoginText(iamOwnerCtx, &management.SetCustomLoginTextsRequest{
Language: disallowedLanguage.String(),
EmailVerificationText: &text.EmailVerificationScreenText{
Description: "hodor",
},
})
assert.NoError(tt, err)
_, err = Tester.Client.Mgmt.SetCustomInitMessageText(iamOwnerCtx, &management.SetCustomInitMessageTextRequest{
Language: disallowedLanguage.String(),
Text: "hodor",
})
assert.NoError(tt, err)
_, err = Tester.Client.Admin.SetDefaultInitMessageText(iamOwnerCtx, &admin.SetDefaultInitMessageTextRequest{
Language: disallowedLanguage.String(),
Text: "hodor",
})
assert.NoError(tt, err)
})
t.Run("allowing all languages works", func(tt *testing.T) {
tt.Run("restricting allowed languages works", func(ttt *testing.T) {
setAndAwaitAllowedLanguages(iamOwnerCtx, ttt, make([]string, 0))
})
})
t.Run("allowing the language makes it usable again", func(tt *testing.T) {
tt.Run("the previously disallowed language is listed in the discovery endpoint again", func(ttt *testing.T) {
awaitDiscoveryEndpoint(ttt, domain, []string{disallowedLanguage.String()}, nil)
})
tt.Run("the login ui is rendered in the previously disallowed language", func(ttt *testing.T) {
awaitLoginUILanguage(ttt, domain, disallowedLanguage, disallowedLanguage, "Términos y condiciones")
})
})
}
func setAndAwaitAllowedLanguages(ctx context.Context, t *testing.T, selectLanguages []string) {
_, err := Tester.Client.Admin.SetRestrictions(ctx, &admin.SetRestrictionsRequest{AllowedLanguages: &admin.SelectLanguages{List: selectLanguages}})
require.NoError(t, err)
awaitCtx, awaitCancel := context.WithTimeout(ctx, 10*time.Second)
defer awaitCancel()
await(t, awaitCtx, func(tt *assert.CollectT) {
restrictions, getErr := Tester.Client.Admin.GetRestrictions(awaitCtx, &admin.GetRestrictionsRequest{})
expectLanguages := selectLanguages
if len(selectLanguages) == 0 {
expectLanguages = nil
}
assert.NoError(tt, getErr)
assert.Equal(tt, expectLanguages, restrictions.GetAllowedLanguages())
})
}
func setAndAwaitDefaultLanguage(ctx context.Context, t *testing.T, lang language.Tag) {
_, err := Tester.Client.Admin.SetDefaultLanguage(ctx, &admin.SetDefaultLanguageRequest{Language: lang.String()})
require.NoError(t, err)
awaitCtx, awaitCancel := context.WithTimeout(ctx, 10*time.Second)
defer awaitCancel()
await(t, awaitCtx, func(tt *assert.CollectT) {
defaultLang, getErr := Tester.Client.Admin.GetDefaultLanguage(awaitCtx, &admin.GetDefaultLanguageRequest{})
assert.NoError(tt, getErr)
assert.Equal(tt, lang.String(), defaultLang.GetLanguage())
})
}
func awaitDiscoveryEndpoint(t *testing.T, domain string, containsUILocales, notContainsUILocales []string) {
awaitCtx, awaitCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer awaitCancel()
await(t, awaitCtx, func(tt *assert.CollectT) {
req, err := http.NewRequestWithContext(awaitCtx, http.MethodGet, "http://"+domain+":8080/.well-known/openid-configuration", nil)
require.NoError(tt, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(tt, err)
require.Equal(tt, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
defer func() {
require.NoError(tt, resp.Body.Close())
}()
require.NoError(tt, err)
doc := struct {
UILocalesSupported []string `json:"ui_locales_supported"`
}{}
require.NoError(tt, json.Unmarshal(body, &doc))
if containsUILocales != nil {
assert.Condition(tt, contains(doc.UILocalesSupported, containsUILocales))
}
if notContainsUILocales != nil {
assert.Condition(tt, not(contains(doc.UILocalesSupported, notContainsUILocales)))
}
})
}
func awaitLoginUILanguage(t *testing.T, domain string, acceptLanguage language.Tag, expectLang language.Tag, containsText string) {
awaitCtx, awaitCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer awaitCancel()
await(t, awaitCtx, func(tt *assert.CollectT) {
req, err := http.NewRequestWithContext(awaitCtx, http.MethodGet, "http://"+domain+":8080/ui/login/register", nil)
req.Header.Set("Accept-Language", acceptLanguage.String())
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
defer func() {
require.NoError(t, resp.Body.Close())
}()
require.NoError(t, err)
assert.Containsf(t, string(body), containsText, "login ui language is in "+expectLang.String())
})
}
// We would love to use assert.Contains here, but it doesn't work with slices of strings
func contains(container []string, subset []string) assert.Comparison {
return func() bool {
if subset == nil {
return true
}
for _, str := range subset {
var found bool
for _, containerStr := range container {
if str == containerStr {
found = true
break
}
}
if !found {
return false
}
}
return true
}
}
func not(cmp assert.Comparison) assert.Comparison {
return func() bool {
return !cmp()
}
}