mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:17:32 +00:00
Merge branch 'main' into clean-transactional-propsal
This commit is contained in:
347
internal/query/administrators.go
Normal file
347
internal/query/administrators.go
Normal file
@@ -0,0 +1,347 @@
|
||||
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/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type Administrators struct {
|
||||
SearchResponse
|
||||
Administrators []*Administrator
|
||||
}
|
||||
|
||||
type Administrator struct {
|
||||
Roles database.TextArray[string]
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
|
||||
User *UserAdministrator
|
||||
Org *OrgAdministrator
|
||||
Instance *InstanceAdministrator
|
||||
Project *ProjectAdministrator
|
||||
ProjectGrant *ProjectGrantAdministrator
|
||||
}
|
||||
|
||||
type UserAdministrator struct {
|
||||
UserID string
|
||||
LoginName string
|
||||
DisplayName string
|
||||
ResourceOwner string
|
||||
}
|
||||
type OrgAdministrator struct {
|
||||
OrgID string
|
||||
Name string
|
||||
}
|
||||
|
||||
type InstanceAdministrator struct {
|
||||
InstanceID string
|
||||
Name string
|
||||
}
|
||||
|
||||
type ProjectAdministrator struct {
|
||||
ProjectID string
|
||||
Name string
|
||||
ResourceOwner string
|
||||
}
|
||||
|
||||
type ProjectGrantAdministrator struct {
|
||||
ProjectID string
|
||||
ProjectName string
|
||||
GrantID string
|
||||
GrantedOrgID string
|
||||
ResourceOwner string
|
||||
}
|
||||
|
||||
func NewAdministratorUserResourceOwnerSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserResourceOwnerCol, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewAdministratorUserLoginNameSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(LoginNameNameCol, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewAdministratorUserDisplayNameSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(HumanDisplayNameCol, value, TextEquals)
|
||||
}
|
||||
|
||||
func administratorInstancePermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
InstanceMemberResourceOwner,
|
||||
domain.PermissionInstanceMemberRead,
|
||||
OwnedRowsPermissionOption(InstanceMemberUserID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func administratorOrgPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
OrgMemberResourceOwner,
|
||||
domain.PermissionOrgMemberRead,
|
||||
OwnedRowsPermissionOption(OrgMemberUserID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func administratorProjectPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
ProjectMemberResourceOwner,
|
||||
domain.PermissionProjectMemberRead,
|
||||
WithProjectsPermissionOption(ProjectMemberProjectID),
|
||||
OwnedRowsPermissionOption(ProjectMemberUserID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func administratorProjectGrantPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
ProjectGrantMemberResourceOwner,
|
||||
domain.PermissionProjectGrantMemberRead,
|
||||
WithProjectsPermissionOption(ProjectMemberProjectID),
|
||||
OwnedRowsPermissionOption(ProjectGrantMemberUserID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func administratorsCheckPermission(ctx context.Context, administrators *Administrators, permissionCheck domain.PermissionCheck) {
|
||||
selfUserID := authz.GetCtxData(ctx).UserID
|
||||
administrators.Administrators = slices.DeleteFunc(administrators.Administrators,
|
||||
func(administrator *Administrator) bool {
|
||||
if administrator.User != nil && administrator.User.UserID == selfUserID {
|
||||
return false
|
||||
}
|
||||
if administrator.ProjectGrant != nil {
|
||||
return administratorProjectGrantCheckPermission(ctx, administrator.ProjectGrant.ResourceOwner, administrator.ProjectGrant.ProjectID, administrator.ProjectGrant.GrantID, administrator.ProjectGrant.GrantedOrgID, permissionCheck) != nil
|
||||
}
|
||||
if administrator.Project != nil {
|
||||
return permissionCheck(ctx, domain.PermissionProjectMemberRead, administrator.Project.ResourceOwner, administrator.Project.ProjectID) != nil
|
||||
}
|
||||
if administrator.Org != nil {
|
||||
return permissionCheck(ctx, domain.PermissionOrgMemberRead, administrator.Org.OrgID, administrator.Org.OrgID) != nil
|
||||
}
|
||||
if administrator.Instance != nil {
|
||||
return permissionCheck(ctx, domain.PermissionInstanceMemberRead, administrator.Instance.InstanceID, administrator.Instance.InstanceID) != nil
|
||||
}
|
||||
return true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func administratorProjectGrantCheckPermission(ctx context.Context, resourceOwner, projectID, grantID, grantedOrgID string, permissionCheck domain.PermissionCheck) error {
|
||||
if err := permissionCheck(ctx, domain.PermissionProjectGrantMemberRead, resourceOwner, grantID); err != nil {
|
||||
if err := permissionCheck(ctx, domain.PermissionProjectGrantMemberRead, grantedOrgID, grantID); err != nil {
|
||||
if err := permissionCheck(ctx, domain.PermissionProjectGrantMemberRead, resourceOwner, projectID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queries) SearchAdministrators(ctx context.Context, queries *MembershipSearchQuery, permissionCheck domain.PermissionCheck) (*Administrators, error) {
|
||||
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
|
||||
admins, err := q.searchAdministrators(ctx, queries, permissionCheckV2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
|
||||
administratorsCheckPermission(ctx, admins, permissionCheck)
|
||||
}
|
||||
return admins, nil
|
||||
}
|
||||
|
||||
func (q *Queries) searchAdministrators(ctx context.Context, queries *MembershipSearchQuery, permissionCheckV2 bool) (administrators *Administrators, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, queryArgs, scan := prepareAdministratorsQuery(ctx, queries, permissionCheckV2)
|
||||
eq := sq.Eq{membershipInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-TODO", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
latestState, err := q.latestState(ctx, orgMemberTable, instanceMemberTable, projectMemberTable, projectGrantMemberTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queryArgs = append(queryArgs, args...)
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
administrators, err = scan(rows)
|
||||
return err
|
||||
}, stmt, queryArgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
administrators.State = latestState
|
||||
return administrators, nil
|
||||
}
|
||||
|
||||
func prepareAdministratorsQuery(ctx context.Context, queries *MembershipSearchQuery, permissionV2 bool) (sq.SelectBuilder, []interface{}, func(*sql.Rows) (*Administrators, error)) {
|
||||
query, args := getMembershipFromQuery(ctx, queries, permissionV2)
|
||||
return sq.Select(
|
||||
MembershipUserID.identifier(),
|
||||
membershipRoles.identifier(),
|
||||
MembershipCreationDate.identifier(),
|
||||
MembershipChangeDate.identifier(),
|
||||
membershipResourceOwner.identifier(),
|
||||
membershipOrgID.identifier(),
|
||||
membershipIAMID.identifier(),
|
||||
membershipProjectID.identifier(),
|
||||
membershipGrantID.identifier(),
|
||||
ProjectGrantColumnGrantedOrgID.identifier(),
|
||||
ProjectColumnResourceOwner.identifier(),
|
||||
ProjectColumnName.identifier(),
|
||||
OrgColumnName.identifier(),
|
||||
InstanceColumnName.identifier(),
|
||||
LoginNameNameCol.identifier(),
|
||||
HumanDisplayNameCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
HumanAvatarURLCol.identifier(),
|
||||
UserTypeCol.identifier(),
|
||||
UserResourceOwnerCol.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(query).
|
||||
LeftJoin(join(ProjectColumnID, membershipProjectID)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, membershipGrantID)).
|
||||
LeftJoin(join(OrgColumnID, membershipOrgID)).
|
||||
LeftJoin(join(InstanceColumnID, membershipInstanceID)).
|
||||
LeftJoin(join(HumanUserIDCol, OrgMemberUserID)).
|
||||
LeftJoin(join(MachineUserIDCol, OrgMemberUserID)).
|
||||
LeftJoin(join(UserIDCol, OrgMemberUserID)).
|
||||
LeftJoin(join(LoginNameUserIDCol, OrgMemberUserID)).
|
||||
Where(
|
||||
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
|
||||
).PlaceholderFormat(sq.Dollar),
|
||||
args,
|
||||
func(rows *sql.Rows) (*Administrators, error) {
|
||||
administrators := make([]*Administrator, 0)
|
||||
var count uint64
|
||||
for rows.Next() {
|
||||
|
||||
var (
|
||||
administrator = new(Administrator)
|
||||
userID = sql.NullString{}
|
||||
orgID = sql.NullString{}
|
||||
instanceID = sql.NullString{}
|
||||
projectID = sql.NullString{}
|
||||
grantID = sql.NullString{}
|
||||
grantedOrgID = sql.NullString{}
|
||||
projectName = sql.NullString{}
|
||||
orgName = sql.NullString{}
|
||||
instanceName = sql.NullString{}
|
||||
projectResourceOwner = sql.NullString{}
|
||||
loginName = sql.NullString{}
|
||||
displayName = sql.NullString{}
|
||||
machineName = sql.NullString{}
|
||||
avatarURL = sql.NullString{}
|
||||
userType = sql.NullInt32{}
|
||||
userResourceOwner = sql.NullString{}
|
||||
)
|
||||
|
||||
err := rows.Scan(
|
||||
&userID,
|
||||
&administrator.Roles,
|
||||
&administrator.CreationDate,
|
||||
&administrator.ChangeDate,
|
||||
&administrator.ResourceOwner,
|
||||
&orgID,
|
||||
&instanceID,
|
||||
&projectID,
|
||||
&grantID,
|
||||
&grantedOrgID,
|
||||
&projectResourceOwner,
|
||||
&projectName,
|
||||
&orgName,
|
||||
&instanceName,
|
||||
&loginName,
|
||||
&displayName,
|
||||
&machineName,
|
||||
&avatarURL,
|
||||
&userType,
|
||||
&userResourceOwner,
|
||||
&count,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userID.Valid {
|
||||
administrator.User = &UserAdministrator{
|
||||
UserID: userID.String,
|
||||
LoginName: loginName.String,
|
||||
DisplayName: displayName.String,
|
||||
ResourceOwner: userResourceOwner.String,
|
||||
}
|
||||
}
|
||||
|
||||
if orgID.Valid {
|
||||
administrator.Org = &OrgAdministrator{
|
||||
OrgID: orgID.String,
|
||||
Name: orgName.String,
|
||||
}
|
||||
}
|
||||
if instanceID.Valid {
|
||||
administrator.Instance = &InstanceAdministrator{
|
||||
InstanceID: instanceID.String,
|
||||
Name: instanceName.String,
|
||||
}
|
||||
}
|
||||
if projectID.Valid && grantID.Valid && grantedOrgID.Valid {
|
||||
administrator.ProjectGrant = &ProjectGrantAdministrator{
|
||||
ProjectID: projectID.String,
|
||||
ProjectName: projectName.String,
|
||||
GrantID: grantID.String,
|
||||
GrantedOrgID: grantedOrgID.String,
|
||||
ResourceOwner: projectResourceOwner.String,
|
||||
}
|
||||
} else if projectID.Valid {
|
||||
administrator.Project = &ProjectAdministrator{
|
||||
ProjectID: projectID.String,
|
||||
Name: projectName.String,
|
||||
ResourceOwner: projectResourceOwner.String,
|
||||
}
|
||||
}
|
||||
|
||||
administrators = append(administrators, administrator)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-TODO", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return &Administrators{
|
||||
Administrators: administrators,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
@@ -5,9 +5,11 @@ import (
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@@ -307,6 +309,19 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) AppByIDWithPermission(ctx context.Context, appID string, activeOnly bool, permissionCheck domain.PermissionCheck) (*App, error) {
|
||||
app, err := q.AppByID(ctx, appID, activeOnly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := appCheckPermission(ctx, app.ResourceOwner, app.ProjectID, permissionCheck); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (q *Queries) AppByID(ctx context.Context, appID string, activeOnly bool) (app *App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -455,27 +470,6 @@ func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string) (id s
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string) (project *Project, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := prepareProjectByOIDCAppQuery()
|
||||
eq := sq.Eq{
|
||||
AppOIDCConfigColumnClientID.identifier(): id,
|
||||
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-XhJi4", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
project, err = scan(row)
|
||||
return err
|
||||
}, query, args...)
|
||||
return project, err
|
||||
}
|
||||
|
||||
func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string) (app *App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -526,11 +520,25 @@ func (q *Queries) AppByClientID(ctx context.Context, clientID string) (app *App,
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, withOwnerRemoved bool) (apps *Apps, err error) {
|
||||
func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, permissionCheck domain.PermissionCheck) (*Apps, error) {
|
||||
apps, err := q.searchApps(ctx, queries, PermissionV2(ctx, permissionCheck))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
|
||||
apps.Apps = appsCheckPermission(ctx, apps.Apps, permissionCheck)
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (q *Queries) searchApps(ctx context.Context, queries *AppSearchQueries, isPermissionV2Enabled bool) (apps *Apps, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := prepareAppsQuery()
|
||||
query = appPermissionCheckV2(ctx, query, isPermissionV2Enabled, queries)
|
||||
|
||||
eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
@@ -548,6 +556,21 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit
|
||||
return apps, err
|
||||
}
|
||||
|
||||
func appPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *AppSearchQueries) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
AppColumnResourceOwner,
|
||||
domain.PermissionProjectAppRead,
|
||||
SingleOrgPermissionOption(queries.Queries),
|
||||
WithProjectsPermissionOption(AppColumnProjectID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries, shouldTriggerBulk bool) (ids []string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -624,10 +647,25 @@ func (q *Queries) SAMLAppLoginVersion(ctx context.Context, appID string) (loginV
|
||||
return loginVersion, nil
|
||||
}
|
||||
|
||||
func appCheckPermission(ctx context.Context, resourceOwner string, projectID string, permissionCheck domain.PermissionCheck) error {
|
||||
return permissionCheck(ctx, domain.PermissionProjectAppRead, resourceOwner, projectID)
|
||||
}
|
||||
|
||||
// appsCheckPermission returns only the apps that the user in context has permission to read
|
||||
func appsCheckPermission(ctx context.Context, apps []*App, permissionCheck domain.PermissionCheck) []*App {
|
||||
return slices.DeleteFunc(apps, func(app *App) bool {
|
||||
return permissionCheck(ctx, domain.PermissionProjectAppRead, app.ResourceOwner, app.ProjectID) != nil
|
||||
})
|
||||
}
|
||||
|
||||
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnName, value, method)
|
||||
}
|
||||
|
||||
func NewAppStateSearchQuery(value domain.AppState) (SearchQuery, error) {
|
||||
return NewNumberQuery(AppColumnState, int(value), NumberEquals)
|
||||
}
|
||||
|
||||
func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnProjectID, id, TextEquals)
|
||||
}
|
||||
@@ -867,48 +905,6 @@ func prepareProjectIDByAppQuery() (sq.SelectBuilder, func(*sql.Row) (projectID s
|
||||
}
|
||||
}
|
||||
|
||||
func prepareProjectByOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*Project, error)) {
|
||||
return sq.Select(
|
||||
ProjectColumnID.identifier(),
|
||||
ProjectColumnCreationDate.identifier(),
|
||||
ProjectColumnChangeDate.identifier(),
|
||||
ProjectColumnResourceOwner.identifier(),
|
||||
ProjectColumnState.identifier(),
|
||||
ProjectColumnSequence.identifier(),
|
||||
ProjectColumnName.identifier(),
|
||||
ProjectColumnProjectRoleAssertion.identifier(),
|
||||
ProjectColumnProjectRoleCheck.identifier(),
|
||||
ProjectColumnHasProjectCheck.identifier(),
|
||||
ProjectColumnPrivateLabelingSetting.identifier(),
|
||||
).From(projectsTable.identifier()).
|
||||
Join(join(AppColumnProjectID, ProjectColumnID)).
|
||||
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*Project, error) {
|
||||
p := new(Project)
|
||||
err := row.Scan(
|
||||
&p.ID,
|
||||
&p.CreationDate,
|
||||
&p.ChangeDate,
|
||||
&p.ResourceOwner,
|
||||
&p.State,
|
||||
&p.Sequence,
|
||||
&p.Name,
|
||||
&p.ProjectRoleAssertion,
|
||||
&p.ProjectRoleCheck,
|
||||
&p.HasProjectCheck,
|
||||
&p.PrivateLabelingSetting,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-yxTMh", "Errors.Project.NotFound")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-dj2FF", "Errors.Internal")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareProjectByAppQuery() (sq.SelectBuilder, func(*sql.Row) (*Project, error)) {
|
||||
return sq.Select(
|
||||
ProjectColumnID.identifier(),
|
||||
@@ -1181,7 +1177,7 @@ func (c sqlOIDCConfig) set(app *App) {
|
||||
if c.loginBaseURI.Valid {
|
||||
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
||||
}
|
||||
compliance := domain.GetOIDCCompliance(app.OIDCConfig.Version, app.OIDCConfig.AppType, app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, app.OIDCConfig.AuthMethodType, app.OIDCConfig.RedirectURIs)
|
||||
compliance := domain.GetOIDCCompliance(gu.Ptr(app.OIDCConfig.Version), gu.Ptr(app.OIDCConfig.AppType), app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, gu.Ptr(app.OIDCConfig.AuthMethodType), app.OIDCConfig.RedirectURIs)
|
||||
app.OIDCConfig.ComplianceProblems = compliance.Problems
|
||||
|
||||
var err error
|
||||
|
@@ -98,6 +98,7 @@ type AuthNKey struct {
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
ApplicationID string
|
||||
|
||||
Expiration time.Time
|
||||
Type domain.AuthNKeyType
|
||||
@@ -222,6 +223,19 @@ func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySear
|
||||
return authNKeys, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetAuthNKeyByIDWithPermission(ctx context.Context, shouldTriggerBulk bool, id string, permissionCheck domain.PermissionCheck, queries ...SearchQuery) (*AuthNKey, error) {
|
||||
key, err := q.GetAuthNKeyByID(ctx, shouldTriggerBulk, id, queries...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := appCheckPermission(ctx, key.ResourceOwner, key.AggregateID, permissionCheck); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, id string, queries ...SearchQuery) (key *AuthNKey, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -254,34 +268,6 @@ func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, i
|
||||
return key, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetAuthNKeyPublicKeyByIDAndIdentifier(ctx context.Context, id string, identifier string) (key []byte, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := prepareAuthNKeyPublicKeyQuery()
|
||||
eq := sq.And{
|
||||
sq.Eq{
|
||||
AuthNKeyColumnID.identifier(): id,
|
||||
AuthNKeyColumnIdentifier.identifier(): identifier,
|
||||
AuthNKeyColumnEnabled.identifier(): true,
|
||||
AuthNKeyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
},
|
||||
sq.Gt{
|
||||
AuthNKeyColumnExpiration.identifier(): time.Now(),
|
||||
},
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-DAb32", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
key, err = scan(row)
|
||||
return err
|
||||
}, query, args...)
|
||||
return key, err
|
||||
}
|
||||
|
||||
func NewAuthNKeyResourceOwnerQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(AuthNKeyColumnResourceOwner, id, TextEquals)
|
||||
}
|
||||
@@ -358,6 +344,7 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
|
||||
AuthNKeyColumnSequence.identifier(),
|
||||
AuthNKeyColumnExpiration.identifier(),
|
||||
AuthNKeyColumnType.identifier(),
|
||||
AuthNKeyColumnObjectID.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(authNKeyTable.identifier()).
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
@@ -376,6 +363,7 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
|
||||
&authNKey.Sequence,
|
||||
&authNKey.Expiration,
|
||||
&authNKey.Type,
|
||||
&authNKey.ApplicationID,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -429,26 +417,6 @@ func prepareAuthNKeyQuery() (sq.SelectBuilder, func(row *sql.Row) (*AuthNKey, er
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAuthNKeyPublicKeyQuery() (sq.SelectBuilder, func(row *sql.Row) ([]byte, error)) {
|
||||
return sq.Select(
|
||||
AuthNKeyColumnPublicKey.identifier(),
|
||||
).From(authNKeyTable.identifier()).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) ([]byte, error) {
|
||||
var publicKey []byte
|
||||
err := row.Scan(
|
||||
&publicKey,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-SDf32", "Errors.AuthNKey.NotFound")
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Bfs2a", "Errors.Internal")
|
||||
}
|
||||
return publicKey, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAuthNKeysDataQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeysData, error)) {
|
||||
return sq.Select(
|
||||
AuthNKeyColumnID.identifier(),
|
||||
|
@@ -26,6 +26,7 @@ var (
|
||||
` projections.authn_keys2.sequence,` +
|
||||
` projections.authn_keys2.expiration,` +
|
||||
` projections.authn_keys2.type,` +
|
||||
` projections.authn_keys2.object_id,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.authn_keys2`
|
||||
prepareAuthNKeysCols = []string{
|
||||
@@ -37,6 +38,7 @@ var (
|
||||
"sequence",
|
||||
"expiration",
|
||||
"type",
|
||||
"object_id",
|
||||
"count",
|
||||
}
|
||||
|
||||
@@ -129,6 +131,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
1,
|
||||
"app1",
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -147,6 +150,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Expiration: testNow,
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -168,6 +172,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
1,
|
||||
"app1",
|
||||
},
|
||||
{
|
||||
"id-2",
|
||||
@@ -178,6 +183,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
1,
|
||||
"app1",
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -196,6 +202,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Expiration: testNow,
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
{
|
||||
ID: "id-2",
|
||||
@@ -206,6 +213,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Expiration: testNow,
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -423,55 +431,6 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
},
|
||||
object: (*AuthNKey)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareAuthNKeyPublicKeyQuery no result",
|
||||
prepare: prepareAuthNKeyPublicKeyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueriesScanErr(
|
||||
regexp.QuoteMeta(prepareAuthNKeyPublicKeyStmt),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: ([]byte)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareAuthNKeyPublicKeyQuery found",
|
||||
prepare: prepareAuthNKeyPublicKeyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(prepareAuthNKeyPublicKeyStmt),
|
||||
prepareAuthNKeyPublicKeyCols,
|
||||
[]driver.Value{
|
||||
[]byte("publicKey"),
|
||||
},
|
||||
),
|
||||
},
|
||||
object: []byte("publicKey"),
|
||||
},
|
||||
{
|
||||
name: "prepareAuthNKeyPublicKeyQuery sql err",
|
||||
prepare: prepareAuthNKeyPublicKeyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(prepareAuthNKeyPublicKeyStmt),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: ([]byte)(nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
256
internal/query/hosted_login_translation.go
Normal file
256
internal/query/hosted_login_translation.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"dario.cat/mergo"
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/zitadel/logging"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/v2/org"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed v2-default.json
|
||||
defaultLoginTranslations []byte
|
||||
|
||||
defaultSystemTranslations map[language.Tag]map[string]any
|
||||
|
||||
hostedLoginTranslationTable = table{
|
||||
name: projection.HostedLoginTranslationTable,
|
||||
instanceIDCol: projection.HostedLoginTranslationInstanceIDCol,
|
||||
}
|
||||
|
||||
hostedLoginTranslationColInstanceID = Column{
|
||||
name: projection.HostedLoginTranslationInstanceIDCol,
|
||||
table: hostedLoginTranslationTable,
|
||||
}
|
||||
hostedLoginTranslationColResourceOwner = Column{
|
||||
name: projection.HostedLoginTranslationAggregateIDCol,
|
||||
table: hostedLoginTranslationTable,
|
||||
}
|
||||
hostedLoginTranslationColResourceOwnerType = Column{
|
||||
name: projection.HostedLoginTranslationAggregateTypeCol,
|
||||
table: hostedLoginTranslationTable,
|
||||
}
|
||||
hostedLoginTranslationColLocale = Column{
|
||||
name: projection.HostedLoginTranslationLocaleCol,
|
||||
table: hostedLoginTranslationTable,
|
||||
}
|
||||
hostedLoginTranslationColFile = Column{
|
||||
name: projection.HostedLoginTranslationFileCol,
|
||||
table: hostedLoginTranslationTable,
|
||||
}
|
||||
hostedLoginTranslationColEtag = Column{
|
||||
name: projection.HostedLoginTranslationEtagCol,
|
||||
table: hostedLoginTranslationTable,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := json.Unmarshal(defaultLoginTranslations, &defaultSystemTranslations)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type HostedLoginTranslations struct {
|
||||
SearchResponse
|
||||
HostedLoginTranslations []*HostedLoginTranslation
|
||||
}
|
||||
|
||||
type HostedLoginTranslation struct {
|
||||
AggregateID string
|
||||
Sequence uint64
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
|
||||
Locale string
|
||||
File map[string]any
|
||||
LevelType string
|
||||
LevelID string
|
||||
Etag string
|
||||
}
|
||||
|
||||
func (q *Queries) GetHostedLoginTranslation(ctx context.Context, req *settings.GetHostedLoginTranslationRequest) (res *settings.GetHostedLoginTranslationResponse, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
inst := authz.GetInstance(ctx)
|
||||
defaultInstLang := inst.DefaultLanguage()
|
||||
|
||||
lang, err := language.BCP47.Parse(req.GetLocale())
|
||||
if err != nil || lang.IsRoot() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "QUERY-rZLAGi", "Errors.Arguments.Locale.Invalid")
|
||||
}
|
||||
parentLang := lang.Parent()
|
||||
if parentLang.IsRoot() {
|
||||
parentLang = lang
|
||||
}
|
||||
|
||||
sysTranslation, systemEtag, err := getSystemTranslation(parentLang, defaultInstLang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var levelID, resourceOwner string
|
||||
switch t := req.GetLevel().(type) {
|
||||
case *settings.GetHostedLoginTranslationRequest_System:
|
||||
return getTranslationOutputMessage(sysTranslation, systemEtag)
|
||||
case *settings.GetHostedLoginTranslationRequest_Instance:
|
||||
levelID = authz.GetInstance(ctx).InstanceID()
|
||||
resourceOwner = instance.AggregateType
|
||||
case *settings.GetHostedLoginTranslationRequest_OrganizationId:
|
||||
levelID = t.OrganizationId
|
||||
resourceOwner = org.AggregateType
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "QUERY-YB6Sri", "Errors.Arguments.Level.Invalid")
|
||||
}
|
||||
|
||||
stmt, scan := prepareHostedLoginTranslationQuery()
|
||||
|
||||
langORBaseLang := sq.Or{
|
||||
sq.Eq{hostedLoginTranslationColLocale.identifier(): lang.String()},
|
||||
sq.Eq{hostedLoginTranslationColLocale.identifier(): parentLang.String()},
|
||||
}
|
||||
eq := sq.Eq{
|
||||
hostedLoginTranslationColInstanceID.identifier(): inst.InstanceID(),
|
||||
hostedLoginTranslationColResourceOwner.identifier(): levelID,
|
||||
hostedLoginTranslationColResourceOwnerType.identifier(): resourceOwner,
|
||||
}
|
||||
|
||||
query, args, err := stmt.Where(eq).Where(langORBaseLang).ToSql()
|
||||
if err != nil {
|
||||
logging.WithError(err).Error("unable to generate sql statement")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-ZgCMux", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
var trs []*HostedLoginTranslation
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
trs, err = scan(rows)
|
||||
return err
|
||||
}, query, args...)
|
||||
if err != nil {
|
||||
logging.WithError(err).Error("failed to query translations")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-6k1zjx", "Errors.Internal")
|
||||
}
|
||||
|
||||
requestedTranslation, parentTranslation := &HostedLoginTranslation{}, &HostedLoginTranslation{}
|
||||
for _, tr := range trs {
|
||||
if tr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if tr.LevelType == resourceOwner {
|
||||
requestedTranslation = tr
|
||||
} else {
|
||||
parentTranslation = tr
|
||||
}
|
||||
}
|
||||
|
||||
if !req.GetIgnoreInheritance() {
|
||||
|
||||
// There is no record for the requested level, set the upper level etag
|
||||
if requestedTranslation.Etag == "" {
|
||||
requestedTranslation.Etag = parentTranslation.Etag
|
||||
}
|
||||
|
||||
// Case where Level == ORGANIZATION -> Check if we have an instance level translation
|
||||
// If so, merge it with the translations we have
|
||||
if parentTranslation != nil && parentTranslation.LevelType == instance.AggregateType {
|
||||
if err := mergo.Merge(&requestedTranslation.File, parentTranslation.File); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-pdgEJd", "Errors.Query.MergeTranslations")
|
||||
}
|
||||
}
|
||||
|
||||
// The DB query returned no results, we have to set the system translation etag
|
||||
if requestedTranslation.Etag == "" {
|
||||
requestedTranslation.Etag = systemEtag
|
||||
}
|
||||
|
||||
// Merge the system translations
|
||||
if err := mergo.Merge(&requestedTranslation.File, sysTranslation); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-HdprNF", "Errors.Query.MergeTranslations")
|
||||
}
|
||||
}
|
||||
|
||||
return getTranslationOutputMessage(requestedTranslation.File, requestedTranslation.Etag)
|
||||
}
|
||||
|
||||
func getSystemTranslation(lang, instanceDefaultLang language.Tag) (map[string]any, string, error) {
|
||||
translation, ok := defaultSystemTranslations[lang]
|
||||
if !ok {
|
||||
translation, ok = defaultSystemTranslations[instanceDefaultLang]
|
||||
if !ok {
|
||||
return nil, "", zerrors.ThrowNotFoundf(nil, "QUERY-6gb5QR", "Errors.Query.HostedLoginTranslationNotFound-%s", lang)
|
||||
}
|
||||
}
|
||||
|
||||
hash := md5.Sum(fmt.Append(nil, translation))
|
||||
|
||||
return translation, hex.EncodeToString(hash[:]), nil
|
||||
}
|
||||
|
||||
func prepareHostedLoginTranslationQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*HostedLoginTranslation, error)) {
|
||||
return sq.Select(
|
||||
hostedLoginTranslationColFile.identifier(),
|
||||
hostedLoginTranslationColResourceOwnerType.identifier(),
|
||||
hostedLoginTranslationColEtag.identifier(),
|
||||
).From(hostedLoginTranslationTable.identifier()).
|
||||
Limit(2).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(r *sql.Rows) ([]*HostedLoginTranslation, error) {
|
||||
translations := make([]*HostedLoginTranslation, 0, 2)
|
||||
for r.Next() {
|
||||
var rawTranslation json.RawMessage
|
||||
translation := &HostedLoginTranslation{}
|
||||
err := r.Scan(
|
||||
&rawTranslation,
|
||||
&translation.LevelType,
|
||||
&translation.Etag,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(rawTranslation, &translation.File); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
translations = append(translations, translation)
|
||||
}
|
||||
|
||||
if err := r.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-oc7r7i", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return translations, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getTranslationOutputMessage(translation map[string]any, etag string) (*settings.GetHostedLoginTranslationResponse, error) {
|
||||
protoTranslation, err := structpb.NewStruct(translation)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-70ppPp", "Errors.Protobuf.ConvertToStruct")
|
||||
}
|
||||
|
||||
return &settings.GetHostedLoginTranslationResponse{
|
||||
Translations: protoTranslation,
|
||||
Etag: etag,
|
||||
}, nil
|
||||
}
|
337
internal/query/hosted_login_translation_test.go
Normal file
337
internal/query/hosted_login_translation_test.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/runtime/protoimpl"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/database/mock"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||
)
|
||||
|
||||
func TestGetSystemTranslation(t *testing.T) {
|
||||
okTranslation := defaultLoginTranslations
|
||||
|
||||
parsedOKTranslation := map[string]map[string]any{}
|
||||
require.Nil(t, json.Unmarshal(okTranslation, &parsedOKTranslation))
|
||||
|
||||
hashOK := md5.Sum(fmt.Append(nil, parsedOKTranslation["de"]))
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
|
||||
inputLanguage language.Tag
|
||||
inputInstanceLanguage language.Tag
|
||||
systemTranslationToSet []byte
|
||||
|
||||
expectedLanguage map[string]any
|
||||
expectedEtag string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "when neither input language nor system default language have translation should return not found error",
|
||||
systemTranslationToSet: okTranslation,
|
||||
inputLanguage: language.MustParse("ro"),
|
||||
inputInstanceLanguage: language.MustParse("fr"),
|
||||
|
||||
expectedError: zerrors.ThrowNotFoundf(nil, "QUERY-6gb5QR", "Errors.Query.HostedLoginTranslationNotFound-%s", "ro"),
|
||||
},
|
||||
{
|
||||
testName: "when input language has no translation should fallback onto instance default",
|
||||
systemTranslationToSet: okTranslation,
|
||||
inputLanguage: language.MustParse("ro"),
|
||||
inputInstanceLanguage: language.MustParse("de"),
|
||||
|
||||
expectedLanguage: parsedOKTranslation["de"],
|
||||
expectedEtag: hex.EncodeToString(hashOK[:]),
|
||||
},
|
||||
{
|
||||
testName: "when input language has translation should return it",
|
||||
systemTranslationToSet: okTranslation,
|
||||
inputLanguage: language.MustParse("de"),
|
||||
inputInstanceLanguage: language.MustParse("en"),
|
||||
|
||||
expectedLanguage: parsedOKTranslation["de"],
|
||||
expectedEtag: hex.EncodeToString(hashOK[:]),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Given
|
||||
defaultLoginTranslations = tc.systemTranslationToSet
|
||||
|
||||
// When
|
||||
translation, etag, err := getSystemTranslation(tc.inputLanguage, tc.inputInstanceLanguage)
|
||||
|
||||
// Verify
|
||||
require.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedLanguage, translation)
|
||||
assert.Equal(t, tc.expectedEtag, etag)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTranslationOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validMap := map[string]any{"loginHeader": "A login header"}
|
||||
protoMap, err := structpb.NewStruct(validMap)
|
||||
require.NoError(t, err)
|
||||
|
||||
hash := md5.Sum(fmt.Append(nil, validMap))
|
||||
encodedHash := hex.EncodeToString(hash[:])
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputTranslation map[string]any
|
||||
expectedError error
|
||||
expectedResponse *settings.GetHostedLoginTranslationResponse
|
||||
}{
|
||||
{
|
||||
testName: "when unparsable map should return internal error",
|
||||
inputTranslation: map[string]any{"\xc5z": "something"},
|
||||
expectedError: zerrors.ThrowInternal(protoimpl.X.NewError("invalid UTF-8 in string: %q", "\xc5z"), "QUERY-70ppPp", "Errors.Protobuf.ConvertToStruct"),
|
||||
},
|
||||
{
|
||||
testName: "when input translation is valid should return expected response message",
|
||||
inputTranslation: validMap,
|
||||
expectedResponse: &settings.GetHostedLoginTranslationResponse{
|
||||
Translations: protoMap,
|
||||
Etag: hex.EncodeToString(hash[:]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// When
|
||||
res, err := getTranslationOutputMessage(tc.inputTranslation, encodedHash)
|
||||
|
||||
// Verify
|
||||
require.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedResponse, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHostedLoginTranslation(t *testing.T) {
|
||||
query := `SELECT projections.hosted_login_translations.file, projections.hosted_login_translations.aggregate_type, projections.hosted_login_translations.etag
|
||||
FROM projections.hosted_login_translations
|
||||
WHERE projections.hosted_login_translations.aggregate_id = $1
|
||||
AND projections.hosted_login_translations.aggregate_type = $2
|
||||
AND projections.hosted_login_translations.instance_id = $3
|
||||
AND (projections.hosted_login_translations.locale = $4 OR projections.hosted_login_translations.locale = $5)
|
||||
LIMIT 2`
|
||||
okTranslation := defaultLoginTranslations
|
||||
|
||||
parsedOKTranslation := map[string]map[string]any{}
|
||||
require.NoError(t, json.Unmarshal(okTranslation, &parsedOKTranslation))
|
||||
|
||||
protoDefaultTranslation, err := structpb.NewStruct(parsedOKTranslation["en"])
|
||||
require.Nil(t, err)
|
||||
|
||||
defaultWithDBTranslations := maps.Clone(parsedOKTranslation["en"])
|
||||
defaultWithDBTranslations["test"] = "translation"
|
||||
defaultWithDBTranslations["test2"] = "translation2"
|
||||
protoDefaultWithDBTranslation, err := structpb.NewStruct(defaultWithDBTranslations)
|
||||
require.NoError(t, err)
|
||||
|
||||
nilProtoDefaultMap, err := structpb.NewStruct(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
hashDefaultTranslations := md5.Sum(fmt.Append(nil, parsedOKTranslation["en"]))
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
|
||||
defaultInstanceLanguage language.Tag
|
||||
sqlExpectations []mock.Expectation
|
||||
|
||||
inputRequest *settings.GetHostedLoginTranslationRequest
|
||||
|
||||
expectedError error
|
||||
expectedResult *settings.GetHostedLoginTranslationResponse
|
||||
}{
|
||||
{
|
||||
testName: "when input language is invalid should return invalid argument error",
|
||||
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{},
|
||||
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "QUERY-rZLAGi", "Errors.Arguments.Locale.Invalid"),
|
||||
},
|
||||
{
|
||||
testName: "when input language is root should return invalid argument error",
|
||||
|
||||
defaultInstanceLanguage: language.English,
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "root",
|
||||
},
|
||||
|
||||
expectedError: zerrors.ThrowInvalidArgument(nil, "QUERY-rZLAGi", "Errors.Arguments.Locale.Invalid"),
|
||||
},
|
||||
{
|
||||
testName: "when no system translation is available should return not found error",
|
||||
|
||||
defaultInstanceLanguage: language.Romanian,
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "ro-RO",
|
||||
},
|
||||
|
||||
expectedError: zerrors.ThrowNotFoundf(nil, "QUERY-6gb5QR", "Errors.Query.HostedLoginTranslationNotFound-%s", "ro"),
|
||||
},
|
||||
{
|
||||
testName: "when requesting system translation should return it",
|
||||
|
||||
defaultInstanceLanguage: language.English,
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "en-US",
|
||||
Level: &settings.GetHostedLoginTranslationRequest_System{},
|
||||
},
|
||||
|
||||
expectedResult: &settings.GetHostedLoginTranslationResponse{
|
||||
Translations: protoDefaultTranslation,
|
||||
Etag: hex.EncodeToString(hashDefaultTranslations[:]),
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "when querying DB fails should return internal error",
|
||||
|
||||
defaultInstanceLanguage: language.English,
|
||||
sqlExpectations: []mock.Expectation{
|
||||
mock.ExpectQuery(
|
||||
query,
|
||||
mock.WithQueryArgs("123", "org", "instance-id", "en-US", "en"),
|
||||
mock.WithQueryErr(sql.ErrConnDone),
|
||||
),
|
||||
},
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "en-US",
|
||||
Level: &settings.GetHostedLoginTranslationRequest_OrganizationId{
|
||||
OrganizationId: "123",
|
||||
},
|
||||
},
|
||||
|
||||
expectedError: zerrors.ThrowInternal(sql.ErrConnDone, "QUERY-6k1zjx", "Errors.Internal"),
|
||||
},
|
||||
{
|
||||
testName: "when querying DB returns no result should return system translations",
|
||||
|
||||
defaultInstanceLanguage: language.English,
|
||||
sqlExpectations: []mock.Expectation{
|
||||
mock.ExpectQuery(
|
||||
query,
|
||||
mock.WithQueryArgs("123", "org", "instance-id", "en-US", "en"),
|
||||
mock.WithQueryResult(
|
||||
[]string{"file", "aggregate_type", "etag"},
|
||||
[][]driver.Value{},
|
||||
),
|
||||
),
|
||||
},
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "en-US",
|
||||
Level: &settings.GetHostedLoginTranslationRequest_OrganizationId{
|
||||
OrganizationId: "123",
|
||||
},
|
||||
},
|
||||
|
||||
expectedResult: &settings.GetHostedLoginTranslationResponse{
|
||||
Translations: protoDefaultTranslation,
|
||||
Etag: hex.EncodeToString(hashDefaultTranslations[:]),
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "when querying DB returns no result and inheritance disabled should return empty result",
|
||||
|
||||
defaultInstanceLanguage: language.English,
|
||||
sqlExpectations: []mock.Expectation{
|
||||
mock.ExpectQuery(
|
||||
query,
|
||||
mock.WithQueryArgs("123", "org", "instance-id", "en-US", "en"),
|
||||
mock.WithQueryResult(
|
||||
[]string{"file", "aggregate_type", "etag"},
|
||||
[][]driver.Value{},
|
||||
),
|
||||
),
|
||||
},
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "en-US",
|
||||
Level: &settings.GetHostedLoginTranslationRequest_OrganizationId{
|
||||
OrganizationId: "123",
|
||||
},
|
||||
IgnoreInheritance: true,
|
||||
},
|
||||
|
||||
expectedResult: &settings.GetHostedLoginTranslationResponse{
|
||||
Etag: "",
|
||||
Translations: nilProtoDefaultMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "when querying DB returns records should return merged result",
|
||||
|
||||
defaultInstanceLanguage: language.English,
|
||||
sqlExpectations: []mock.Expectation{
|
||||
mock.ExpectQuery(
|
||||
query,
|
||||
mock.WithQueryArgs("123", "org", "instance-id", "en-US", "en"),
|
||||
mock.WithQueryResult(
|
||||
[]string{"file", "aggregate_type", "etag"},
|
||||
[][]driver.Value{
|
||||
{[]byte(`{"test": "translation"}`), "org", "etag-org"},
|
||||
{[]byte(`{"test2": "translation2"}`), "instance", "etag-instance"},
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
inputRequest: &settings.GetHostedLoginTranslationRequest{
|
||||
Locale: "en-US",
|
||||
Level: &settings.GetHostedLoginTranslationRequest_OrganizationId{
|
||||
OrganizationId: "123",
|
||||
},
|
||||
},
|
||||
|
||||
expectedResult: &settings.GetHostedLoginTranslationResponse{
|
||||
Etag: "etag-org",
|
||||
Translations: protoDefaultWithDBTranslation,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Given
|
||||
db := &database.DB{DB: mock.NewSQLMock(t, tc.sqlExpectations...).DB}
|
||||
querier := Queries{client: db}
|
||||
|
||||
ctx := authz.NewMockContext("instance-id", "org-id", "user-id", authz.WithMockDefaultLanguage(tc.defaultInstanceLanguage))
|
||||
|
||||
// When
|
||||
res, err := querier.GetHostedLoginTranslation(ctx, tc.inputRequest)
|
||||
|
||||
// Verify
|
||||
require.Equal(t, tc.expectedError, err)
|
||||
|
||||
if tc.expectedError == nil {
|
||||
assert.Equal(t, tc.expectedResult.GetEtag(), res.GetEtag())
|
||||
assert.Equal(t, tc.expectedResult.GetTranslations().GetFields(), res.GetTranslations().GetFields())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -8,21 +8,18 @@ import (
|
||||
)
|
||||
|
||||
type InstanceFeatures struct {
|
||||
Details *domain.ObjectDetails
|
||||
LoginDefaultOrg FeatureSource[bool]
|
||||
TriggerIntrospectionProjections FeatureSource[bool]
|
||||
LegacyIntrospection FeatureSource[bool]
|
||||
UserSchema FeatureSource[bool]
|
||||
TokenExchange FeatureSource[bool]
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
WebKey FeatureSource[bool]
|
||||
DebugOIDCParentError FeatureSource[bool]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
DisableUserTokenEvent FeatureSource[bool]
|
||||
EnableBackChannelLogout FeatureSource[bool]
|
||||
LoginV2 FeatureSource[*feature.LoginV2]
|
||||
PermissionCheckV2 FeatureSource[bool]
|
||||
ConsoleUseV2UserApi FeatureSource[bool]
|
||||
Details *domain.ObjectDetails
|
||||
LoginDefaultOrg FeatureSource[bool]
|
||||
UserSchema FeatureSource[bool]
|
||||
TokenExchange FeatureSource[bool]
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
DebugOIDCParentError FeatureSource[bool]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
DisableUserTokenEvent FeatureSource[bool]
|
||||
EnableBackChannelLogout FeatureSource[bool]
|
||||
LoginV2 FeatureSource[*feature.LoginV2]
|
||||
PermissionCheckV2 FeatureSource[bool]
|
||||
ConsoleUseV2UserApi FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
||||
|
@@ -63,12 +63,9 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v1.DefaultLoginInstanceEventType,
|
||||
feature_v2.InstanceResetEventType,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType,
|
||||
feature_v2.InstanceUserSchemaEventType,
|
||||
feature_v2.InstanceTokenExchangeEventType,
|
||||
feature_v2.InstanceImprovedPerformanceEventType,
|
||||
feature_v2.InstanceWebKeyEventType,
|
||||
feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
feature_v2.InstanceDisableUserTokenEvent,
|
||||
@@ -93,8 +90,6 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
|
||||
return false
|
||||
}
|
||||
m.instance.LoginDefaultOrg = m.system.LoginDefaultOrg
|
||||
m.instance.TriggerIntrospectionProjections = m.system.TriggerIntrospectionProjections
|
||||
m.instance.LegacyIntrospection = m.system.LegacyIntrospection
|
||||
m.instance.UserSchema = m.system.UserSchema
|
||||
m.instance.TokenExchange = m.system.TokenExchange
|
||||
m.instance.ImprovedPerformance = m.system.ImprovedPerformance
|
||||
@@ -111,23 +106,16 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
|
||||
return err
|
||||
}
|
||||
switch key {
|
||||
case feature.KeyUnspecified,
|
||||
feature.KeyActionsDeprecated:
|
||||
case feature.KeyUnspecified:
|
||||
return nil
|
||||
case feature.KeyLoginDefaultOrg:
|
||||
features.LoginDefaultOrg.set(level, event.Value)
|
||||
case feature.KeyTriggerIntrospectionProjections:
|
||||
features.TriggerIntrospectionProjections.set(level, event.Value)
|
||||
case feature.KeyLegacyIntrospection:
|
||||
features.LegacyIntrospection.set(level, event.Value)
|
||||
case feature.KeyUserSchema:
|
||||
features.UserSchema.set(level, event.Value)
|
||||
case feature.KeyTokenExchange:
|
||||
features.TokenExchange.set(level, event.Value)
|
||||
case feature.KeyImprovedPerformance:
|
||||
features.ImprovedPerformance.set(level, event.Value)
|
||||
case feature.KeyWebKey:
|
||||
features.WebKey.set(level, event.Value)
|
||||
case feature.KeyDebugOIDCParentError:
|
||||
features.DebugOIDCParentError.set(level, event.Value)
|
||||
case feature.KeyOIDCSingleV1SessionTermination:
|
||||
|
@@ -71,14 +71,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -93,14 +85,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, false,
|
||||
@@ -116,14 +100,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: false,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: false,
|
||||
@@ -142,14 +118,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, false,
|
||||
@@ -158,10 +126,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceResetEventType,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
),
|
||||
),
|
||||
args: args{true},
|
||||
@@ -173,14 +137,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
@@ -195,14 +151,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, false,
|
||||
@@ -211,10 +159,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceResetEventType,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
),
|
||||
),
|
||||
args: args{false},
|
||||
@@ -226,14 +170,6 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
|
@@ -25,12 +25,6 @@ var introspectionTriggerHandlers = sync.OnceValue(func() []*handler.Handler {
|
||||
)
|
||||
})
|
||||
|
||||
// TriggerIntrospectionProjections triggers all projections
|
||||
// relevant to introspection queries concurrently.
|
||||
func TriggerIntrospectionProjections(ctx context.Context) {
|
||||
triggerBatch(ctx, introspectionTriggerHandlers()...)
|
||||
}
|
||||
|
||||
type AppType string
|
||||
|
||||
const (
|
||||
|
@@ -1,20 +1,10 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/repository/keypair"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type Key interface {
|
||||
@@ -36,11 +26,6 @@ type PublicKey interface {
|
||||
Key() interface{}
|
||||
}
|
||||
|
||||
type PrivateKeys struct {
|
||||
SearchResponse
|
||||
Keys []PrivateKey
|
||||
}
|
||||
|
||||
type PublicKeys struct {
|
||||
SearchResponse
|
||||
Keys []PublicKey
|
||||
@@ -72,34 +57,6 @@ func (k *key) Sequence() uint64 {
|
||||
return k.sequence
|
||||
}
|
||||
|
||||
type privateKey struct {
|
||||
key
|
||||
expiry time.Time
|
||||
privateKey *crypto.CryptoValue
|
||||
}
|
||||
|
||||
func (k *privateKey) Expiry() time.Time {
|
||||
return k.expiry
|
||||
}
|
||||
|
||||
func (k *privateKey) Key() *crypto.CryptoValue {
|
||||
return k.privateKey
|
||||
}
|
||||
|
||||
type rsaPublicKey struct {
|
||||
key
|
||||
expiry time.Time
|
||||
publicKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
func (r *rsaPublicKey) Expiry() time.Time {
|
||||
return r.expiry
|
||||
}
|
||||
|
||||
func (r *rsaPublicKey) Key() interface{} {
|
||||
return r.publicKey
|
||||
}
|
||||
|
||||
var (
|
||||
keyTable = table{
|
||||
name: projection.KeyProjectionTable,
|
||||
@@ -157,277 +114,3 @@ var (
|
||||
table: keyPrivateTable,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
keyPublicTable = table{
|
||||
name: projection.KeyPublicTable,
|
||||
instanceIDCol: projection.KeyPrivateColumnInstanceID,
|
||||
}
|
||||
KeyPublicColID = Column{
|
||||
name: projection.KeyPublicColumnID,
|
||||
table: keyPublicTable,
|
||||
}
|
||||
KeyPublicColExpiry = Column{
|
||||
name: projection.KeyPublicColumnExpiry,
|
||||
table: keyPublicTable,
|
||||
}
|
||||
KeyPublicColKey = Column{
|
||||
name: projection.KeyPublicColumnKey,
|
||||
table: keyPublicTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) ActivePublicKeys(ctx context.Context, t time.Time) (keys *PublicKeys, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := preparePublicKeysQuery()
|
||||
if t.IsZero() {
|
||||
t = time.Now()
|
||||
}
|
||||
stmt, args, err := query.Where(
|
||||
sq.And{
|
||||
sq.Eq{KeyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()},
|
||||
sq.Gt{KeyPublicColExpiry.identifier(): t},
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-SDFfg", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
keys, err = scan(rows)
|
||||
return err
|
||||
}, stmt, args...)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Sghn4", "Errors.Internal")
|
||||
}
|
||||
|
||||
keys.State, err = q.latestState(ctx, keyTable)
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return keys, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (q *Queries) ActivePrivateSigningKey(ctx context.Context, t time.Time) (keys *PrivateKeys, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := preparePrivateKeysQuery()
|
||||
if t.IsZero() {
|
||||
t = time.Now()
|
||||
}
|
||||
query, args, err := stmt.Where(
|
||||
sq.And{
|
||||
sq.Eq{
|
||||
KeyColUse.identifier(): crypto.KeyUsageSigning,
|
||||
KeyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
},
|
||||
sq.Gt{KeyPrivateColExpiry.identifier(): t},
|
||||
}).OrderBy(KeyPrivateColExpiry.identifier()).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-SDff2", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
keys, err = scan(rows)
|
||||
return err
|
||||
}, query, args...)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-WRFG4", "Errors.Internal")
|
||||
}
|
||||
keys.State, err = q.latestState(ctx, keyTable)
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return keys, err
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func preparePublicKeysQuery() (sq.SelectBuilder, func(*sql.Rows) (*PublicKeys, error)) {
|
||||
return sq.Select(
|
||||
KeyColID.identifier(),
|
||||
KeyColCreationDate.identifier(),
|
||||
KeyColChangeDate.identifier(),
|
||||
KeyColSequence.identifier(),
|
||||
KeyColResourceOwner.identifier(),
|
||||
KeyColAlgorithm.identifier(),
|
||||
KeyColUse.identifier(),
|
||||
KeyPublicColExpiry.identifier(),
|
||||
KeyPublicColKey.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(keyTable.identifier()).
|
||||
LeftJoin(join(KeyPublicColID, KeyColID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*PublicKeys, error) {
|
||||
keys := make([]PublicKey, 0)
|
||||
var count uint64
|
||||
for rows.Next() {
|
||||
k := new(rsaPublicKey)
|
||||
var keyValue []byte
|
||||
err := rows.Scan(
|
||||
&k.id,
|
||||
&k.creationDate,
|
||||
&k.changeDate,
|
||||
&k.sequence,
|
||||
&k.resourceOwner,
|
||||
&k.algorithm,
|
||||
&k.use,
|
||||
&k.expiry,
|
||||
&keyValue,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.publicKey, err = crypto.BytesToPublicKey(keyValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-rKd6k", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return &PublicKeys{
|
||||
Keys: keys,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func preparePrivateKeysQuery() (sq.SelectBuilder, func(*sql.Rows) (*PrivateKeys, error)) {
|
||||
return sq.Select(
|
||||
KeyColID.identifier(),
|
||||
KeyColCreationDate.identifier(),
|
||||
KeyColChangeDate.identifier(),
|
||||
KeyColSequence.identifier(),
|
||||
KeyColResourceOwner.identifier(),
|
||||
KeyColAlgorithm.identifier(),
|
||||
KeyColUse.identifier(),
|
||||
KeyPrivateColExpiry.identifier(),
|
||||
KeyPrivateColKey.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(keyTable.identifier()).
|
||||
LeftJoin(join(KeyPrivateColID, KeyColID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*PrivateKeys, error) {
|
||||
keys := make([]PrivateKey, 0)
|
||||
var count uint64
|
||||
for rows.Next() {
|
||||
k := new(privateKey)
|
||||
err := rows.Scan(
|
||||
&k.id,
|
||||
&k.creationDate,
|
||||
&k.changeDate,
|
||||
&k.sequence,
|
||||
&k.resourceOwner,
|
||||
&k.algorithm,
|
||||
&k.use,
|
||||
&k.expiry,
|
||||
&k.privateKey,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-rKd6k", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return &PrivateKeys{
|
||||
Keys: keys,
|
||||
SearchResponse: SearchResponse{
|
||||
Count: count,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type PublicKeyReadModel struct {
|
||||
eventstore.ReadModel
|
||||
|
||||
Algorithm string
|
||||
Key *crypto.CryptoValue
|
||||
Expiry time.Time
|
||||
Usage crypto.KeyUsage
|
||||
}
|
||||
|
||||
func NewPublicKeyReadModel(keyID, resourceOwner string) *PublicKeyReadModel {
|
||||
return &PublicKeyReadModel{
|
||||
ReadModel: eventstore.ReadModel{
|
||||
AggregateID: keyID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *PublicKeyReadModel) AppendEvents(events ...eventstore.Event) {
|
||||
wm.ReadModel.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (wm *PublicKeyReadModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *keypair.AddedEvent:
|
||||
wm.Algorithm = e.Algorithm
|
||||
wm.Key = e.PublicKey.Key
|
||||
wm.Expiry = e.PublicKey.Expiry
|
||||
wm.Usage = e.Usage
|
||||
default:
|
||||
}
|
||||
}
|
||||
return wm.ReadModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *PublicKeyReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AwaitOpenTransactions().
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(keypair.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(keypair.AddedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (q *Queries) GetPublicKeyByID(ctx context.Context, keyID string) (_ PublicKey, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
model := NewPublicKeyReadModel(keyID, authz.GetInstance(ctx).InstanceID())
|
||||
if err := q.eventstore.FilterToQueryReducer(ctx, model); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if model.Algorithm == "" || model.Key == nil {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-Ahf7x", "Errors.Key.NotFound")
|
||||
}
|
||||
keyValue, err := crypto.Decrypt(model.Key, q.keyEncryptionAlgorithm)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Ie4oh", "Errors.Internal")
|
||||
}
|
||||
publicKey, err := crypto.BytesToPublicKey(keyValue)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Kai2Z", "Errors.Internal")
|
||||
}
|
||||
|
||||
return &rsaPublicKey{
|
||||
key: key{
|
||||
id: model.AggregateID,
|
||||
creationDate: model.CreationDate,
|
||||
changeDate: model.ChangeDate,
|
||||
sequence: model.ProcessedSequence,
|
||||
resourceOwner: model.ResourceOwner,
|
||||
algorithm: model.Algorithm,
|
||||
use: model.Usage,
|
||||
},
|
||||
expiry: model.Expiry,
|
||||
publicKey: publicKey,
|
||||
}, nil
|
||||
}
|
||||
|
@@ -1,453 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
key_repo "github.com/zitadel/zitadel/internal/repository/keypair"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
preparePublicKeysStmt = `SELECT projections.keys4.id,` +
|
||||
` projections.keys4.creation_date,` +
|
||||
` projections.keys4.change_date,` +
|
||||
` projections.keys4.sequence,` +
|
||||
` projections.keys4.resource_owner,` +
|
||||
` projections.keys4.algorithm,` +
|
||||
` projections.keys4.use,` +
|
||||
` projections.keys4_public.expiry,` +
|
||||
` projections.keys4_public.key,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.keys4` +
|
||||
` LEFT JOIN projections.keys4_public ON projections.keys4.id = projections.keys4_public.id AND projections.keys4.instance_id = projections.keys4_public.instance_id`
|
||||
preparePublicKeysCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"sequence",
|
||||
"resource_owner",
|
||||
"algorithm",
|
||||
"use",
|
||||
"expiry",
|
||||
"key",
|
||||
"count",
|
||||
}
|
||||
|
||||
preparePrivateKeysStmt = `SELECT projections.keys4.id,` +
|
||||
` projections.keys4.creation_date,` +
|
||||
` projections.keys4.change_date,` +
|
||||
` projections.keys4.sequence,` +
|
||||
` projections.keys4.resource_owner,` +
|
||||
` projections.keys4.algorithm,` +
|
||||
` projections.keys4.use,` +
|
||||
` projections.keys4_private.expiry,` +
|
||||
` projections.keys4_private.key,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.keys4` +
|
||||
` LEFT JOIN projections.keys4_private ON projections.keys4.id = projections.keys4_private.id AND projections.keys4.instance_id = projections.keys4_private.instance_id`
|
||||
)
|
||||
|
||||
func Test_KeyPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "preparePublicKeysQuery no result",
|
||||
prepare: preparePublicKeysQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(preparePublicKeysStmt),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: &PublicKeys{Keys: []PublicKey{}},
|
||||
},
|
||||
{
|
||||
name: "preparePublicKeysQuery found",
|
||||
prepare: preparePublicKeysQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(preparePublicKeysStmt),
|
||||
preparePublicKeysCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"key-id",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211109),
|
||||
"ro",
|
||||
"RS256",
|
||||
0,
|
||||
testNow,
|
||||
[]byte("-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsvX9P58JFxEs5C+L+H7W\nduFSWL5EPzber7C2m94klrSV6q0bAcrYQnGwFOlveThsY200hRbadKaKjHD7qIKH\nDEe0IY2PSRht33Jye52AwhkRw+M3xuQH/7R8LydnsNFk2KHpr5X2SBv42e37LjkE\nslKSaMRgJW+v0KZ30piY8QsdFRKKaVg5/Ajt1YToM1YVsdHXJ3vmXFMtypLdxwUD\ndIaLEX6pFUkU75KSuEQ/E2luT61Q3ta9kOWm9+0zvi7OMcbdekJT7mzcVnh93R1c\n13ZhQCLbh9A7si8jKFtaMWevjayrvqQABEcTN9N4Hoxcyg6l4neZtRDk75OMYcqm\nDQIDAQAB\n-----END RSA PUBLIC KEY-----\n"),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &PublicKeys{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Keys: []PublicKey{
|
||||
&rsaPublicKey{
|
||||
key: key{
|
||||
id: "key-id",
|
||||
creationDate: testNow,
|
||||
changeDate: testNow,
|
||||
sequence: 20211109,
|
||||
resourceOwner: "ro",
|
||||
algorithm: "RS256",
|
||||
use: crypto.KeyUsageSigning,
|
||||
},
|
||||
expiry: testNow,
|
||||
publicKey: &rsa.PublicKey{
|
||||
E: 65537,
|
||||
N: fromBase16("b2f5fd3f9f0917112ce42f8bf87ed676e15258be443f36deafb0b69bde2496b495eaad1b01cad84271b014e96f79386c636d348516da74a68a8c70fba882870c47b4218d8f49186ddf72727b9d80c21911c3e337c6e407ffb47c2f2767b0d164d8a1e9af95f6481bf8d9edfb2e3904b2529268c460256fafd0a677d29898f10b1d15128a695839fc08edd584e8335615b1d1d7277be65c532dca92ddc7050374868b117ea9154914ef9292b8443f13696e4fad50ded6bd90e5a6f7ed33be2ece31c6dd7a4253ee6cdc56787ddd1d5cd776614022db87d03bb22f23285b5a3167af8dacabbea40004471337d3781e8c5cca0ea5e27799b510e4ef938c61caa60d"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preparePublicKeysQuery sql err",
|
||||
prepare: preparePublicKeysQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(preparePublicKeysStmt),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*PublicKeys)(nil),
|
||||
},
|
||||
{
|
||||
name: "preparePrivateKeysQuery no result",
|
||||
prepare: preparePrivateKeysQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(preparePrivateKeysStmt),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: &PrivateKeys{Keys: []PrivateKey{}},
|
||||
},
|
||||
{
|
||||
name: "preparePrivateKeysQuery found",
|
||||
prepare: preparePrivateKeysQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(preparePrivateKeysStmt),
|
||||
preparePublicKeysCols,
|
||||
[][]driver.Value{
|
||||
{
|
||||
"key-id",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211109),
|
||||
"ro",
|
||||
"RS256",
|
||||
0,
|
||||
testNow,
|
||||
[]byte(`{"Algorithm": "enc", "Crypted": "cHJpdmF0ZUtleQ==", "CryptoType": 0, "KeyID": "id"}`),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &PrivateKeys{
|
||||
SearchResponse: SearchResponse{
|
||||
Count: 1,
|
||||
},
|
||||
Keys: []PrivateKey{
|
||||
&privateKey{
|
||||
key: key{
|
||||
id: "key-id",
|
||||
creationDate: testNow,
|
||||
changeDate: testNow,
|
||||
sequence: 20211109,
|
||||
resourceOwner: "ro",
|
||||
algorithm: "RS256",
|
||||
use: crypto.KeyUsageSigning,
|
||||
},
|
||||
expiry: testNow,
|
||||
privateKey: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preparePrivateKeysQuery sql err",
|
||||
prepare: preparePrivateKeysQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(preparePrivateKeysStmt),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*PrivateKeys)(nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func fromBase16(base16 string) *big.Int {
|
||||
i, ok := new(big.Int).SetString(base16, 16)
|
||||
if !ok {
|
||||
panic("bad number: " + base16)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
const pubKey = `-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs38btwb3c7r0tMaQpGvB
|
||||
mY+mPwMU/LpfuPoC0k2t4RsKp0fv40SMl50CRrHgk395wch8PMPYbl3+8TtYAJuy
|
||||
rFALIj3Ff1UcKIk0hOH5DDsfh7/q2wFuncTmS6bifYo8CfSq2vDGnM7nZnEvxY/M
|
||||
fSydZdcmIqlkUpfQmtzExw9+tSe5Dxq6gn5JtlGgLgZGt69r5iMMrTEGhhVAXzNu
|
||||
MZbmlCoBru+rC8ITlTX/0V1ZcsSbL8tYWhthyu9x6yjo1bH85wiVI4gs0MhU8f2a
|
||||
+kjL/KGZbR14Ua2eo6tonBZLC5DHWM2TkYXgRCDPufjcgmzN0Lm91E4P8KvBcvly
|
||||
6QIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
`
|
||||
|
||||
func TestQueries_GetPublicKeyByID(t *testing.T) {
|
||||
now := time.Now()
|
||||
future := now.Add(time.Hour)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
encryption func(*testing.T) *crypto.MockEncryptionAlgorithm
|
||||
want *rsaPublicKey
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "filter error",
|
||||
eventstore: expectEventstore(
|
||||
expectFilterError(io.ErrClosedPipe),
|
||||
),
|
||||
wantErr: io.ErrClosedPipe,
|
||||
},
|
||||
{
|
||||
name: "not found error",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
wantErr: zerrors.ThrowNotFound(nil, "QUERY-Ahf7x", "Errors.Key.NotFound"),
|
||||
},
|
||||
{
|
||||
name: "decrypt error",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(key_repo.NewAddedEvent(context.Background(),
|
||||
&eventstore.Aggregate{
|
||||
ID: "keyID",
|
||||
Type: key_repo.AggregateType,
|
||||
ResourceOwner: "instanceID",
|
||||
InstanceID: "instanceID",
|
||||
Version: key_repo.AggregateVersion,
|
||||
},
|
||||
crypto.KeyUsageSigning, "alg",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "alg",
|
||||
KeyID: "keyID",
|
||||
Crypted: []byte("private"),
|
||||
},
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "alg",
|
||||
KeyID: "keyID",
|
||||
Crypted: []byte("public"),
|
||||
},
|
||||
future,
|
||||
future,
|
||||
)),
|
||||
),
|
||||
),
|
||||
encryption: func(t *testing.T) *crypto.MockEncryptionAlgorithm {
|
||||
encryption := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t))
|
||||
expect := encryption.EXPECT()
|
||||
expect.Algorithm().Return("alg")
|
||||
expect.DecryptionKeyIDs().Return([]string{})
|
||||
return encryption
|
||||
},
|
||||
wantErr: zerrors.ThrowInternal(nil, "QUERY-Ie4oh", "Errors.Internal"),
|
||||
},
|
||||
{
|
||||
name: "parse error",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(key_repo.NewAddedEvent(context.Background(),
|
||||
&eventstore.Aggregate{
|
||||
ID: "keyID",
|
||||
Type: key_repo.AggregateType,
|
||||
ResourceOwner: "instanceID",
|
||||
InstanceID: "instanceID",
|
||||
Version: key_repo.AggregateVersion,
|
||||
},
|
||||
crypto.KeyUsageSigning, "alg",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "alg",
|
||||
KeyID: "keyID",
|
||||
Crypted: []byte("private"),
|
||||
},
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "alg",
|
||||
KeyID: "keyID",
|
||||
Crypted: []byte("public"),
|
||||
},
|
||||
future,
|
||||
future,
|
||||
)),
|
||||
),
|
||||
),
|
||||
encryption: func(t *testing.T) *crypto.MockEncryptionAlgorithm {
|
||||
encryption := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t))
|
||||
expect := encryption.EXPECT()
|
||||
expect.Algorithm().Return("alg")
|
||||
expect.DecryptionKeyIDs().Return([]string{"keyID"})
|
||||
expect.Decrypt([]byte("public"), "keyID").Return([]byte("foo"), nil)
|
||||
return encryption
|
||||
},
|
||||
wantErr: zerrors.ThrowInternal(nil, "QUERY-Kai2Z", "Errors.Internal"),
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(key_repo.NewAddedEvent(context.Background(),
|
||||
&eventstore.Aggregate{
|
||||
ID: "keyID",
|
||||
Type: key_repo.AggregateType,
|
||||
ResourceOwner: "instanceID",
|
||||
InstanceID: "instanceID",
|
||||
Version: key_repo.AggregateVersion,
|
||||
},
|
||||
crypto.KeyUsageSigning, "alg",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "alg",
|
||||
KeyID: "keyID",
|
||||
Crypted: []byte("private"),
|
||||
},
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "alg",
|
||||
KeyID: "keyID",
|
||||
Crypted: []byte("public"),
|
||||
},
|
||||
future,
|
||||
future,
|
||||
)),
|
||||
),
|
||||
),
|
||||
encryption: func(t *testing.T) *crypto.MockEncryptionAlgorithm {
|
||||
encryption := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t))
|
||||
expect := encryption.EXPECT()
|
||||
expect.Algorithm().Return("alg")
|
||||
expect.DecryptionKeyIDs().Return([]string{"keyID"})
|
||||
expect.Decrypt([]byte("public"), "keyID").Return([]byte(pubKey), nil)
|
||||
return encryption
|
||||
},
|
||||
want: &rsaPublicKey{
|
||||
key: key{
|
||||
id: "keyID",
|
||||
resourceOwner: "instanceID",
|
||||
algorithm: "alg",
|
||||
use: crypto.KeyUsageSigning,
|
||||
},
|
||||
expiry: future,
|
||||
publicKey: func() *rsa.PublicKey {
|
||||
publicKey, err := crypto.BytesToPublicKey([]byte(pubKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return publicKey
|
||||
}(),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
q := &Queries{
|
||||
eventstore: tt.eventstore(t),
|
||||
}
|
||||
if tt.encryption != nil {
|
||||
q.keyEncryptionAlgorithm = tt.encryption(t)
|
||||
}
|
||||
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||
key, err := q.GetPublicKeyByID(ctx, "keyID")
|
||||
if tt.wantErr != nil {
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key)
|
||||
|
||||
got := key.(*rsaPublicKey)
|
||||
assert.WithinDuration(t, tt.want.expiry, got.expiry, time.Second)
|
||||
tt.want.expiry = time.Time{}
|
||||
got.expiry = time.Time{}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
@@ -35,8 +35,17 @@ func NewMemberLastNameSearchQuery(method TextComparison, value string) (SearchQu
|
||||
}
|
||||
|
||||
func NewMemberUserIDSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipUserID, value, TextEquals)
|
||||
return NewTextQuery(MembershipUserID, value, TextEquals)
|
||||
}
|
||||
|
||||
func NewMemberInUserIDsSearchQuery(ids []string) (SearchQuery, error) {
|
||||
list := make([]interface{}, len(ids))
|
||||
for i, value := range ids {
|
||||
list[i] = value
|
||||
}
|
||||
return NewListQuery(MembershipUserID, list, ListIn)
|
||||
}
|
||||
|
||||
func NewMemberResourceOwnerSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipResourceOwner, value, TextEquals)
|
||||
}
|
||||
|
@@ -128,7 +128,7 @@ func prepareProjectGrantMembersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Memb
|
||||
LeftJoin(join(MachineUserIDCol, ProjectGrantMemberUserID)).
|
||||
LeftJoin(join(UserIDCol, ProjectGrantMemberUserID)).
|
||||
LeftJoin(join(LoginNameUserIDCol, ProjectGrantMemberUserID)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, ProjectGrantMemberGrantID)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, ProjectGrantMemberGrantID) + " AND " + ProjectGrantMemberProjectID.identifier() + " = " + ProjectGrantColumnProjectID.identifier()).
|
||||
Where(
|
||||
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
|
||||
).PlaceholderFormat(sq.Dollar),
|
||||
|
@@ -46,6 +46,7 @@ var (
|
||||
"LEFT JOIN projections.project_grants4 " +
|
||||
"ON members.grant_id = projections.project_grants4.grant_id " +
|
||||
"AND members.instance_id = projections.project_grants4.instance_id " +
|
||||
"AND members.project_id = projections.project_grants4.project_id " +
|
||||
"WHERE projections.login_names3.is_primary = $1")
|
||||
projectGrantMembersColumns = []string{
|
||||
"creation_date",
|
||||
|
144
internal/query/projection/hosted_login_translation.go
Normal file
144
internal/query/projection/hosted_login_translation.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
HostedLoginTranslationTable = "projections.hosted_login_translations"
|
||||
|
||||
HostedLoginTranslationInstanceIDCol = "instance_id"
|
||||
HostedLoginTranslationCreationDateCol = "creation_date"
|
||||
HostedLoginTranslationChangeDateCol = "change_date"
|
||||
HostedLoginTranslationAggregateIDCol = "aggregate_id"
|
||||
HostedLoginTranslationAggregateTypeCol = "aggregate_type"
|
||||
HostedLoginTranslationSequenceCol = "sequence"
|
||||
HostedLoginTranslationLocaleCol = "locale"
|
||||
HostedLoginTranslationFileCol = "file"
|
||||
HostedLoginTranslationEtagCol = "etag"
|
||||
)
|
||||
|
||||
type hostedLoginTranslationProjection struct{}
|
||||
|
||||
func newHostedLoginTranslationProjection(ctx context.Context, config handler.Config) *handler.Handler {
|
||||
return handler.NewHandler(ctx, &config, new(hostedLoginTranslationProjection))
|
||||
}
|
||||
|
||||
// Init implements [handler.initializer]
|
||||
func (p *hostedLoginTranslationProjection) Init() *old_handler.Check {
|
||||
return handler.NewTableCheck(
|
||||
handler.NewTable([]*handler.InitColumn{
|
||||
handler.NewColumn(HostedLoginTranslationInstanceIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(HostedLoginTranslationCreationDateCol, handler.ColumnTypeTimestamp),
|
||||
handler.NewColumn(HostedLoginTranslationChangeDateCol, handler.ColumnTypeTimestamp),
|
||||
handler.NewColumn(HostedLoginTranslationAggregateIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(HostedLoginTranslationAggregateTypeCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(HostedLoginTranslationSequenceCol, handler.ColumnTypeInt64),
|
||||
handler.NewColumn(HostedLoginTranslationLocaleCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(HostedLoginTranslationFileCol, handler.ColumnTypeJSONB),
|
||||
handler.NewColumn(HostedLoginTranslationEtagCol, handler.ColumnTypeText),
|
||||
},
|
||||
handler.NewPrimaryKey(
|
||||
HostedLoginTranslationInstanceIDCol,
|
||||
HostedLoginTranslationAggregateIDCol,
|
||||
HostedLoginTranslationAggregateTypeCol,
|
||||
HostedLoginTranslationLocaleCol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (hltp *hostedLoginTranslationProjection) Name() string {
|
||||
return HostedLoginTranslationTable
|
||||
}
|
||||
|
||||
func (hltp *hostedLoginTranslationProjection) Reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: org.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: org.HostedLoginTranslationSet,
|
||||
Reduce: hltp.reduceSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Aggregate: instance.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.HostedLoginTranslationSet,
|
||||
Reduce: hltp.reduceSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (hltp *hostedLoginTranslationProjection) reduceSet(e eventstore.Event) (*handler.Statement, error) {
|
||||
|
||||
switch e := e.(type) {
|
||||
case *org.HostedLoginTranslationSetEvent:
|
||||
orgEvent := *e
|
||||
return handler.NewUpsertStatement(
|
||||
&orgEvent,
|
||||
[]handler.Column{
|
||||
handler.NewCol(HostedLoginTranslationInstanceIDCol, nil),
|
||||
handler.NewCol(HostedLoginTranslationAggregateIDCol, nil),
|
||||
handler.NewCol(HostedLoginTranslationAggregateTypeCol, nil),
|
||||
handler.NewCol(HostedLoginTranslationLocaleCol, nil),
|
||||
},
|
||||
[]handler.Column{
|
||||
handler.NewCol(HostedLoginTranslationInstanceIDCol, orgEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(HostedLoginTranslationAggregateIDCol, orgEvent.Aggregate().ID),
|
||||
handler.NewCol(HostedLoginTranslationAggregateTypeCol, orgEvent.Aggregate().Type),
|
||||
handler.NewCol(HostedLoginTranslationCreationDateCol, handler.OnlySetValueOnInsert(HostedLoginTranslationTable, orgEvent.CreationDate())),
|
||||
handler.NewCol(HostedLoginTranslationChangeDateCol, orgEvent.CreationDate()),
|
||||
handler.NewCol(HostedLoginTranslationSequenceCol, orgEvent.Sequence()),
|
||||
handler.NewCol(HostedLoginTranslationLocaleCol, orgEvent.Language),
|
||||
handler.NewCol(HostedLoginTranslationFileCol, orgEvent.Translation),
|
||||
handler.NewCol(HostedLoginTranslationEtagCol, hltp.computeEtag(orgEvent.Translation)),
|
||||
},
|
||||
), nil
|
||||
case *instance.HostedLoginTranslationSetEvent:
|
||||
instanceEvent := *e
|
||||
return handler.NewUpsertStatement(
|
||||
&instanceEvent,
|
||||
[]handler.Column{
|
||||
handler.NewCol(HostedLoginTranslationInstanceIDCol, nil),
|
||||
handler.NewCol(HostedLoginTranslationAggregateIDCol, nil),
|
||||
handler.NewCol(HostedLoginTranslationAggregateTypeCol, nil),
|
||||
handler.NewCol(HostedLoginTranslationLocaleCol, nil),
|
||||
},
|
||||
[]handler.Column{
|
||||
handler.NewCol(HostedLoginTranslationInstanceIDCol, instanceEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(HostedLoginTranslationAggregateIDCol, instanceEvent.Aggregate().ID),
|
||||
handler.NewCol(HostedLoginTranslationAggregateTypeCol, instanceEvent.Aggregate().Type),
|
||||
handler.NewCol(HostedLoginTranslationCreationDateCol, handler.OnlySetValueOnInsert(HostedLoginTranslationTable, instanceEvent.CreationDate())),
|
||||
handler.NewCol(HostedLoginTranslationChangeDateCol, instanceEvent.CreationDate()),
|
||||
handler.NewCol(HostedLoginTranslationSequenceCol, instanceEvent.Sequence()),
|
||||
handler.NewCol(HostedLoginTranslationLocaleCol, instanceEvent.Language),
|
||||
handler.NewCol(HostedLoginTranslationFileCol, instanceEvent.Translation),
|
||||
handler.NewCol(HostedLoginTranslationEtagCol, hltp.computeEtag(instanceEvent.Translation)),
|
||||
},
|
||||
), nil
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "PROJE-AZshaa", "reduce.wrong.event.type %v", []eventstore.EventType{org.HostedLoginTranslationSet})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (hltp *hostedLoginTranslationProjection) computeEtag(translation map[string]any) string {
|
||||
hash := md5.Sum(fmt.Append(nil, translation))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
@@ -64,14 +64,6 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.InstanceLoginDefaultOrgEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceTriggerIntrospectionProjectionsEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceLegacyIntrospectionEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceUserSchemaEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
@@ -84,10 +76,6 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.InstanceImprovedPerformanceEventType,
|
||||
Reduce: reduceInstanceSetFeature[[]feature.ImprovedPerformanceType],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceWebKeyEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceDebugOIDCParentErrorEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
|
@@ -26,7 +26,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) {
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
feature_v2.InstanceLegacyIntrospectionEventType,
|
||||
feature_v2.SystemUserSchemaEventType,
|
||||
feature_v2.AggregateType,
|
||||
[]byte(`{"value": true}`),
|
||||
), eventstore.GenericEventMapper[feature_v2.SetEvent[bool]]),
|
||||
@@ -41,7 +41,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) {
|
||||
expectedStmt: "INSERT INTO projections.instance_features2 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features2.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"legacy_introspection",
|
||||
"user_schema",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
|
@@ -88,6 +88,7 @@ var (
|
||||
UserSchemaProjection *handler.Handler
|
||||
WebKeyProjection *handler.Handler
|
||||
DebugEventsProjection *handler.Handler
|
||||
HostedLoginTranslationProjection *handler.Handler
|
||||
|
||||
ProjectGrantFields *handler.FieldHandler
|
||||
OrgDomainVerifiedFields *handler.FieldHandler
|
||||
@@ -183,6 +184,7 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
|
||||
UserSchemaProjection = newUserSchemaProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["user_schemas"]))
|
||||
WebKeyProjection = newWebKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["web_keys"]))
|
||||
DebugEventsProjection = newDebugEventsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_events"]))
|
||||
HostedLoginTranslationProjection = newHostedLoginTranslationProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["hosted_login_translation"]))
|
||||
|
||||
ProjectGrantFields = newFillProjectGrantFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsProjectGrant]))
|
||||
OrgDomainVerifiedFields = newFillOrgDomainVerifiedFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsOrgDomainVerified]))
|
||||
@@ -363,5 +365,6 @@ func newProjectionsList() {
|
||||
UserSchemaProjection,
|
||||
WebKeyProjection,
|
||||
DebugEventsProjection,
|
||||
HostedLoginTranslationProjection,
|
||||
}
|
||||
}
|
||||
|
@@ -56,14 +56,6 @@ func (*systemFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.SystemLoginDefaultOrgEventType,
|
||||
Reduce: reduceSystemSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.SystemTriggerIntrospectionProjectionsEventType,
|
||||
Reduce: reduceSystemSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.SystemLegacyIntrospectionEventType,
|
||||
Reduce: reduceSystemSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.SystemUserSchemaEventType,
|
||||
Reduce: reduceSystemSetFeature[bool],
|
||||
|
@@ -24,7 +24,7 @@ func TestSystemFeaturesProjection_reduces(t *testing.T) {
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
feature_v2.SystemLegacyIntrospectionEventType,
|
||||
feature_v2.SystemUserSchemaEventType,
|
||||
feature_v2.AggregateType,
|
||||
[]byte(`{"value": true}`),
|
||||
), eventstore.GenericEventMapper[feature_v2.SetEvent[bool]]),
|
||||
@@ -38,7 +38,7 @@ func TestSystemFeaturesProjection_reduces(t *testing.T) {
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.system_features (key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.system_features.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)",
|
||||
expectedArgs: []interface{}{
|
||||
"legacy_introspection",
|
||||
"user_schema",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
|
@@ -20,17 +20,15 @@ func (f *FeatureSource[T]) set(level feature.Level, value any) {
|
||||
type SystemFeatures struct {
|
||||
Details *domain.ObjectDetails
|
||||
|
||||
LoginDefaultOrg FeatureSource[bool]
|
||||
TriggerIntrospectionProjections FeatureSource[bool]
|
||||
LegacyIntrospection FeatureSource[bool]
|
||||
UserSchema FeatureSource[bool]
|
||||
TokenExchange FeatureSource[bool]
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
DisableUserTokenEvent FeatureSource[bool]
|
||||
EnableBackChannelLogout FeatureSource[bool]
|
||||
LoginV2 FeatureSource[*feature.LoginV2]
|
||||
PermissionCheckV2 FeatureSource[bool]
|
||||
LoginDefaultOrg FeatureSource[bool]
|
||||
UserSchema FeatureSource[bool]
|
||||
TokenExchange FeatureSource[bool]
|
||||
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
|
||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||
DisableUserTokenEvent FeatureSource[bool]
|
||||
EnableBackChannelLogout FeatureSource[bool]
|
||||
LoginV2 FeatureSource[*feature.LoginV2]
|
||||
PermissionCheckV2 FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
||||
|
@@ -56,8 +56,6 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
EventTypes(
|
||||
feature_v2.SystemResetEventType,
|
||||
feature_v2.SystemLoginDefaultOrgEventType,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType,
|
||||
feature_v2.SystemLegacyIntrospectionEventType,
|
||||
feature_v2.SystemUserSchemaEventType,
|
||||
feature_v2.SystemTokenExchangeEventType,
|
||||
feature_v2.SystemImprovedPerformanceEventType,
|
||||
@@ -81,15 +79,10 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
|
||||
return err
|
||||
}
|
||||
switch key {
|
||||
case feature.KeyUnspecified,
|
||||
feature.KeyActionsDeprecated:
|
||||
case feature.KeyUnspecified:
|
||||
return nil
|
||||
case feature.KeyLoginDefaultOrg:
|
||||
features.LoginDefaultOrg.set(level, event.Value)
|
||||
case feature.KeyTriggerIntrospectionProjections:
|
||||
features.TriggerIntrospectionProjections.set(level, event.Value)
|
||||
case feature.KeyLegacyIntrospection:
|
||||
features.LegacyIntrospection.set(level, event.Value)
|
||||
case feature.KeyUserSchema:
|
||||
features.UserSchema.set(level, event.Value)
|
||||
case feature.KeyTokenExchange:
|
||||
|
@@ -49,14 +49,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
@@ -71,14 +63,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
@@ -93,14 +77,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
@@ -109,10 +85,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemResetEventType,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
),
|
||||
),
|
||||
want: &SystemFeatures{
|
||||
@@ -123,14 +95,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
@@ -145,14 +109,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
@@ -161,10 +117,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemResetEventType,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType, true,
|
||||
)),
|
||||
),
|
||||
),
|
||||
want: &SystemFeatures{
|
||||
@@ -175,14 +127,6 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
TriggerIntrospectionProjections: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
LegacyIntrospection: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
|
@@ -697,6 +697,35 @@ func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwn
|
||||
return isUnique, err
|
||||
}
|
||||
|
||||
//go:embed user_claimed_user_ids.sql
|
||||
var userClaimedUserIDOfOrgDomain string
|
||||
|
||||
func (q *Queries) SearchClaimedUserIDsOfOrgDomain(ctx context.Context, domain, orgID string) (userIDs []string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
err = q.client.QueryContext(ctx,
|
||||
func(rows *sql.Rows) error {
|
||||
userIDs = make([]string, 0)
|
||||
for rows.Next() {
|
||||
var userID string
|
||||
err := rows.Scan(&userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
userClaimedUserIDOfOrgDomain,
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
"%@"+domain,
|
||||
orgID,
|
||||
)
|
||||
|
||||
return userIDs, err
|
||||
}
|
||||
|
||||
func (q *UserSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
query = q.SearchRequest.toQuery(query)
|
||||
for _, q := range q.Queries {
|
||||
|
13
internal/query/user_claimed_user_ids.sql
Normal file
13
internal/query/user_claimed_user_ids.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
SELECT u.id
|
||||
FROM projections.login_names3_users u
|
||||
LEFT JOIN projections.login_names3_policies p_custom
|
||||
ON u.instance_id = p_custom.instance_id
|
||||
AND p_custom.instance_id = $1
|
||||
AND p_custom.resource_owner = u.resource_owner
|
||||
JOIN projections.login_names3_policies p_default
|
||||
ON u.instance_id = p_default.instance_id
|
||||
AND p_default.instance_id = $1 AND p_default.is_default IS TRUE
|
||||
WHERE u.instance_id = $1
|
||||
AND COALESCE(p_custom.must_be_domain, p_default.must_be_domain) = false
|
||||
AND u.user_name_lower like $2
|
||||
AND u.resource_owner <> $3;
|
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -44,8 +45,9 @@ type UserGrant struct {
|
||||
OrgName string `json:"org_name,omitempty"`
|
||||
OrgPrimaryDomain string `json:"org_primary_domain,omitempty"`
|
||||
|
||||
ProjectID string `json:"project_id,omitempty"`
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
ProjectResourceOwner string `json:"project_resource_owner,omitempty"`
|
||||
ProjectID string `json:"project_id,omitempty"`
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
|
||||
GrantedOrgID string `json:"granted_org_id,omitempty"`
|
||||
GrantedOrgName string `json:"granted_org_name,omitempty"`
|
||||
@@ -57,6 +59,27 @@ type UserGrants struct {
|
||||
UserGrants []*UserGrant
|
||||
}
|
||||
|
||||
func userGrantsCheckPermission(ctx context.Context, grants *UserGrants, permissionCheck domain.PermissionCheck) {
|
||||
grants.UserGrants = slices.DeleteFunc(grants.UserGrants,
|
||||
func(grant *UserGrant) bool {
|
||||
return userGrantCheckPermission(ctx, grant.ResourceOwner, grant.ProjectID, grant.GrantID, grant.UserID, permissionCheck) != nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func userGrantCheckPermission(ctx context.Context, resourceOwner, projectID, grantID, userID string, permissionCheck domain.PermissionCheck) error {
|
||||
// you should always be able to read your own permissions
|
||||
if authz.GetCtxData(ctx).UserID == userID {
|
||||
return nil
|
||||
}
|
||||
// check permission on the project grant
|
||||
if grantID != "" {
|
||||
return permissionCheck(ctx, domain.PermissionUserGrantRead, resourceOwner, grantID)
|
||||
}
|
||||
// check on project
|
||||
return permissionCheck(ctx, domain.PermissionUserGrantRead, resourceOwner, projectID)
|
||||
}
|
||||
|
||||
type UserGrantsQueries struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
@@ -70,6 +93,21 @@ func (q *UserGrantsQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
|
||||
return query
|
||||
}
|
||||
|
||||
func userGrantPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *UserGrantsQueries) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
UserGrantResourceOwner,
|
||||
domain.PermissionUserGrantRead,
|
||||
SingleOrgPermissionOption(queries.Queries),
|
||||
WithProjectsPermissionOption(UserGrantProjectID),
|
||||
OwnedRowsPermissionOption(UserGrantUserID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func NewUserGrantUserIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserGrantUserID, id, TextEquals)
|
||||
}
|
||||
@@ -78,14 +116,6 @@ func NewUserGrantProjectIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserGrantProjectID, id, TextEquals)
|
||||
}
|
||||
|
||||
func NewUserGrantProjectIDsSearchQuery(ids []string) (SearchQuery, error) {
|
||||
list := make([]interface{}, len(ids))
|
||||
for i, value := range ids {
|
||||
list[i] = value
|
||||
}
|
||||
return NewListQuery(UserGrantProjectID, list, ListIn)
|
||||
}
|
||||
|
||||
func NewUserGrantProjectOwnerSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(ProjectColumnResourceOwner, id, TextEquals)
|
||||
}
|
||||
@@ -94,6 +124,10 @@ func NewUserGrantResourceOwnerSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserGrantResourceOwner, id, TextEquals)
|
||||
}
|
||||
|
||||
func NewUserGrantUserResourceOwnerSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserResourceOwnerCol, id, TextEquals)
|
||||
}
|
||||
|
||||
func NewUserGrantGrantIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserGrantGrantID, id, TextEquals)
|
||||
}
|
||||
@@ -102,6 +136,14 @@ func NewUserGrantIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(UserGrantID, id, TextEquals)
|
||||
}
|
||||
|
||||
func NewUserGrantInIDsSearchQuery(ids []string) (SearchQuery, error) {
|
||||
list := make([]interface{}, len(ids))
|
||||
for i, value := range ids {
|
||||
list[i] = value
|
||||
}
|
||||
return NewListQuery(UserGrantID, list, ListIn)
|
||||
}
|
||||
|
||||
func NewUserGrantUserTypeQuery(typ domain.UserType) (SearchQuery, error) {
|
||||
return NewNumberQuery(UserTypeCol, typ, NumberEquals)
|
||||
}
|
||||
@@ -262,7 +304,19 @@ func (q *Queries) UserGrant(ctx context.Context, shouldTriggerBulk bool, queries
|
||||
return grant, err
|
||||
}
|
||||
|
||||
func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, shouldTriggerBulk bool) (grants *UserGrants, err error) {
|
||||
func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, shouldTriggerBulk bool, permissionCheck domain.PermissionCheck) (*UserGrants, error) {
|
||||
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
|
||||
grants, err := q.userGrants(ctx, queries, shouldTriggerBulk, permissionCheckV2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
|
||||
userGrantsCheckPermission(ctx, grants, permissionCheck)
|
||||
}
|
||||
return grants, nil
|
||||
}
|
||||
|
||||
func (q *Queries) userGrants(ctx context.Context, queries *UserGrantsQueries, shouldTriggerBulk bool, permissionCheckV2 bool) (grants *UserGrants, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -274,6 +328,7 @@ func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, sh
|
||||
}
|
||||
|
||||
query, scan := prepareUserGrantsQuery()
|
||||
query = userGrantPermissionCheckV2(ctx, query, permissionCheckV2, queries)
|
||||
eq := sq.Eq{UserGrantInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
@@ -324,6 +379,7 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
|
||||
|
||||
UserGrantProjectID.identifier(),
|
||||
ProjectColumnName.identifier(),
|
||||
ProjectColumnResourceOwner.identifier(),
|
||||
|
||||
GrantedOrgColumnId.identifier(),
|
||||
GrantedOrgColumnName.identifier(),
|
||||
@@ -334,7 +390,8 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
|
||||
LeftJoin(join(HumanUserIDCol, UserGrantUserID)).
|
||||
LeftJoin(join(OrgColumnID, UserGrantResourceOwner)).
|
||||
LeftJoin(join(ProjectColumnID, UserGrantProjectID)).
|
||||
LeftJoin(join(GrantedOrgColumnId, UserResourceOwnerCol)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, UserGrantGrantID) + " AND " + ProjectGrantColumnProjectID.identifier() + " = " + UserGrantProjectID.identifier()).
|
||||
LeftJoin(join(GrantedOrgColumnId, ProjectGrantColumnGrantedOrgID)).
|
||||
LeftJoin(join(LoginNameUserIDCol, UserGrantUserID)).
|
||||
Where(
|
||||
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
|
||||
@@ -356,7 +413,8 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
|
||||
orgName sql.NullString
|
||||
orgDomain sql.NullString
|
||||
|
||||
projectName sql.NullString
|
||||
projectName sql.NullString
|
||||
projectResourceOwner sql.NullString
|
||||
|
||||
grantedOrgID sql.NullString
|
||||
grantedOrgName sql.NullString
|
||||
@@ -389,6 +447,7 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
|
||||
|
||||
&g.ProjectID,
|
||||
&projectName,
|
||||
&projectResourceOwner,
|
||||
|
||||
&grantedOrgID,
|
||||
&grantedOrgName,
|
||||
@@ -413,6 +472,7 @@ func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, erro
|
||||
g.OrgName = orgName.String
|
||||
g.OrgPrimaryDomain = orgDomain.String
|
||||
g.ProjectName = projectName.String
|
||||
g.ProjectResourceOwner = projectResourceOwner.String
|
||||
g.GrantedOrgID = grantedOrgID.String
|
||||
g.GrantedOrgName = grantedOrgName.String
|
||||
g.GrantedOrgDomain = grantedOrgDomain.String
|
||||
@@ -447,6 +507,7 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
|
||||
|
||||
UserGrantProjectID.identifier(),
|
||||
ProjectColumnName.identifier(),
|
||||
ProjectColumnResourceOwner.identifier(),
|
||||
|
||||
GrantedOrgColumnId.identifier(),
|
||||
GrantedOrgColumnName.identifier(),
|
||||
@@ -459,7 +520,8 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
|
||||
LeftJoin(join(HumanUserIDCol, UserGrantUserID)).
|
||||
LeftJoin(join(OrgColumnID, UserGrantResourceOwner)).
|
||||
LeftJoin(join(ProjectColumnID, UserGrantProjectID)).
|
||||
LeftJoin(join(GrantedOrgColumnId, UserResourceOwnerCol)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, UserGrantGrantID) + " AND " + ProjectGrantColumnProjectID.identifier() + " = " + UserGrantProjectID.identifier()).
|
||||
LeftJoin(join(GrantedOrgColumnId, ProjectGrantColumnGrantedOrgID)).
|
||||
LeftJoin(join(LoginNameUserIDCol, UserGrantUserID)).
|
||||
Where(
|
||||
sq.Eq{LoginNameIsPrimaryCol.identifier(): true},
|
||||
@@ -488,7 +550,8 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
|
||||
grantedOrgName sql.NullString
|
||||
grantedOrgDomain sql.NullString
|
||||
|
||||
projectName sql.NullString
|
||||
projectName sql.NullString
|
||||
projectResourceOwner sql.NullString
|
||||
)
|
||||
|
||||
err := rows.Scan(
|
||||
@@ -517,6 +580,7 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
|
||||
|
||||
&g.ProjectID,
|
||||
&projectName,
|
||||
&projectResourceOwner,
|
||||
|
||||
&grantedOrgID,
|
||||
&grantedOrgName,
|
||||
@@ -540,6 +604,7 @@ func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, e
|
||||
g.OrgName = orgName.String
|
||||
g.OrgPrimaryDomain = orgDomain.String
|
||||
g.ProjectName = projectName.String
|
||||
g.ProjectResourceOwner = projectResourceOwner.String
|
||||
g.GrantedOrgID = grantedOrgID.String
|
||||
g.GrantedOrgName = grantedOrgName.String
|
||||
g.GrantedOrgDomain = grantedOrgDomain.String
|
||||
|
@@ -37,6 +37,7 @@ var (
|
||||
", projections.orgs1.primary_domain" +
|
||||
", projections.user_grants5.project_id" +
|
||||
", projections.projects4.name" +
|
||||
", projections.projects4.resource_owner" +
|
||||
", granted_orgs.id" +
|
||||
", granted_orgs.name" +
|
||||
", granted_orgs.primary_domain" +
|
||||
@@ -45,7 +46,8 @@ var (
|
||||
" LEFT JOIN projections.users14_humans ON projections.user_grants5.user_id = projections.users14_humans.user_id AND projections.user_grants5.instance_id = projections.users14_humans.instance_id" +
|
||||
" LEFT JOIN projections.orgs1 ON projections.user_grants5.resource_owner = projections.orgs1.id AND projections.user_grants5.instance_id = projections.orgs1.instance_id" +
|
||||
" LEFT JOIN projections.projects4 ON projections.user_grants5.project_id = projections.projects4.id AND projections.user_grants5.instance_id = projections.projects4.instance_id" +
|
||||
" LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users14.resource_owner = granted_orgs.id AND projections.users14.instance_id = granted_orgs.instance_id" +
|
||||
" LEFT JOIN projections.project_grants4 ON projections.user_grants5.grant_id = projections.project_grants4.grant_id AND projections.user_grants5.instance_id = projections.project_grants4.instance_id AND projections.project_grants4.project_id = projections.user_grants5.project_id" +
|
||||
" LEFT JOIN projections.orgs1 AS granted_orgs ON projections.project_grants4.granted_org_id = granted_orgs.id AND projections.project_grants4.instance_id = granted_orgs.instance_id" +
|
||||
" LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" +
|
||||
" WHERE projections.login_names3.is_primary = $1")
|
||||
userGrantCols = []string{
|
||||
@@ -71,6 +73,7 @@ var (
|
||||
"primary_domain",
|
||||
"project_id",
|
||||
"name", // project name
|
||||
"resource_owner", // project_grant resource owner
|
||||
"id", // granted org id
|
||||
"name", // granted org name
|
||||
"primary_domain", // granted org domain
|
||||
@@ -98,6 +101,7 @@ var (
|
||||
", projections.orgs1.primary_domain" +
|
||||
", projections.user_grants5.project_id" +
|
||||
", projections.projects4.name" +
|
||||
", projections.projects4.resource_owner" +
|
||||
", granted_orgs.id" +
|
||||
", granted_orgs.name" +
|
||||
", granted_orgs.primary_domain" +
|
||||
@@ -107,7 +111,8 @@ var (
|
||||
" LEFT JOIN projections.users14_humans ON projections.user_grants5.user_id = projections.users14_humans.user_id AND projections.user_grants5.instance_id = projections.users14_humans.instance_id" +
|
||||
" LEFT JOIN projections.orgs1 ON projections.user_grants5.resource_owner = projections.orgs1.id AND projections.user_grants5.instance_id = projections.orgs1.instance_id" +
|
||||
" LEFT JOIN projections.projects4 ON projections.user_grants5.project_id = projections.projects4.id AND projections.user_grants5.instance_id = projections.projects4.instance_id" +
|
||||
" LEFT JOIN projections.orgs1 AS granted_orgs ON projections.users14.resource_owner = granted_orgs.id AND projections.users14.instance_id = granted_orgs.instance_id" +
|
||||
" LEFT JOIN projections.project_grants4 ON projections.user_grants5.grant_id = projections.project_grants4.grant_id AND projections.user_grants5.instance_id = projections.project_grants4.instance_id AND projections.project_grants4.project_id = projections.user_grants5.project_id" +
|
||||
" LEFT JOIN projections.orgs1 AS granted_orgs ON projections.project_grants4.granted_org_id = granted_orgs.id AND projections.project_grants4.instance_id = granted_orgs.instance_id" +
|
||||
" LEFT JOIN projections.login_names3 ON projections.user_grants5.user_id = projections.login_names3.user_id AND projections.user_grants5.instance_id = projections.login_names3.instance_id" +
|
||||
" WHERE projections.login_names3.is_primary = $1")
|
||||
userGrantsCols = append(
|
||||
@@ -175,6 +180,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -182,31 +188,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
),
|
||||
},
|
||||
object: &UserGrant{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -239,6 +246,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -246,31 +254,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
),
|
||||
},
|
||||
object: &UserGrant{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeMachine,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Email: "",
|
||||
DisplayName: "",
|
||||
AvatarURL: "",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeMachine,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Email: "",
|
||||
DisplayName: "",
|
||||
AvatarURL: "",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -303,6 +312,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
nil,
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -310,31 +320,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
),
|
||||
},
|
||||
object: &UserGrant{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "",
|
||||
OrgPrimaryDomain: "",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "",
|
||||
OrgPrimaryDomain: "",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -367,6 +378,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
nil,
|
||||
nil,
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -374,31 +386,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
),
|
||||
},
|
||||
object: &UserGrant{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "",
|
||||
ProjectResourceOwner: "",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -431,6 +444,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -438,31 +452,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
),
|
||||
},
|
||||
object: &UserGrant{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -525,6 +540,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -538,31 +554,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
},
|
||||
UserGrants: []*UserGrant{
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -598,6 +615,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -611,31 +629,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
},
|
||||
UserGrants: []*UserGrant{
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeMachine,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Email: "",
|
||||
DisplayName: "",
|
||||
AvatarURL: "",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeMachine,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "",
|
||||
LastName: "",
|
||||
Email: "",
|
||||
DisplayName: "",
|
||||
AvatarURL: "",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -671,6 +690,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
nil,
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -684,31 +704,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
},
|
||||
UserGrants: []*UserGrant{
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeMachine,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "",
|
||||
OrgPrimaryDomain: "",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeMachine,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "",
|
||||
OrgPrimaryDomain: "",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -744,6 +765,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
nil,
|
||||
nil,
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -757,31 +779,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
},
|
||||
UserGrants: []*UserGrant{
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "",
|
||||
ProjectResourceOwner: "",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -817,6 +840,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -830,31 +854,32 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
},
|
||||
UserGrants: []*UserGrant{
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -890,6 +915,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -917,6 +943,7 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
"primary-domain",
|
||||
"project-id",
|
||||
"project-name",
|
||||
"project-resource-owner",
|
||||
"granted-org-id",
|
||||
"granted-org-name",
|
||||
"granted-org-domain",
|
||||
@@ -930,58 +957,60 @@ func Test_UserGrantPrepares(t *testing.T) {
|
||||
},
|
||||
UserGrants: []*UserGrant{
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211111,
|
||||
Roles: database.TextArray[string]{"role-key"},
|
||||
GrantID: "grant-id",
|
||||
State: domain.UserGrantStateActive,
|
||||
UserID: "user-id",
|
||||
Username: "username",
|
||||
UserType: domain.UserTypeHuman,
|
||||
UserResourceOwner: "resource-owner",
|
||||
FirstName: "first-name",
|
||||
LastName: "last-name",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
AvatarURL: "avatar-key",
|
||||
PreferredLoginName: "login-name",
|
||||
ResourceOwner: "ro",
|
||||
OrgName: "org-name",
|
||||
OrgPrimaryDomain: "primary-domain",
|
||||
ProjectID: "project-id",
|
||||
ProjectName: "project-name",
|
||||
ProjectResourceOwner: "project-resource-owner",
|
||||
GrantedOrgID: "granted-org-id",
|
||||
GrantedOrgName: "granted-org-name",
|
||||
GrantedOrgDomain: "granted-org-domain",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -63,7 +63,15 @@ type MembershipSearchQuery struct {
|
||||
}
|
||||
|
||||
func NewMembershipUserIDQuery(userID string) (SearchQuery, error) {
|
||||
return NewTextQuery(membershipUserID.setTable(membershipAlias), userID, TextEquals)
|
||||
return NewTextQuery(MembershipUserID.setTable(membershipAlias), userID, TextEquals)
|
||||
}
|
||||
|
||||
func NewMembershipCreationDateQuery(timestamp time.Time, comparison TimestampComparison) (SearchQuery, error) {
|
||||
return NewTimestampQuery(MembershipCreationDate.setTable(membershipAlias), timestamp, comparison)
|
||||
}
|
||||
|
||||
func NewMembershipChangeDateQuery(timestamp time.Time, comparison TimestampComparison) (SearchQuery, error) {
|
||||
return NewTimestampQuery(MembershipChangeDate.setTable(membershipAlias), timestamp, comparison)
|
||||
}
|
||||
|
||||
func NewMembershipOrgIDQuery(value string) (SearchQuery, error) {
|
||||
@@ -137,7 +145,7 @@ func (q *Queries) Memberships(ctx context.Context, queries *MembershipSearchQuer
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
query, queryArgs, scan := prepareMembershipsQuery(queries)
|
||||
query, queryArgs, scan := prepareMembershipsQuery(ctx, queries, false)
|
||||
eq := sq.Eq{membershipInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
@@ -166,7 +174,7 @@ var (
|
||||
name: "members",
|
||||
instanceIDCol: projection.MemberInstanceID,
|
||||
}
|
||||
membershipUserID = Column{
|
||||
MembershipUserID = Column{
|
||||
name: projection.MemberUserIDCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
@@ -174,11 +182,11 @@ var (
|
||||
name: projection.MemberRolesCol,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipCreationDate = Column{
|
||||
MembershipCreationDate = Column{
|
||||
name: projection.MemberCreationDate,
|
||||
table: membershipAlias,
|
||||
}
|
||||
membershipChangeDate = Column{
|
||||
MembershipChangeDate = Column{
|
||||
name: projection.MemberChangeDate,
|
||||
table: membershipAlias,
|
||||
}
|
||||
@@ -216,11 +224,11 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func getMembershipFromQuery(queries *MembershipSearchQuery) (string, []interface{}) {
|
||||
orgMembers, orgMembersArgs := prepareOrgMember(queries)
|
||||
iamMembers, iamMembersArgs := prepareIAMMember(queries)
|
||||
projectMembers, projectMembersArgs := prepareProjectMember(queries)
|
||||
projectGrantMembers, projectGrantMembersArgs := prepareProjectGrantMember(queries)
|
||||
func getMembershipFromQuery(ctx context.Context, queries *MembershipSearchQuery, permissionV2 bool) (string, []interface{}) {
|
||||
orgMembers, orgMembersArgs := prepareOrgMember(ctx, queries, permissionV2)
|
||||
iamMembers, iamMembersArgs := prepareIAMMember(ctx, queries, permissionV2)
|
||||
projectMembers, projectMembersArgs := prepareProjectMember(ctx, queries, permissionV2)
|
||||
projectGrantMembers, projectGrantMembersArgs := prepareProjectGrantMember(ctx, queries, permissionV2)
|
||||
args := make([]interface{}, 0)
|
||||
args = append(append(append(append(args, orgMembersArgs...), iamMembersArgs...), projectMembersArgs...), projectGrantMembersArgs...)
|
||||
|
||||
@@ -236,13 +244,13 @@ func getMembershipFromQuery(queries *MembershipSearchQuery) (string, []interface
|
||||
args
|
||||
}
|
||||
|
||||
func prepareMembershipsQuery(queries *MembershipSearchQuery) (sq.SelectBuilder, []interface{}, func(*sql.Rows) (*Memberships, error)) {
|
||||
query, args := getMembershipFromQuery(queries)
|
||||
func prepareMembershipsQuery(ctx context.Context, queries *MembershipSearchQuery, permissionV2 bool) (sq.SelectBuilder, []interface{}, func(*sql.Rows) (*Memberships, error)) {
|
||||
query, args := getMembershipFromQuery(ctx, queries, permissionV2)
|
||||
return sq.Select(
|
||||
membershipUserID.identifier(),
|
||||
MembershipUserID.identifier(),
|
||||
membershipRoles.identifier(),
|
||||
membershipCreationDate.identifier(),
|
||||
membershipChangeDate.identifier(),
|
||||
MembershipCreationDate.identifier(),
|
||||
MembershipChangeDate.identifier(),
|
||||
membershipSequence.identifier(),
|
||||
membershipResourceOwner.identifier(),
|
||||
membershipOrgID.identifier(),
|
||||
@@ -257,7 +265,7 @@ func prepareMembershipsQuery(queries *MembershipSearchQuery) (sq.SelectBuilder,
|
||||
).From(query).
|
||||
LeftJoin(join(ProjectColumnID, membershipProjectID)).
|
||||
LeftJoin(join(OrgColumnID, membershipOrgID)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, membershipGrantID)).
|
||||
LeftJoin(join(ProjectGrantColumnGrantID, membershipGrantID) + " AND " + membershipProjectID.identifier() + " = " + ProjectGrantColumnProjectID.identifier()).
|
||||
LeftJoin(join(InstanceColumnID, membershipInstanceID)).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
args,
|
||||
@@ -340,7 +348,7 @@ func prepareMembershipsQuery(queries *MembershipSearchQuery) (sq.SelectBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
func prepareOrgMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
func prepareOrgMember(ctx context.Context, query *MembershipSearchQuery, permissionV2 bool) (string, []interface{}) {
|
||||
builder := sq.Select(
|
||||
OrgMemberUserID.identifier(),
|
||||
OrgMemberRoles.identifier(),
|
||||
@@ -354,6 +362,7 @@ func prepareOrgMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
"NULL::TEXT AS "+membershipProjectID.name,
|
||||
"NULL::TEXT AS "+membershipGrantID.name,
|
||||
).From(orgMemberTable.identifier())
|
||||
builder = administratorOrgPermissionCheckV2(ctx, builder, permissionV2)
|
||||
|
||||
for _, q := range query.Queries {
|
||||
if q.Col().table.name == membershipAlias.name || q.Col().table.name == orgMemberTable.name {
|
||||
@@ -363,7 +372,7 @@ func prepareOrgMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
return builder.MustSql()
|
||||
}
|
||||
|
||||
func prepareIAMMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
func prepareIAMMember(ctx context.Context, query *MembershipSearchQuery, permissionV2 bool) (string, []interface{}) {
|
||||
builder := sq.Select(
|
||||
InstanceMemberUserID.identifier(),
|
||||
InstanceMemberRoles.identifier(),
|
||||
@@ -377,6 +386,7 @@ func prepareIAMMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
"NULL::TEXT AS "+membershipProjectID.name,
|
||||
"NULL::TEXT AS "+membershipGrantID.name,
|
||||
).From(instanceMemberTable.identifier())
|
||||
builder = administratorInstancePermissionCheckV2(ctx, builder, permissionV2)
|
||||
|
||||
for _, q := range query.Queries {
|
||||
if q.Col().table.name == membershipAlias.name || q.Col().table.name == instanceMemberTable.name {
|
||||
@@ -386,7 +396,7 @@ func prepareIAMMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
return builder.MustSql()
|
||||
}
|
||||
|
||||
func prepareProjectMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
func prepareProjectMember(ctx context.Context, query *MembershipSearchQuery, permissionV2 bool) (string, []interface{}) {
|
||||
builder := sq.Select(
|
||||
ProjectMemberUserID.identifier(),
|
||||
ProjectMemberRoles.identifier(),
|
||||
@@ -400,6 +410,7 @@ func prepareProjectMember(query *MembershipSearchQuery) (string, []interface{})
|
||||
ProjectMemberProjectID.identifier(),
|
||||
"NULL::TEXT AS "+membershipGrantID.name,
|
||||
).From(projectMemberTable.identifier())
|
||||
builder = administratorProjectPermissionCheckV2(ctx, builder, permissionV2)
|
||||
|
||||
for _, q := range query.Queries {
|
||||
if q.Col().table.name == membershipAlias.name || q.Col().table.name == projectMemberTable.name {
|
||||
@@ -410,7 +421,7 @@ func prepareProjectMember(query *MembershipSearchQuery) (string, []interface{})
|
||||
return builder.MustSql()
|
||||
}
|
||||
|
||||
func prepareProjectGrantMember(query *MembershipSearchQuery) (string, []interface{}) {
|
||||
func prepareProjectGrantMember(ctx context.Context, query *MembershipSearchQuery, permissionV2 bool) (string, []interface{}) {
|
||||
builder := sq.Select(
|
||||
ProjectGrantMemberUserID.identifier(),
|
||||
ProjectGrantMemberRoles.identifier(),
|
||||
@@ -424,6 +435,7 @@ func prepareProjectGrantMember(query *MembershipSearchQuery) (string, []interfac
|
||||
ProjectGrantMemberProjectID.identifier(),
|
||||
ProjectGrantMemberGrantID.identifier(),
|
||||
).From(projectGrantMemberTable.identifier())
|
||||
builder = administratorProjectGrantPermissionCheckV2(ctx, builder, permissionV2)
|
||||
|
||||
for _, q := range query.Queries {
|
||||
if q.Col().table.name == membershipAlias.name || q.Col().table.name == projectMemberTable.name || q.Col().table.name == projectGrantMemberTable.name {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
@@ -85,7 +86,7 @@ var (
|
||||
") AS members" +
|
||||
" LEFT JOIN projections.projects4 ON members.project_id = projections.projects4.id AND members.instance_id = projections.projects4.instance_id" +
|
||||
" LEFT JOIN projections.orgs1 ON members.org_id = projections.orgs1.id AND members.instance_id = projections.orgs1.instance_id" +
|
||||
" LEFT JOIN projections.project_grants4 ON members.grant_id = projections.project_grants4.grant_id AND members.instance_id = projections.project_grants4.instance_id" +
|
||||
" LEFT JOIN projections.project_grants4 ON members.grant_id = projections.project_grants4.grant_id AND members.instance_id = projections.project_grants4.instance_id AND members.project_id = projections.project_grants4.project_id" +
|
||||
" LEFT JOIN projections.instances ON members.instance_id = projections.instances.id")
|
||||
membershipCols = []string{
|
||||
"user_id",
|
||||
@@ -461,7 +462,7 @@ func Test_MembershipPrepares(t *testing.T) {
|
||||
|
||||
func prepareMembershipWrapper() func() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, error)) {
|
||||
return func() (sq.SelectBuilder, func(*sql.Rows) (*Memberships, error)) {
|
||||
builder, _, fun := prepareMembershipsQuery(&MembershipSearchQuery{})
|
||||
builder, _, fun := prepareMembershipsQuery(context.Background(), &MembershipSearchQuery{}, false)
|
||||
return builder, fun
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,14 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
@@ -36,6 +38,28 @@ type UserMetadataSearchQueries struct {
|
||||
Queries []SearchQuery
|
||||
}
|
||||
|
||||
func userMetadataCheckPermission(ctx context.Context, userMetadataList *UserMetadataList, permissionCheck domain.PermissionCheck) {
|
||||
userMetadataList.Metadata = slices.DeleteFunc(userMetadataList.Metadata,
|
||||
func(userMetadata *UserMetadata) bool {
|
||||
return userCheckPermission(ctx, userMetadata.ResourceOwner, userMetadata.UserID, permissionCheck) != nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func userMetadataPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *UserMetadataSearchQueries) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
UserMetadataResourceOwnerCol,
|
||||
domain.PermissionUserRead,
|
||||
SingleOrgPermissionOption(queries.Queries),
|
||||
OwnedRowsPermissionOption(UserMetadataUserIDCol),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
var (
|
||||
userMetadataTable = table{
|
||||
name: projection.UserMetadataProjectionTable,
|
||||
@@ -139,7 +163,19 @@ func (q *Queries) SearchUserMetadataForUsers(ctx context.Context, shouldTriggerB
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchUserMetadata(ctx context.Context, shouldTriggerBulk bool, userID string, queries *UserMetadataSearchQueries, withOwnerRemoved bool) (metadata *UserMetadataList, err error) {
|
||||
func (q *Queries) SearchUserMetadata(ctx context.Context, shouldTriggerBulk bool, userID string, queries *UserMetadataSearchQueries, permissionCheck domain.PermissionCheck) (metadata *UserMetadataList, err error) {
|
||||
permissionCheckV2 := PermissionV2(ctx, permissionCheck)
|
||||
users, err := q.searchUserMetadata(ctx, shouldTriggerBulk, userID, queries, permissionCheckV2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
|
||||
userMetadataCheckPermission(ctx, users, permissionCheck)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (q *Queries) searchUserMetadata(ctx context.Context, shouldTriggerBulk bool, userID string, queries *UserMetadataSearchQueries, permissionCheckV2 bool) (metadata *UserMetadataList, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -151,6 +187,7 @@ func (q *Queries) SearchUserMetadata(ctx context.Context, shouldTriggerBulk bool
|
||||
}
|
||||
|
||||
query, scan := prepareUserMetadataListQuery()
|
||||
query = userMetadataPermissionCheckV2(ctx, query, permissionCheckV2, queries)
|
||||
eq := sq.Eq{
|
||||
UserMetadataUserIDCol.identifier(): userID,
|
||||
UserMetadataInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
|
@@ -31,12 +31,6 @@ var oidcUserInfoTriggerHandlers = sync.OnceValue(func() []*handler.Handler {
|
||||
}
|
||||
})
|
||||
|
||||
// TriggerOIDCUserInfoProjections triggers all projections
|
||||
// relevant to userinfo queries concurrently.
|
||||
func TriggerOIDCUserInfoProjections(ctx context.Context) {
|
||||
triggerBatch(ctx, oidcUserInfoTriggerHandlers()...)
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed userinfo_by_id.sql
|
||||
oidcUserInfoQueryTmpl string
|
||||
|
1779
internal/query/v2-default.json
Normal file
1779
internal/query/v2-default.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user