Files
zitadel/internal/api/grpc/settings/v2/query.go
T
b0609aa861 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]>
2026-02-12 08:44:34 +00:00

319 lines
13 KiB
Go

package settings
import (
"context"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
)
func (s *Server) GetLoginSettings(ctx context.Context, req *connect.Request[settings.GetLoginSettingsRequest]) (*connect.Response[settings.GetLoginSettingsResponse], error) {
current, err := s.query.LoginPolicyByID(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetLoginSettingsResponse{
Settings: loginSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.OrgID,
},
}), nil
}
func (s *Server) GetPasswordComplexitySettings(ctx context.Context, req *connect.Request[settings.GetPasswordComplexitySettingsRequest]) (*connect.Response[settings.GetPasswordComplexitySettingsResponse], error) {
current, err := s.query.PasswordComplexityPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetPasswordComplexitySettingsResponse{
Settings: passwordComplexitySettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}), nil
}
func (s *Server) GetPasswordExpirySettings(ctx context.Context, req *connect.Request[settings.GetPasswordExpirySettingsRequest]) (*connect.Response[settings.GetPasswordExpirySettingsResponse], error) {
current, err := s.query.PasswordAgePolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetPasswordExpirySettingsResponse{
Settings: passwordExpirySettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}), nil
}
func (s *Server) GetBrandingSettings(ctx context.Context, req *connect.Request[settings.GetBrandingSettingsRequest]) (*connect.Response[settings.GetBrandingSettingsResponse], error) {
current, err := s.query.ActiveLabelPolicyByOrg(ctx, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetBrandingSettingsResponse{
Settings: brandingSettingsToPb(current, s.assetsAPIDomain(ctx)),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}), nil
}
func (s *Server) GetDomainSettings(ctx context.Context, req *connect.Request[settings.GetDomainSettingsRequest]) (*connect.Response[settings.GetDomainSettingsResponse], error) {
current, err := s.query.DomainPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetDomainSettingsResponse{
Settings: domainSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}), nil
}
func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *connect.Request[settings.GetLegalAndSupportSettingsRequest]) (*connect.Response[settings.GetLegalAndSupportSettingsResponse], error) {
current, err := s.query.PrivacyPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetLegalAndSupportSettingsResponse{
Settings: legalAndSupportSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}), nil
}
func (s *Server) GetLockoutSettings(ctx context.Context, req *connect.Request[settings.GetLockoutSettingsRequest]) (*connect.Response[settings.GetLockoutSettingsResponse], error) {
current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()))
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetLockoutSettingsResponse{
Settings: lockoutSettingsToPb(current),
Details: &object_pb.Details{
Sequence: current.Sequence,
CreationDate: timestamppb.New(current.CreationDate),
ChangeDate: timestamppb.New(current.ChangeDate),
ResourceOwner: current.ResourceOwner,
},
}), nil
}
func (s *Server) GetActiveIdentityProviders(ctx context.Context, req *connect.Request[settings.GetActiveIdentityProvidersRequest]) (*connect.Response[settings.GetActiveIdentityProvidersResponse], error) {
queries, err := activeIdentityProvidersToQuery(req.Msg)
if err != nil {
return nil, err
}
links, err := s.query.IDPLoginPolicyLinks(ctx, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), &query.IDPLoginPolicyLinksSearchQuery{Queries: queries}, false)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetActiveIdentityProvidersResponse{
Details: object.ToListDetails(links.SearchResponse),
IdentityProviders: identityProvidersToPb(links.Links),
}), nil
}
func activeIdentityProvidersToQuery(req *settings.GetActiveIdentityProvidersRequest) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, 0, 4)
if req.CreationAllowed != nil {
creationQuery, err := query.NewIDPTemplateIsCreationAllowedSearchQuery(*req.CreationAllowed)
if err != nil {
return nil, err
}
q = append(q, creationQuery)
}
if req.LinkingAllowed != nil {
creationQuery, err := query.NewIDPTemplateIsLinkingAllowedSearchQuery(*req.LinkingAllowed)
if err != nil {
return nil, err
}
q = append(q, creationQuery)
}
if req.AutoCreation != nil {
creationQuery, err := query.NewIDPTemplateIsAutoCreationSearchQuery(*req.AutoCreation)
if err != nil {
return nil, err
}
q = append(q, creationQuery)
}
if req.AutoLinking != nil {
compare := query.NumberEquals
if *req.AutoLinking {
compare = query.NumberNotEquals
}
creationQuery, err := query.NewIDPTemplateAutoLinkingSearchQuery(0, compare)
if err != nil {
return nil, err
}
q = append(q, creationQuery)
}
return q, nil
}
func (s *Server) GetGeneralSettings(ctx context.Context, _ *connect.Request[settings.GetGeneralSettingsRequest]) (*connect.Response[settings.GetGeneralSettingsResponse], error) {
instance := authz.GetInstance(ctx)
allowedLanguages := instance.AllowedLanguages()
if len(allowedLanguages) == 0 {
allowedLanguages = i18n.SupportedLanguages()
}
return connect.NewResponse(&settings.GetGeneralSettingsResponse{
SupportedLanguages: domain.LanguagesToStrings(i18n.SupportedLanguages()),
DefaultOrgId: instance.DefaultOrganisationID(),
DefaultLanguage: instance.DefaultLanguage().String(),
AllowedLanguages: domain.LanguagesToStrings(allowedLanguages),
}), nil
}
func (s *Server) GetSecuritySettings(ctx context.Context, req *connect.Request[settings.GetSecuritySettingsRequest]) (*connect.Response[settings.GetSecuritySettingsResponse], error) {
policy, err := s.query.SecurityPolicy(ctx)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.GetSecuritySettingsResponse{
Settings: securityPolicyToSettingsPb(policy),
}), nil
}
func (s *Server) GetHostedLoginTranslation(ctx context.Context, req *connect.Request[settings.GetHostedLoginTranslationRequest]) (*connect.Response[settings.GetHostedLoginTranslationResponse], error) {
translation, err := s.query.GetHostedLoginTranslation(ctx, req.Msg)
if err != nil {
return nil, err
}
return connect.NewResponse(translation), nil
}
func (s *Server) ListOrganizationSettings(ctx context.Context, req *connect.Request[settings.ListOrganizationSettingsRequest]) (*connect.Response[settings.ListOrganizationSettingsResponse], error) {
queries, err := s.listOrganizationSettingsRequestToModel(req.Msg)
if err != nil {
return nil, err
}
resp, err := s.query.SearchOrganizationSettings(ctx, queries, s.checkPermission)
if err != nil {
return nil, err
}
return connect.NewResponse(&settings.ListOrganizationSettingsResponse{
OrganizationSettings: organizationSettingsListToPb(resp.OrganizationSettingsList),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}), nil
}
func (s *Server) listOrganizationSettingsRequestToModel(req *settings.ListOrganizationSettingsRequest) (*query.OrganizationSettingsSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
if err != nil {
return nil, err
}
queries, err := organizationSettingsFiltersToQuery(req.Filters)
if err != nil {
return nil, err
}
return &query.OrganizationSettingsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: organizationSettingsFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func organizationSettingsFieldNameToSortingColumn(field *settings.OrganizationSettingsFieldName) query.Column {
if field == nil {
return query.OrganizationSettingsColumnCreationDate
}
switch *field {
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE:
return query.OrganizationSettingsColumnCreationDate
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_ORGANIZATION_ID:
return query.OrganizationSettingsColumnID
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CHANGE_DATE:
return query.OrganizationSettingsColumnChangeDate
case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_UNSPECIFIED:
return query.OrganizationSettingsColumnCreationDate
default:
return query.OrganizationSettingsColumnCreationDate
}
}
func organizationSettingsFiltersToQuery(queries []*settings.OrganizationSettingsSearchFilter) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, qry := range queries {
q[i], err = organizationSettingsToModel(qry)
if err != nil {
return nil, err
}
}
return q, nil
}
func organizationSettingsToModel(filter *settings.OrganizationSettingsSearchFilter) (query.SearchQuery, error) {
switch q := filter.Filter.(type) {
case *settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter:
return organizationInIDsFilterToQuery(q.InOrganizationIdsFilter)
case *settings.OrganizationSettingsSearchFilter_OrganizationScopedUsernamesFilter:
return organizationScopedUsernamesFilterToQuery(q.OrganizationScopedUsernamesFilter)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "SETTINGS-uvTDqZHlvS", "List.Query.Invalid")
}
}
func organizationInIDsFilterToQuery(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
return query.NewOrganizationSettingsOrganizationIDSearchQuery(q.Ids)
}
func organizationScopedUsernamesFilterToQuery(q *settings.OrganizationScopedUsernamesFilter) (query.SearchQuery, error) {
return query.NewOrganizationSettingsOrganizationScopedUsernamesSearchQuery(q.OrganizationScopedUsernames)
}
func organizationSettingsListToPb(settingsList []*query.OrganizationSettings) []*settings.OrganizationSettings {
o := make([]*settings.OrganizationSettings, len(settingsList))
for i, organizationSettings := range settingsList {
o[i] = organizationSettingsToPb(organizationSettings)
}
return o
}
func organizationSettingsToPb(organizationSettings *query.OrganizationSettings) *settings.OrganizationSettings {
return &settings.OrganizationSettings{
OrganizationId: organizationSettings.ID,
CreationDate: timestamppb.New(organizationSettings.CreationDate),
ChangeDate: timestamppb.New(organizationSettings.ChangeDate),
OrganizationScopedUsernames: organizationSettings.OrganizationScopedUsernames,
}
}