2025-01-16 11:09:15 +01:00
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
sq "github.com/Masterminds/squirrel"
|
|
|
|
"github.com/zitadel/logging"
|
|
|
|
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
2025-04-15 19:38:25 +03:00
|
|
|
"github.com/zitadel/zitadel/internal/database"
|
|
|
|
domain_pkg "github.com/zitadel/zitadel/internal/domain"
|
2025-01-16 11:09:15 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2025-04-15 19:38:25 +03:00
|
|
|
// eventstore.permitted_orgs(instanceid text, userid text, system_user_perms JSONB, perm text, filter_org text)
|
|
|
|
wherePermittedOrgsExpr = "%s = ANY(eventstore.permitted_orgs(?, ?, ?, ?, ?))"
|
2025-01-16 11:09:15 +01:00
|
|
|
)
|
|
|
|
|
2025-04-15 19:38:25 +03:00
|
|
|
type permissionClauseBuilder struct {
|
|
|
|
orgIDColumn Column
|
|
|
|
instanceID string
|
|
|
|
userID string
|
|
|
|
systemPermissions []authz.SystemUserPermissions
|
|
|
|
permission string
|
|
|
|
orgID string
|
|
|
|
connections []sq.Eq
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *permissionClauseBuilder) appendConnection(column string, value any) {
|
|
|
|
b.connections = append(b.connections, sq.Eq{column: value})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *permissionClauseBuilder) clauses() sq.Or {
|
|
|
|
clauses := make(sq.Or, 1, len(b.connections)+1)
|
|
|
|
clauses[0] = sq.Expr(
|
|
|
|
fmt.Sprintf(wherePermittedOrgsExpr, b.orgIDColumn.identifier()),
|
|
|
|
b.instanceID,
|
|
|
|
b.userID,
|
|
|
|
database.NewJSONArray(b.systemPermissions),
|
|
|
|
b.permission,
|
|
|
|
b.orgID,
|
|
|
|
)
|
|
|
|
for _, include := range b.connections {
|
|
|
|
clauses = append(clauses, include)
|
|
|
|
}
|
|
|
|
return clauses
|
|
|
|
}
|
2025-02-17 09:55:28 +00:00
|
|
|
|
2025-04-15 19:38:25 +03:00
|
|
|
type PermissionOption func(b *permissionClauseBuilder)
|
|
|
|
|
|
|
|
// OwnedRowsPermissionOption allows rows to be returned of which the current user is the owner.
|
|
|
|
// Even if the user does not have an explicit permission for the organization.
|
|
|
|
// For example an authenticated user can always see his own user account.
|
|
|
|
func OwnedRowsPermissionOption(userIDColumn Column) PermissionOption {
|
|
|
|
return func(b *permissionClauseBuilder) {
|
|
|
|
b.appendConnection(userIDColumn.identifier(), b.userID)
|
|
|
|
}
|
|
|
|
}
|
2025-02-20 15:39:48 +00:00
|
|
|
|
2025-04-15 19:38:25 +03:00
|
|
|
// ConnectionPermissionOption allows returning of rows where the value is matched.
|
|
|
|
// Even if the user does not have an explicit permission for the organization.
|
|
|
|
func ConnectionPermissionOption(column Column, value any) PermissionOption {
|
|
|
|
return func(b *permissionClauseBuilder) {
|
|
|
|
b.appendConnection(column.identifier(), value)
|
|
|
|
}
|
|
|
|
}
|
2025-04-02 16:53:06 +02:00
|
|
|
|
2025-04-15 19:38:25 +03:00
|
|
|
// SingleOrgPermissionOption may be used to optimize the permitted orgs function by limiting the
|
|
|
|
// returned organizations, to the one used in the requested filters.
|
|
|
|
func SingleOrgPermissionOption(queries []SearchQuery) PermissionOption {
|
|
|
|
return func(b *permissionClauseBuilder) {
|
|
|
|
b.orgID = findTextEqualsQuery(b.orgIDColumn, queries)
|
|
|
|
}
|
|
|
|
}
|
2025-02-20 15:39:48 +00:00
|
|
|
|
2025-04-15 19:38:25 +03:00
|
|
|
// PermissionClause sets a `WHERE` clause to query,
|
|
|
|
// which filters returned rows the current authenticated user has the requested permission to.
|
|
|
|
//
|
|
|
|
// Experimental: Work in progress. Currently only organization permissions are supported
|
|
|
|
func PermissionClause(ctx context.Context, orgIDCol Column, permission string, options ...PermissionOption) sq.Or {
|
|
|
|
ctxData := authz.GetCtxData(ctx)
|
|
|
|
b := &permissionClauseBuilder{
|
|
|
|
orgIDColumn: orgIDCol,
|
|
|
|
instanceID: authz.GetInstance(ctx).InstanceID(),
|
|
|
|
userID: ctxData.UserID,
|
|
|
|
systemPermissions: ctxData.SystemUserPermissions,
|
|
|
|
permission: permission,
|
|
|
|
}
|
|
|
|
for _, opt := range options {
|
|
|
|
opt(b)
|
2025-04-02 16:53:06 +02:00
|
|
|
}
|
2025-04-15 19:38:25 +03:00
|
|
|
logging.WithFields(
|
|
|
|
"org_id_column", b.orgIDColumn,
|
|
|
|
"instance_id", b.instanceID,
|
|
|
|
"user_id", b.userID,
|
|
|
|
"system_user_permissions", b.systemPermissions,
|
|
|
|
"permission", b.permission,
|
|
|
|
"org_id", b.orgID,
|
|
|
|
"overrides", b.connections,
|
|
|
|
).Debug("permitted orgs check used")
|
|
|
|
|
|
|
|
return b.clauses()
|
|
|
|
}
|
2025-04-02 16:53:06 +02:00
|
|
|
|
2025-04-15 19:38:25 +03:00
|
|
|
// PermissionV2 checks are enabled when the feature flag is set and the permission check function is not nil.
|
|
|
|
// When the permission check function is nil, it indicates a v1 API and no resource based permission check is needed.
|
|
|
|
func PermissionV2(ctx context.Context, cf domain_pkg.PermissionCheck) bool {
|
|
|
|
return authz.GetFeatures(ctx).PermissionCheckV2 && cf != nil
|
2025-02-20 15:39:48 +00:00
|
|
|
}
|