mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat(api): return allowed_languages (#11553)
# Which Problems Are Solved While Zitadel provides a possibility to restrict certain languages to be used, the corresponding list could not be retrieved in the settings service (v2). This blocked login v2 implementations from respecting the list and they would always use all available languages. # How the Problems Are Solved - Retrieve the list when checking the instance and pass it into the context. - Return it as part of the existing `GetGeneralSettingsResponse` - This allows us to remove an additional query in some other cases / endpoints. # Additional Changes none # Additional Context - required for https://github.com/zitadel/zitadel/pull/11372 - backport to v4.x --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Marco A. <[email protected]>
This commit is contained in:
co-authored by
Copilot
Marco A.
parent
ceffb9c536
commit
b0609aa861
@@ -11,7 +11,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/feature"
|
"github.com/zitadel/zitadel/internal/feature"
|
||||||
)
|
)
|
||||||
|
|
||||||
var emptyInstance = &instance{}
|
var (
|
||||||
|
emptyInstance = &instance{}
|
||||||
|
_ Instance = (*instance)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Instance interface {
|
type Instance interface {
|
||||||
InstanceID() string
|
InstanceID() string
|
||||||
@@ -19,6 +22,7 @@ type Instance interface {
|
|||||||
ManagementConsoleClientID() string
|
ManagementConsoleClientID() string
|
||||||
ManagementConsoleApplicationID() string
|
ManagementConsoleApplicationID() string
|
||||||
DefaultLanguage() language.Tag
|
DefaultLanguage() language.Tag
|
||||||
|
AllowedLanguages() []language.Tag
|
||||||
DefaultOrganisationID() string
|
DefaultOrganisationID() string
|
||||||
SecurityPolicyAllowedOrigins() []string
|
SecurityPolicyAllowedOrigins() []string
|
||||||
EnableImpersonation() bool
|
EnableImpersonation() bool
|
||||||
@@ -43,6 +47,7 @@ type instance struct {
|
|||||||
clientID string
|
clientID string
|
||||||
orgID string
|
orgID string
|
||||||
defaultLanguage language.Tag
|
defaultLanguage language.Tag
|
||||||
|
allowedLanguages []language.Tag
|
||||||
features feature.Features
|
features feature.Features
|
||||||
executionTargets target.Router
|
executionTargets target.Router
|
||||||
}
|
}
|
||||||
@@ -75,6 +80,10 @@ func (i *instance) DefaultLanguage() language.Tag {
|
|||||||
return i.defaultLanguage
|
return i.defaultLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *instance) AllowedLanguages() []language.Tag {
|
||||||
|
return i.allowedLanguages
|
||||||
|
}
|
||||||
|
|
||||||
func (i *instance) DefaultOrganisationID() string {
|
func (i *instance) DefaultOrganisationID() string {
|
||||||
return i.orgID
|
return i.orgID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ func (m *mockInstance) DefaultLanguage() language.Tag {
|
|||||||
return language.English
|
return language.English
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) AllowedLanguages() []language.Tag {
|
||||||
|
return []language.Tag{language.English}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockInstance) DefaultOrganisationID() string {
|
func (m *mockInstance) DefaultOrganisationID() string {
|
||||||
return "orgID"
|
return "orgID"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1112,7 +1112,9 @@ func TestServer_ListPublicKeys(t *testing.T) {
|
|||||||
|
|
||||||
func assertPublicKeys(ttt *assert.CollectT, want *action.ListPublicKeysResponse, got *action.ListPublicKeysResponse) {
|
func assertPublicKeys(ttt *assert.CollectT, want *action.ListPublicKeysResponse, got *action.ListPublicKeysResponse) {
|
||||||
assert.EqualExportedValues(ttt, want.Pagination, got.Pagination)
|
assert.EqualExportedValues(ttt, want.Pagination, got.Pagination)
|
||||||
assert.Len(ttt, got.PublicKeys, len(want.PublicKeys))
|
if !assert.Len(ttt, got.PublicKeys, len(want.PublicKeys)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
for i := range want.PublicKeys {
|
for i := range want.PublicKeys {
|
||||||
assert.Equal(ttt, want.PublicKeys[i].GetKeyId(), got.PublicKeys[i].GetKeyId())
|
assert.Equal(ttt, want.PublicKeys[i].GetKeyId(), got.PublicKeys[i].GetKeyId())
|
||||||
assert.Equal(ttt, want.PublicKeys[i].GetActive(), got.PublicKeys[i].GetActive())
|
assert.Equal(ttt, want.PublicKeys[i].GetActive(), got.PublicKeys[i].GetActive())
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ func (s *Server) GetDefaultLanguage(ctx context.Context, _ *admin_pb.GetDefaultL
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetAllowedLanguages(ctx context.Context, _ *admin_pb.GetAllowedLanguagesRequest) (*admin_pb.GetAllowedLanguagesResponse, error) {
|
func (s *Server) GetAllowedLanguages(ctx context.Context, _ *admin_pb.GetAllowedLanguagesRequest) (*admin_pb.GetAllowedLanguagesResponse, error) {
|
||||||
restrictions, err := s.query.GetInstanceRestrictions(ctx)
|
allowed := authz.GetInstance(ctx).AllowedLanguages()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
allowed := restrictions.AllowedLanguages
|
|
||||||
if len(allowed) == 0 {
|
if len(allowed) == 0 {
|
||||||
allowed = i18n.SupportedLanguages()
|
allowed = i18n.SupportedLanguages()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,6 +281,10 @@ func (m *mockInstance) DefaultLanguage() language.Tag {
|
|||||||
return language.English
|
return language.English
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) AllowedLanguages() []language.Tag {
|
||||||
|
return []language.Tag{language.English}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockInstance) DefaultOrganisationID() string {
|
func (m *mockInstance) DefaultOrganisationID() string {
|
||||||
return "orgID"
|
return "orgID"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,10 +186,15 @@ func activeIdentityProvidersToQuery(req *settings.GetActiveIdentityProvidersRequ
|
|||||||
|
|
||||||
func (s *Server) GetGeneralSettings(ctx context.Context, _ *connect.Request[settings.GetGeneralSettingsRequest]) (*connect.Response[settings.GetGeneralSettingsResponse], error) {
|
func (s *Server) GetGeneralSettings(ctx context.Context, _ *connect.Request[settings.GetGeneralSettingsRequest]) (*connect.Response[settings.GetGeneralSettingsResponse], error) {
|
||||||
instance := authz.GetInstance(ctx)
|
instance := authz.GetInstance(ctx)
|
||||||
|
allowedLanguages := instance.AllowedLanguages()
|
||||||
|
if len(allowedLanguages) == 0 {
|
||||||
|
allowedLanguages = i18n.SupportedLanguages()
|
||||||
|
}
|
||||||
return connect.NewResponse(&settings.GetGeneralSettingsResponse{
|
return connect.NewResponse(&settings.GetGeneralSettingsResponse{
|
||||||
SupportedLanguages: domain.LanguagesToStrings(i18n.SupportedLanguages()),
|
SupportedLanguages: domain.LanguagesToStrings(i18n.SupportedLanguages()),
|
||||||
DefaultOrgId: instance.DefaultOrganisationID(),
|
DefaultOrgId: instance.DefaultOrganisationID(),
|
||||||
DefaultLanguage: instance.DefaultLanguage().String(),
|
DefaultLanguage: instance.DefaultLanguage().String(),
|
||||||
|
AllowedLanguages: domain.LanguagesToStrings(allowedLanguages),
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -338,6 +338,10 @@ func (m *mockInstance) DefaultLanguage() language.Tag {
|
|||||||
return language.English
|
return language.English
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) AllowedLanguages() []language.Tag {
|
||||||
|
return []language.Tag{language.English}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockInstance) DefaultOrganisationID() string {
|
func (m *mockInstance) DefaultOrganisationID() string {
|
||||||
return "orgID"
|
return "orgID"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,11 +120,7 @@ func (s *Server) Discovery(ctx context.Context, r *op.Request[struct{}]) (_ *op.
|
|||||||
err = oidcError(ctx, err)
|
err = oidcError(ctx, err)
|
||||||
span.EndWithError(err)
|
span.EndWithError(err)
|
||||||
}()
|
}()
|
||||||
restrictions, err := s.query.GetInstanceRestrictions(ctx)
|
allowedLanguages := authz.GetInstance(ctx).AllowedLanguages()
|
||||||
if err != nil {
|
|
||||||
return nil, op.NewStatusError(oidc.ErrServerError().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("internal server error"), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
allowedLanguages := restrictions.AllowedLanguages
|
|
||||||
if len(allowedLanguages) == 0 {
|
if len(allowedLanguages) == 0 {
|
||||||
allowedLanguages = i18n.SupportedLanguages()
|
allowedLanguages = i18n.SupportedLanguages()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,11 +433,7 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, transl
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) getTranslator(ctx context.Context, authReq *domain.AuthRequest) *i18n.Translator {
|
func (l *Login) getTranslator(ctx context.Context, authReq *domain.AuthRequest) *i18n.Translator {
|
||||||
restrictions, err := l.query.GetInstanceRestrictions(ctx)
|
translator := l.renderer.NewTranslator(ctx, authz.GetInstance(ctx).AllowedLanguages())
|
||||||
if err != nil {
|
|
||||||
logging.OnError(err).Warn("cannot load instance restrictions to retrieve allowed languages for creating the translator")
|
|
||||||
}
|
|
||||||
translator := l.renderer.NewTranslator(ctx, restrictions.AllowedLanguages)
|
|
||||||
if authReq != nil {
|
if authReq != nil {
|
||||||
l.addLoginTranslations(translator, authReq.DefaultTranslations)
|
l.addLoginTranslations(translator, authReq.DefaultTranslations)
|
||||||
l.addLoginTranslations(translator, authReq.OrgTranslations)
|
l.addLoginTranslations(translator, authReq.OrgTranslations)
|
||||||
|
|||||||
@@ -205,6 +205,10 @@ func (m *mockInstance) DefaultLanguage() language.Tag {
|
|||||||
return AllowedLanguage
|
return AllowedLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) AllowedLanguages() []language.Tag {
|
||||||
|
return []language.Tag{language.English}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockInstance) DefaultOrganisationID() string {
|
func (m *mockInstance) DefaultOrganisationID() string {
|
||||||
return "defaultOrgID"
|
return "defaultOrgID"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/database"
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||||
target_domain "github.com/zitadel/zitadel/internal/execution/target"
|
target_domain "github.com/zitadel/zitadel/internal/execution/target"
|
||||||
@@ -472,6 +473,7 @@ type authzInstance struct {
|
|||||||
ExternalDomains database.TextArray[string] `json:"external_domains,omitempty"`
|
ExternalDomains database.TextArray[string] `json:"external_domains,omitempty"`
|
||||||
TrustedDomains database.TextArray[string] `json:"trusted_domains,omitempty"`
|
TrustedDomains database.TextArray[string] `json:"trusted_domains,omitempty"`
|
||||||
ExecutionTargets target_domain.Router `json:"execution_targets,omitzero"`
|
ExecutionTargets target_domain.Router `json:"execution_targets,omitzero"`
|
||||||
|
AllowedLangs []language.Tag `json:"allowed_langs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type csp struct {
|
type csp struct {
|
||||||
@@ -499,6 +501,10 @@ func (i *authzInstance) DefaultLanguage() language.Tag {
|
|||||||
return i.DefaultLang
|
return i.DefaultLang
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *authzInstance) AllowedLanguages() []language.Tag {
|
||||||
|
return i.AllowedLangs
|
||||||
|
}
|
||||||
|
|
||||||
func (i *authzInstance) DefaultOrganisationID() string {
|
func (i *authzInstance) DefaultOrganisationID() string {
|
||||||
return i.DefaultOrgID
|
return i.DefaultOrgID
|
||||||
}
|
}
|
||||||
@@ -566,6 +572,7 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) {
|
|||||||
block sql.NullBool
|
block sql.NullBool
|
||||||
features []byte
|
features []byte
|
||||||
executionTargetsBytes []byte
|
executionTargetsBytes []byte
|
||||||
|
allowedLanguages database.TextArray[string]
|
||||||
)
|
)
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&instance.ID,
|
&instance.ID,
|
||||||
@@ -583,6 +590,7 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) {
|
|||||||
&instance.ExternalDomains,
|
&instance.ExternalDomains,
|
||||||
&instance.TrustedDomains,
|
&instance.TrustedDomains,
|
||||||
&executionTargetsBytes,
|
&executionTargetsBytes,
|
||||||
|
&allowedLanguages,
|
||||||
)
|
)
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return zerrors.ThrowNotFound(nil, "QUERY-1kIjX", "Errors.Instance.NotFound")
|
return zerrors.ThrowNotFound(nil, "QUERY-1kIjX", "Errors.Instance.NotFound")
|
||||||
@@ -611,6 +619,7 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) {
|
|||||||
}
|
}
|
||||||
instance.ExecutionTargets = target_domain.NewRouter(targets)
|
instance.ExecutionTargets = target_domain.NewRouter(targets)
|
||||||
}
|
}
|
||||||
|
instance.AllowedLangs = domain.StringsToLanguages(allowedLanguages)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ select
|
|||||||
f.features,
|
f.features,
|
||||||
ed.domains as external_domains,
|
ed.domains as external_domains,
|
||||||
td.domains as trusted_domains,
|
td.domains as trusted_domains,
|
||||||
et.execution_targets
|
et.execution_targets,
|
||||||
|
r.allowed_languages
|
||||||
from domain d
|
from domain d
|
||||||
join projections.instances i on i.id = d.instance_id
|
join projections.instances i on i.id = d.instance_id
|
||||||
left join projections.security_policies2 s on i.id = s.instance_id
|
left join projections.security_policies2 s on i.id = s.instance_id
|
||||||
@@ -81,4 +82,5 @@ left join projections.limits l on i.id = l.instance_id
|
|||||||
left join features f on i.id = f.instance_id
|
left join features f on i.id = f.instance_id
|
||||||
left join external_domains ed on i.id = ed.instance_id
|
left join external_domains ed on i.id = ed.instance_id
|
||||||
left join trusted_domains td on i.id = td.instance_id
|
left join trusted_domains td on i.id = td.instance_id
|
||||||
left join execution_targets et on i.id = et.instance_id;
|
left join execution_targets et on i.id = et.instance_id
|
||||||
|
left join projections.restrictions2 r on i.id = r.instance_id;
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ select
|
|||||||
f.features,
|
f.features,
|
||||||
ed.domains as external_domains,
|
ed.domains as external_domains,
|
||||||
td.domains as trusted_domains,
|
td.domains as trusted_domains,
|
||||||
et.execution_targets
|
et.execution_targets,
|
||||||
|
r.allowed_languages
|
||||||
from projections.instances i
|
from projections.instances i
|
||||||
left join projections.security_policies2 s on i.id = s.instance_id
|
left join projections.security_policies2 s on i.id = s.instance_id
|
||||||
left join projections.limits l on i.id = l.instance_id
|
left join projections.limits l on i.id = l.instance_id
|
||||||
@@ -71,4 +72,5 @@ left join features f on i.id = f.instance_id
|
|||||||
left join external_domains ed on i.id = ed.instance_id
|
left join external_domains ed on i.id = ed.instance_id
|
||||||
left join trusted_domains td on i.id = td.instance_id
|
left join trusted_domains td on i.id = td.instance_id
|
||||||
left join execution_targets et on i.id = et.instance_id
|
left join execution_targets et on i.id = et.instance_id
|
||||||
|
left join projections.restrictions2 r on i.id = r.instance_id
|
||||||
where i.id = $1;
|
where i.id = $1;
|
||||||
|
|||||||
@@ -701,6 +701,8 @@ message GetGeneralSettingsResponse {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// The list of supported languages.
|
// The list of supported languages.
|
||||||
|
// Note that the instance might restrict the languages further
|
||||||
|
// only allowing a subset of these languages to be used.
|
||||||
// The format is a BCP 47 language tag (e.g. "en", "de", "fr-CH").
|
// The format is a BCP 47 language tag (e.g. "en", "de", "fr-CH").
|
||||||
repeated string supported_languages = 3 [
|
repeated string supported_languages = 3 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
@@ -711,6 +713,16 @@ message GetGeneralSettingsResponse {
|
|||||||
// The unique identifier of the default organization.
|
// The unique identifier of the default organization.
|
||||||
// The default organization is used to assign new users to an organization if no other organization is specified.
|
// The default organization is used to assign new users to an organization if no other organization is specified.
|
||||||
string default_organization_id = 4;
|
string default_organization_id = 4;
|
||||||
|
|
||||||
|
// The list of allowed languages for the instance.
|
||||||
|
// This is a subset of the supported languages to be used in the instance
|
||||||
|
// e.g. for user selection during registration or language detection in the UI.
|
||||||
|
// The format is a BCP 47 language tag (e.g. "en", "de", "fr-CH").
|
||||||
|
repeated string allowed_languages = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"en\", \"de\", \"it\"]"
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetSecuritySettingsRequest{}
|
message GetSecuritySettingsRequest{}
|
||||||
|
|||||||
Reference in New Issue
Block a user