Files
zitadel/internal/api/grpc/org/v2/query.go
Livio Spring 6b407ab8f2 feat(api): move organization api (#11045)
# Which Problems Are Solved

As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.

# How the Problems Are Solved

- This PR moves the remaining organization v2beta service endpoints to a
corresponding v2 version. The v2beta service and all endpoints are
deprecated.
- The v2beta endpoints are removed from the docs.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.
- When listing Organizations can now be sorted by creation date as well.
- The custom `org_id` parameter in the `AddOrganizationRequest` message
has been deprecated in favor of `organization_id`

# Additional Changes

None

# Additional Context

- relates to #10772 
- Directly targeting v4.x since main needs to be cleaned up first with
the relation table and permission checks.
2025-11-13 08:43:38 +01:00

290 lines
9.5 KiB
Go

package org
import (
"context"
"connectrpc.com/connect"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
"github.com/zitadel/zitadel/internal/api/grpc/metadata/v2"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
)
func (s *Server) ListOrganizations(ctx context.Context, req *connect.Request[org.ListOrganizationsRequest]) (*connect.Response[org.ListOrganizationsResponse], error) {
queries, err := listOrgRequestToModel(ctx, req)
if err != nil {
return nil, err
}
orgs, err := s.query.SearchOrgs(ctx, queries.Msg, s.checkPermission)
if err != nil {
return nil, err
}
return connect.NewResponse(&org.ListOrganizationsResponse{
Result: organizationsToPb(orgs.Orgs),
Details: object.ToListDetails(orgs.SearchResponse),
}), nil
}
func (s *Server) ListOrganizationMetadata(ctx context.Context, request *connect.Request[org.ListOrganizationMetadataRequest]) (*connect.Response[org.ListOrganizationMetadataResponse], error) {
metadataQueries, err := listOrgMetadataToDomain(s.systemDefaults, request.Msg)
if err != nil {
return nil, err
}
res, err := s.query.SearchOrgMetadata(ctx, true, request.Msg.GetOrganizationId(), metadataQueries, false, true)
if err != nil {
return nil, err
}
return connect.NewResponse(&org.ListOrganizationMetadataResponse{
Metadata: metadata.OrgMetadataListToPb(res.Metadata),
Pagination: &filter_pb.PaginationResponse{
TotalResult: res.Count,
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
},
}), nil
}
func (s *Server) ListOrganizationDomains(ctx context.Context, req *connect.Request[org.ListOrganizationDomainsRequest]) (*connect.Response[org.ListOrganizationDomainsResponse], error) {
queries, err := listOrgDomainsRequestToDomain(s.systemDefaults, req.Msg)
if err != nil {
return nil, err
}
orgIDQuery, err := query.NewOrgDomainOrgIDSearchQuery(req.Msg.GetOrganizationId())
if err != nil {
return nil, err
}
queries.Queries = append(queries.Queries, orgIDQuery)
domains, err := s.query.SearchOrgDomains(ctx, queries, false, true)
if err != nil {
return nil, err
}
return connect.NewResponse(&org.ListOrganizationDomainsResponse{
Domains: domainsToPb(domains.Domains),
Pagination: &filter_pb.PaginationResponse{
TotalResult: domains.Count,
AppliedLimit: uint64(req.Msg.GetPagination().GetLimit()),
},
}), nil
}
func listOrgDomainsRequestToDomain(systemDefaults systemdefaults.SystemDefaults, request *org.ListOrganizationDomainsRequest) (*query.OrgDomainSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(systemDefaults, request.Pagination)
if err != nil {
return nil, err
}
queries, err := DomainQueriesToModel(request.Filters)
if err != nil {
return nil, err
}
return &query.OrgDomainSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: fieldNameToOrganizationDomainColumn(request.GetSortingColumn()),
},
Queries: queries,
}, nil
}
func fieldNameToOrganizationDomainColumn(column org.DomainFieldName) query.Column {
switch column {
case org.DomainFieldName_DOMAIN_FIELD_NAME_NAME:
return query.OrgDomainDomainCol
case org.DomainFieldName_DOMAIN_FIELD_NAME_CREATION_DATE:
return query.OrgDomainCreationDateCol
case org.DomainFieldName_DOMAIN_FIELD_NAME_UNSPECIFIED:
return query.Column{}
default:
return query.Column{}
}
}
func listOrgRequestToModel(ctx context.Context, req *connect.Request[org.ListOrganizationsRequest]) (*connect.Response[query.OrgSearchQueries], error) {
offset, limit, asc := object.ListQueryToQuery(req.Msg.Query)
queries, err := orgQueriesToQuery(ctx, req.Msg.Queries)
if err != nil {
return nil, err
}
return connect.NewResponse(&query.OrgSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
SortingColumn: fieldNameToOrganizationColumn(req.Msg.SortingColumn),
Asc: asc,
},
Queries: queries,
}), nil
}
func orgQueriesToQuery(ctx context.Context, queries []*org.SearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = orgQueryToQuery(ctx, query)
if err != nil {
return nil, err
}
}
return q, nil
}
func orgQueryToQuery(ctx context.Context, orgQuery *org.SearchQuery) (query.SearchQuery, error) {
switch q := orgQuery.Query.(type) {
case *org.SearchQuery_DomainQuery:
return query.NewOrgVerifiedDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.Method), q.DomainQuery.Domain)
case *org.SearchQuery_NameQuery:
return query.NewOrgNameSearchQuery(object.TextMethodToQuery(q.NameQuery.Method), q.NameQuery.Name)
case *org.SearchQuery_StateQuery:
return query.NewOrgStateSearchQuery(orgStateToDomain(q.StateQuery.State))
case *org.SearchQuery_IdQuery:
return query.NewOrgIDSearchQuery(q.IdQuery.Id)
case *org.SearchQuery_DefaultQuery:
return query.NewOrgIDSearchQuery(authz.GetInstance(ctx).DefaultOrganisationID())
default:
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-vR9nC", "List.Query.Invalid")
}
}
func orgStateToPb(state domain.OrgState) org.OrganizationState {
switch state {
case domain.OrgStateActive:
return org.OrganizationState_ORGANIZATION_STATE_ACTIVE
case domain.OrgStateInactive:
return org.OrganizationState_ORGANIZATION_STATE_INACTIVE
case domain.OrgStateRemoved:
return org.OrganizationState_ORGANIZATION_STATE_REMOVED
case domain.OrgStateUnspecified:
fallthrough
default:
return org.OrganizationState_ORGANIZATION_STATE_UNSPECIFIED
}
}
func orgStateToDomain(state org.OrganizationState) domain.OrgState {
switch state {
case org.OrganizationState_ORGANIZATION_STATE_ACTIVE:
return domain.OrgStateActive
case org.OrganizationState_ORGANIZATION_STATE_INACTIVE:
return domain.OrgStateInactive
case org.OrganizationState_ORGANIZATION_STATE_REMOVED:
return domain.OrgStateRemoved
case org.OrganizationState_ORGANIZATION_STATE_UNSPECIFIED:
fallthrough
default:
return domain.OrgStateUnspecified
}
}
func fieldNameToOrganizationColumn(fieldName org.OrganizationFieldName) query.Column {
switch fieldName {
case org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME:
return query.OrgColumnName
case org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_CREATION_DATE:
return query.OrgColumnCreationDate
case org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_UNSPECIFIED:
return query.Column{}
default:
return query.Column{}
}
}
func organizationsToPb(orgs []*query.Org) []*org.Organization {
o := make([]*org.Organization, len(orgs))
for i, org := range orgs {
o[i] = organizationToPb(org)
}
return o
}
func organizationToPb(organization *query.Org) *org.Organization {
return &org.Organization{
Id: organization.ID,
Name: organization.Name,
PrimaryDomain: organization.Domain,
Details: object.DomainToDetailsPb(&domain.ObjectDetails{
Sequence: organization.Sequence,
EventDate: organization.ChangeDate,
ResourceOwner: organization.ResourceOwner,
CreationDate: organization.CreationDate,
}),
State: orgStateToPb(organization.State),
}
}
func listOrgMetadataToDomain(systemDefaults systemdefaults.SystemDefaults, request *org.ListOrganizationMetadataRequest) (*query.OrgMetadataSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(systemDefaults, request.Pagination)
if err != nil {
return nil, err
}
queries, err := metadata.OrgMetadataQueriesToQuery(request.GetFilters())
if err != nil {
return nil, err
}
return &query.OrgMetadataSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
Queries: queries,
}, nil
}
func DomainQueriesToModel(queries []*org.DomainSearchFilter) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = DomainQueryToModel(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func DomainQueryToModel(searchQuery *org.DomainSearchFilter) (query.SearchQuery, error) {
switch q := searchQuery.Filter.(type) {
case *org.DomainSearchFilter_DomainFilter:
return query.NewOrgDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainFilter.Method), q.DomainFilter.GetDomain())
default:
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-Ags89", "List.Query.Invalid")
}
}
func domainsToPb(domains []*query.Domain) []*org.Domain {
d := make([]*org.Domain, len(domains))
for i, domain := range domains {
d[i] = domainToPb(domain)
}
return d
}
func domainToPb(d *query.Domain) *org.Domain {
return &org.Domain{
OrganizationId: d.OrgID,
Domain: d.Domain,
IsVerified: d.IsVerified,
IsPrimary: d.IsPrimary,
ValidationType: domainValidationTypeToPb(d.ValidationType),
}
}
func domainValidationTypeToPb(validationType domain.OrgDomainValidationType) org.DomainValidationType {
switch validationType {
case domain.OrgDomainValidationTypeDNS:
return org.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS
case domain.OrgDomainValidationTypeHTTP:
return org.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP
case domain.OrgDomainValidationTypeUnspecified:
return org.DomainValidationType_DOMAIN_VALIDATION_TYPE_UNSPECIFIED
default:
return org.DomainValidationType_DOMAIN_VALIDATION_TYPE_UNSPECIFIED
}
}