Files
zitadel/internal/query/group_users.go

221 lines
6.0 KiB
Go
Raw Normal View History

package query
import (
"context"
"database/sql"
"slices"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
var (
groupUsersTable = table{
name: projection.GroupUsersProjectionTable,
instanceIDCol: projection.GroupUsersColumnInstanceID,
}
GroupUsersColumnGroupID = Column{
name: projection.GroupUsersColumnGroupID,
table: groupUsersTable,
}
GroupUsersColumnUserID = Column{
name: projection.GroupUsersColumnUserID,
table: groupUsersTable,
}
GroupUsersColumnResourceOwner = Column{
name: projection.GroupUsersColumnResourceOwner,
table: groupUsersTable,
}
GroupUsersColumnCreationDate = Column{
name: projection.GroupUsersColumnCreationDate,
table: groupUsersTable,
}
GroupUsersColumnInstanceID = Column{
name: projection.GroupUsersColumnInstanceID,
table: groupUsersTable,
}
GroupUsersColumnSequence = Column{
name: projection.GroupUsersColumnSequence,
table: groupUsersTable,
}
)
type GroupUsers struct {
SearchResponse
GroupUsers []*GroupUser
}
type GroupUser struct {
GroupID string
ResourceOwner string
CreationDate time.Time
Sequence uint64
// user fields
UserID string
PreferredLoginName string
DisplayName string
AvatarUrl string
}
type GroupUsersSearchQuery struct {
SearchRequest
Queries []SearchQuery
}
func (q *Queries) SearchGroupUsers(ctx context.Context, queries *GroupUsersSearchQuery, permissionCheck domain.PermissionCheck) (_ *GroupUsers, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
groupUsers, err := q.searchGroupUsers(ctx, queries, permissionCheckV2)
if err != nil {
return nil, err
}
if permissionCheck != nil && !permissionCheckV2 {
groupUsersCheckPermission(ctx, groupUsers, permissionCheck)
}
return groupUsers, nil
}
func NewGroupUsersUserIDsSearchQuery(userIDs []string) (SearchQuery, error) {
list := make([]interface{}, len(userIDs))
for i, value := range userIDs {
list[i] = value
}
return NewListQuery(GroupUsersColumnUserID, list, ListIn)
}
func NewGroupUsersGroupIDsSearchQuery(groupIDs []string) (SearchQuery, error) {
list := make([]interface{}, len(groupIDs))
for i, value := range groupIDs {
list[i] = value
}
return NewListQuery(GroupUsersColumnGroupID, list, ListIn)
}
func (q *Queries) searchGroupUsers(ctx context.Context, queries *GroupUsersSearchQuery, permissionCheckV2 bool) (groupUsers *GroupUsers, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareGroupUsersQuery()
query = groupUsersPermissionCheckV2(ctx, query, queries, permissionCheckV2)
eq := sq.And{
sq.Eq{
GroupUsersColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
},
}
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-TTlfF6", "Errors.Query.InvalidRequest")
}
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
groupUsers, err = scan(rows)
return err
}, stmt, args...)
if err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-M5O50l", "Errors.Internal")
}
groupUsers.State, err = q.latestState(ctx, groupUsersTable)
return groupUsers, nil
}
func prepareGroupUsersQuery() (query sq.SelectBuilder, scan func(*sql.Rows) (*GroupUsers, error)) {
return sq.Select(
GroupUsersColumnGroupID.identifier(),
GroupUsersColumnUserID.identifier(),
HumanDisplayNameCol.identifier(),
LoginNameNameCol.identifier(),
GroupUsersColumnResourceOwner.identifier(),
HumanAvatarURLCol.identifier(),
GroupUsersColumnCreationDate.identifier(),
GroupUsersColumnSequence.identifier(),
countColumn.identifier(),
).From(groupUsersTable.identifier()).
LeftJoin(join(HumanUserIDCol, GroupUsersColumnUserID)).
LeftJoin(join(LoginNameUserIDCol, GroupUsersColumnUserID)).
Where(
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
).PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*GroupUsers, error) {
groupUsers := make([]*GroupUser, 0)
var count uint64
for rows.Next() {
g := new(GroupUser)
var (
displayName sql.NullString
avatarURL sql.NullString
preferredLoginName sql.NullString
)
err := rows.Scan(
&g.GroupID,
&g.UserID,
&displayName,
&preferredLoginName,
&g.ResourceOwner,
&avatarURL,
&g.CreationDate,
&g.Sequence,
&count,
)
if err != nil {
return nil, err
}
g.DisplayName = displayName.String
g.AvatarUrl = avatarURL.String
g.PreferredLoginName = preferredLoginName.String
groupUsers = append(groupUsers, g)
}
if err := rows.Close(); err != nil {
return nil, zerrors.ThrowInternal(err, "QUERY-JuX6i5", "Errors.Query.CloseRows")
}
return &GroupUsers{
GroupUsers: groupUsers,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}
func groupUsersCheckPermission(ctx context.Context, groupUsers *GroupUsers, permissionCheck domain.PermissionCheck) {
groupUsers.GroupUsers = slices.DeleteFunc(groupUsers.GroupUsers,
func(gu *GroupUser) bool {
return permissionCheck(ctx, domain.PermissionGroupUserRead, gu.ResourceOwner, gu.GroupID) != nil
},
)
}
func groupUsersPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, queries *GroupUsersSearchQuery, permissionCheckV2 bool) sq.SelectBuilder {
if !permissionCheckV2 {
return query
}
join, args := PermissionClause(
ctx,
GroupUsersColumnResourceOwner,
domain.PermissionGroupUserRead,
)
return query.JoinClause(join, args...)
}
func (q *GroupUsersSearchQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
query = q.toQuery(query)
}
return query
}