feat(queries): user grants (#2838)

* refactor(domain): add user type

* fix(projections): start with login names

* fix(login_policy): correct handling of user domain claimed event

* fix(projections): add members

* refactor: simplify member projections

* add migration for members

* add metadata to member projections

* refactor: login name projection

* fix: set correct suffixes on login name projections

* test(projections): login name reduces

* fix: correct cols in reduce member

* test(projections): org, iam, project members

* member additional cols and conds as opt,
add project grant members

* fix(migration): members

* fix(migration): correct database name

* migration version

* migs

* better naming for member cond and col

* split project and project grant members

* prepare member columns

* feat(queries): membership query

* test(queries): membership prepare

* fix(queries): multiple projections for latest sequence

* fix(api): use query for membership queries in auth and management

* feat: org member queries

* fix(api): use query for iam member calls

* fix(queries): org members

* fix(queries): project members

* fix(queries): project grant members

* fix(query): member queries and user avatar column

* member cols

* fix(queries): membership stmt

* fix user test

* fix user test

* fix(projections): add user grant projection

* fix(user_grant): handle state changes

* add state to migration

* fix(management): use query for user grant requests

* merge eventstore-naming into user-grant-projection

* feat(queries): user grants

* fix(migrations): version

* fix(api): user query for user grants

* fix(query): event mappers for usergrant aggregate

* fix(projection): correct aggregate for user grants

* fix(queries): user grant roles as list contains

* cleanup reducers

* fix avater_key to avatar_key

* tests

* cleanup

* cleanup

* add resourceowner query

* fix: user grant project name search query

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Silvan
2022-01-14 10:45:50 +01:00
committed by GitHub
parent a63a995269
commit c542cab4f8
29 changed files with 1546 additions and 816 deletions

View File

@@ -25,7 +25,7 @@ var (
", zitadel.projections.users_humans.last_name" +
", zitadel.projections.users_humans.display_name" +
", zitadel.projections.users_machines.name" +
", zitadel.projections.users_humans.avater_key" +
", zitadel.projections.users_humans.avatar_key" +
", COUNT(*) OVER () " +
"FROM zitadel.projections.iam_members as members " +
"LEFT JOIN zitadel.projections.users_humans " +
@@ -48,7 +48,7 @@ var (
"last_name",
"display_name",
"name",
"avater_key",
"avatar_key",
"count",
}
)

View File

@@ -25,7 +25,7 @@ var (
", zitadel.projections.users_humans.last_name" +
", zitadel.projections.users_humans.display_name" +
", zitadel.projections.users_machines.name" +
", zitadel.projections.users_humans.avater_key" +
", zitadel.projections.users_humans.avatar_key" +
", COUNT(*) OVER () " +
"FROM zitadel.projections.org_members as members " +
"LEFT JOIN zitadel.projections.users_humans " +
@@ -48,7 +48,7 @@ var (
"last_name",
"display_name",
"name",
"avater_key",
"avatar_key",
"count",
}
)

View File

@@ -25,7 +25,7 @@ var (
", zitadel.projections.users_humans.last_name" +
", zitadel.projections.users_humans.display_name" +
", zitadel.projections.users_machines.name" +
", zitadel.projections.users_humans.avater_key" +
", zitadel.projections.users_humans.avatar_key" +
", COUNT(*) OVER () " +
"FROM zitadel.projections.project_grant_members as members " +
"LEFT JOIN zitadel.projections.users_humans " +
@@ -48,7 +48,7 @@ var (
"last_name",
"display_name",
"name",
"avater_key",
"avatar_key",
"count",
}
)

View File

@@ -25,7 +25,7 @@ var (
", zitadel.projections.users_humans.last_name" +
", zitadel.projections.users_humans.display_name" +
", zitadel.projections.users_machines.name" +
", zitadel.projections.users_humans.avater_key" +
", zitadel.projections.users_humans.avatar_key" +
", COUNT(*) OVER () " +
"FROM zitadel.projections.project_members as members " +
"LEFT JOIN zitadel.projections.users_humans " +
@@ -48,7 +48,7 @@ var (
"last_name",
"display_name",
"name",
"avater_key",
"avatar_key",
"count",
}
)

View File

@@ -40,6 +40,7 @@ const (
UserStateCol = "state"
UserSequenceCol = "sequence"
UserUsernameCol = "username"
UserTypeCol = "type"
)
const (
@@ -53,7 +54,7 @@ const (
HumanDisplayNameCol = "display_name"
HumanPreferredLanguageCol = "preferred_language"
HumanGenderCol = "gender"
HumanAvaterURLCol = "avater_key"
HumanAvaterURLCol = "avatar_key"
// email
HumanEmailCol = "email"
@@ -203,6 +204,7 @@ func (p *UserProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat
handler.NewCol(UserStateCol, domain.UserStateInitial),
handler.NewCol(UserSequenceCol, e.Sequence()),
handler.NewCol(UserUsernameCol, e.UserName),
handler.NewCol(UserTypeCol, domain.UserTypeHuman),
},
),
crdb.AddCreateStatement(
@@ -239,6 +241,7 @@ func (p *UserProjection) reduceHumanRegistered(event eventstore.Event) (*handler
handler.NewCol(UserStateCol, domain.UserStateInitial),
handler.NewCol(UserSequenceCol, e.Sequence()),
handler.NewCol(UserUsernameCol, e.UserName),
handler.NewCol(UserTypeCol, domain.UserTypeHuman),
},
),
crdb.AddCreateStatement(
@@ -656,6 +659,7 @@ func (p *UserProjection) reduceMachineAdded(event eventstore.Event) (*handler.St
handler.NewCol(UserStateCol, domain.UserStateInitial),
handler.NewCol(UserSequenceCol, e.Sequence()),
handler.NewCol(UserUsernameCol, e.UserName),
handler.NewCol(UserTypeCol, domain.UserTypeMachine),
},
),
crdb.AddCreateStatement(

View File

@@ -50,7 +50,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -59,6 +59,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"user-name",
domain.UserTypeHuman,
},
},
{
@@ -107,7 +108,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -116,6 +117,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"user-name",
domain.UserTypeHuman,
},
},
{
@@ -159,7 +161,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -168,6 +170,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"user-name",
domain.UserTypeHuman,
},
},
{
@@ -216,7 +219,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -225,6 +228,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"user-name",
domain.UserTypeHuman,
},
},
{
@@ -273,7 +277,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -282,6 +286,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"user-name",
domain.UserTypeHuman,
},
},
{
@@ -325,7 +330,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -334,6 +339,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"user-name",
domain.UserTypeHuman,
},
},
{
@@ -1031,7 +1037,7 @@ func TestUserProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE zitadel.projections.users_humans SET (avater_key) = ($1) WHERE (user_id = $2)",
expectedStmt: "UPDATE zitadel.projections.users_humans SET (avatar_key) = ($1) WHERE (user_id = $2)",
expectedArgs: []interface{}{
"users/agg-id/avatar",
"agg-id",
@@ -1067,7 +1073,7 @@ func TestUserProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE zitadel.projections.users_humans SET (avater_key) = ($1) WHERE (user_id = $2)",
expectedStmt: "UPDATE zitadel.projections.users_humans SET (avatar_key) = ($1) WHERE (user_id = $2)",
expectedArgs: []interface{}{
nil,
"agg-id",
@@ -1098,7 +1104,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -1107,6 +1113,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"username",
domain.UserTypeMachine,
},
},
{
@@ -1143,7 +1150,7 @@ func TestUserProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedStmt: "INSERT INTO zitadel.projections.users (id, creation_date, change_date, resource_owner, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -1152,6 +1159,7 @@ func TestUserProjection_reduces(t *testing.T) {
domain.UserStateInitial,
uint64(15),
"username",
domain.UserTypeMachine,
},
},
{

View File

@@ -44,6 +44,7 @@ const sqlPlaceholder = "?"
type SearchQuery interface {
toQuery(sq.SelectBuilder) sq.SelectBuilder
comp() sq.Sqlizer
}
type NotNullQuery struct {
@@ -60,7 +61,34 @@ func NewNotNullQuery(col Column) (*NotNullQuery, error) {
}
func (q *NotNullQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query.Where(sq.NotEq{q.Column.identifier(): nil})
return query.Where(q.comp())
}
func (q *NotNullQuery) comp() sq.Sqlizer {
return sq.NotEq{q.Column.identifier(): nil}
}
type orQuery struct {
queries []SearchQuery
}
func newOrQuery(queries ...SearchQuery) (*orQuery, error) {
if len(queries) == 0 {
return nil, ErrMissingColumn
}
return &orQuery{queries: queries}, nil
}
func (q *orQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query.Where(q.comp())
}
func (q *orQuery) comp() sq.Sqlizer {
or := make(sq.Or, len(q.queries))
for i, query := range q.queries {
or[i] = query.comp()
}
return or
}
type TextQuery struct {
@@ -90,32 +118,31 @@ func NewTextQuery(col Column, value string, compare TextComparison) (*TextQuery,
}
func (q *TextQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
where, args := q.comp()
return query.Where(where, args...)
return query.Where(q.comp())
}
func (s *TextQuery) comp() (comparison interface{}, args []interface{}) {
func (s *TextQuery) comp() sq.Sqlizer {
switch s.Compare {
case TextEquals:
return sq.Eq{s.Column.identifier(): s.Text}, nil
return sq.Eq{s.Column.identifier(): s.Text}
case TextEqualsIgnoreCase:
return sq.ILike{s.Column.identifier(): s.Text}, nil
return sq.ILike{s.Column.identifier(): s.Text}
case TextStartsWith:
return sq.Like{s.Column.identifier(): s.Text + "%"}, nil
return sq.Like{s.Column.identifier(): s.Text + "%"}
case TextStartsWithIgnoreCase:
return sq.ILike{s.Column.identifier(): s.Text + "%"}, nil
return sq.ILike{s.Column.identifier(): s.Text + "%"}
case TextEndsWith:
return sq.Like{s.Column.identifier(): "%" + s.Text}, nil
return sq.Like{s.Column.identifier(): "%" + s.Text}
case TextEndsWithIgnoreCase:
return sq.ILike{s.Column.identifier(): "%" + s.Text}, nil
return sq.ILike{s.Column.identifier(): "%" + s.Text}
case TextContains:
return sq.Like{s.Column.identifier(): "%" + s.Text + "%"}, nil
return sq.Like{s.Column.identifier(): "%" + s.Text + "%"}
case TextContainsIgnoreCase:
return sq.ILike{s.Column.identifier(): "%" + s.Text + "%"}, nil
return sq.ILike{s.Column.identifier(): "%" + s.Text + "%"}
case TextListContains:
return s.Column.identifier() + " @> ? ", []interface{}{pq.StringArray{s.Text}}
return &listContains{col: s.Column, args: []interface{}{pq.StringArray{s.Text}}}
}
return nil, nil
return nil
}
type TextComparison int
@@ -187,24 +214,23 @@ func NewNumberQuery(c Column, value interface{}, compare NumberComparison) (*Num
}
func (q *NumberQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
where, args := q.comp()
return query.Where(where, args...)
return query.Where(q.comp())
}
func (s *NumberQuery) comp() (comparison interface{}, args []interface{}) {
func (s *NumberQuery) comp() sq.Sqlizer {
switch s.Compare {
case NumberEquals:
return sq.Eq{s.Column.identifier(): s.Number}, nil
return sq.Eq{s.Column.identifier(): s.Number}
case NumberNotEquals:
return sq.NotEq{s.Column.identifier(): s.Number}, nil
return sq.NotEq{s.Column.identifier(): s.Number}
case NumberLess:
return sq.Lt{s.Column.identifier(): s.Number}, nil
return sq.Lt{s.Column.identifier(): s.Number}
case NumberGreater:
return sq.Gt{s.Column.identifier(): s.Number}, nil
return sq.Gt{s.Column.identifier(): s.Number}
case NumberListContains:
return s.Column.identifier() + " @> ? ", []interface{}{pq.Array(s.Number)}
return &listContains{col: s.Column, args: []interface{}{pq.GenericArray{s.Number}}}
}
return nil, nil
return nil
}
type NumberComparison int
@@ -258,16 +284,15 @@ func NewListQuery(column Column, value []interface{}, compare ListComparison) (*
}
func (q *ListQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
where, args := q.comp()
return query.Where(where, args...)
return query.Where(q.comp())
}
func (s *ListQuery) comp() (interface{}, []interface{}) {
func (s *ListQuery) comp() sq.Sqlizer {
switch s.Compare {
case ListIn:
return sq.Eq{s.Column.identifier(): s.List}, nil
return sq.Eq{s.Column.identifier(): s.List}
}
return nil, nil
return nil
}
type ListComparison int
@@ -300,12 +325,11 @@ func NewBoolQuery(c Column, value bool) (*BoolQuery, error) {
}
func (q *BoolQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
where, args := q.comp()
return query.Where(where, args...)
return query.Where(q.comp())
}
func (s *BoolQuery) comp() (comparison interface{}, args []interface{}) {
return sq.Eq{s.Column.identifier(): s.Value}, nil
func (s *BoolQuery) comp() sq.Sqlizer {
return sq.Eq{s.Column.identifier(): s.Value}
}
var (
@@ -367,3 +391,12 @@ func (c Column) isZero() bool {
func join(join, from Column) string {
return join.table.identifier() + " ON " + from.identifier() + " = " + join.identifier()
}
type listContains struct {
col Column
args []interface{}
}
func (q *listContains) ToSql() (string, []interface{}, error) {
return q.col.identifier() + " @> ? ", q.args, nil
}

View File

@@ -230,7 +230,6 @@ func TestTextQuery_comp(t *testing.T) {
}
type want struct {
query interface{}
args []interface{}
isNil bool
}
tests := []struct {
@@ -247,7 +246,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.Eq{"test_table.test_col": "Hurst"},
args: nil,
},
},
{
@@ -259,7 +257,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.ILike{"test_table.test_col": "Hurst"},
args: nil,
},
},
{
@@ -271,7 +268,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.Like{"test_table.test_col": "Hurst%"},
args: nil,
},
},
{
@@ -283,7 +279,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.ILike{"test_table.test_col": "Hurst%"},
args: nil,
},
},
{
@@ -295,7 +290,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.Like{"test_table.test_col": "%Hurst"},
args: nil,
},
},
{
@@ -307,7 +301,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.ILike{"test_table.test_col": "%Hurst"},
args: nil,
},
},
{
@@ -319,7 +312,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.Like{"test_table.test_col": "%Hurst%"},
args: nil,
},
},
{
@@ -331,7 +323,6 @@ func TestTextQuery_comp(t *testing.T) {
},
want: want{
query: sq.ILike{"test_table.test_col": "%Hurst%"},
args: nil,
},
},
{
@@ -342,8 +333,10 @@ func TestTextQuery_comp(t *testing.T) {
Compare: TextListContains,
},
want: want{
query: "test_table.test_col @> ? ",
args: []interface{}{pq.StringArray{"Hurst"}},
query: &listContains{
col: testCol,
args: []interface{}{pq.StringArray{"Hurst"}},
},
},
},
{
@@ -376,7 +369,7 @@ func TestTextQuery_comp(t *testing.T) {
Text: tt.fields.Text,
Compare: tt.fields.Compare,
}
query, args := s.comp()
query := s.comp()
if query == nil && tt.want.isNil {
return
} else if tt.want.isNil && query != nil {
@@ -386,10 +379,6 @@ func TestTextQuery_comp(t *testing.T) {
if !reflect.DeepEqual(query, tt.want.query) {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
}
if !reflect.DeepEqual(args, tt.want.args) {
t.Errorf("wrong args: want: %v, (%T), got: %v (%T)", tt.want.args, tt.want.args, args, args)
}
})
}
}
@@ -589,7 +578,6 @@ func TestNumberQuery_comp(t *testing.T) {
}
type want struct {
query interface{}
args []interface{}
isNil bool
}
tests := []struct {
@@ -606,7 +594,6 @@ func TestNumberQuery_comp(t *testing.T) {
},
want: want{
query: sq.Eq{"test_table.test_col": 42},
args: nil,
},
},
{
@@ -618,7 +605,6 @@ func TestNumberQuery_comp(t *testing.T) {
},
want: want{
query: sq.NotEq{"test_table.test_col": 42},
args: nil,
},
},
{
@@ -630,7 +616,6 @@ func TestNumberQuery_comp(t *testing.T) {
},
want: want{
query: sq.Lt{"test_table.test_col": 42},
args: nil,
},
},
{
@@ -642,7 +627,6 @@ func TestNumberQuery_comp(t *testing.T) {
},
want: want{
query: sq.Gt{"test_table.test_col": 42},
args: nil,
},
},
{
@@ -653,8 +637,10 @@ func TestNumberQuery_comp(t *testing.T) {
Compare: NumberListContains,
},
want: want{
query: "test_table.test_col @> ? ",
args: []interface{}{pq.Array(42)},
query: &listContains{
col: testCol,
args: []interface{}{pq.GenericArray{42}},
},
},
},
{
@@ -687,7 +673,7 @@ func TestNumberQuery_comp(t *testing.T) {
Number: tt.fields.Number,
Compare: tt.fields.Compare,
}
query, args := s.comp()
query := s.comp()
if query == nil && tt.want.isNil {
return
} else if tt.want.isNil && query != nil {
@@ -697,10 +683,6 @@ func TestNumberQuery_comp(t *testing.T) {
if !reflect.DeepEqual(query, tt.want.query) {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
}
if !reflect.DeepEqual(args, tt.want.args) {
t.Errorf("wrong args: want: %v, (%T), got: %v (%T)", tt.want.args, tt.want.args, args, args)
}
})
}
}

View File

@@ -39,6 +39,10 @@ var (
name: projection.UserUsernameCol,
table: userTable,
}
UserTypeCol = Column{
name: projection.UserTypeCol,
table: userTable,
}
)
var (

View File

@@ -0,0 +1,449 @@
package query
import (
"context"
"database/sql"
errs "errors"
"time"
sq "github.com/Masterminds/squirrel"
"github.com/lib/pq"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query/projection"
)
type UserGrant struct {
ID string
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
Roles []string
GrantID string
State domain.UserGrantState
UserID string
Username string
UserType domain.UserType
UserResourceOwner string
FirstName string
LastName string
Email string
DisplayName string
AvatarURL string
ResourceOwner string
OrgName string
OrgPrimaryDomain string
ProjectID string
ProjectName string
}
type UserGrants struct {
SearchResponse
UserGrants []*UserGrant
}
type UserGrantsQueries struct {
SearchRequest
Queries []SearchQuery
}
func (q *UserGrantsQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
query = q.SearchRequest.toQuery(query)
for _, q := range q.Queries {
query = q.toQuery(query)
}
return query
}
func NewUserGrantUserIDSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(UserGrantUserID, id, TextEquals)
}
func NewUserGrantProjectIDSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(UserGrantProjectID, id, TextEquals)
}
func NewUserGrantProjectOwnerSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(ProjectColumnResourceOwner, id, TextEquals)
}
func NewUserGrantResourceOwnerSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(UserGrantResourceOwner, id, TextEquals)
}
func NewUserGrantGrantIDSearchQuery(id string) (SearchQuery, error) {
return NewTextQuery(UserGrantGrantID, id, TextEquals)
}
func NewUserGrantUserTypeQuery(typ domain.UserType) (SearchQuery, error) {
return NewNumberQuery(UserTypeCol, typ, NumberEquals)
}
func NewUserGrantDisplayNameQuery(displayName string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(HumanDisplayNameCol, displayName, method)
}
func NewUserGrantEmailQuery(email string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(HumanEmailCol, email, method)
}
func NewUserGrantFirstNameQuery(value string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(HumanFirstNameCol, value, method)
}
func NewUserGrantLastNameQuery(value string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(HumanLastNameCol, value, method)
}
func NewUserGrantUsernameQuery(value string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(UserUsernameCol, value, method)
}
func NewUserGrantDomainQuery(value string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(OrgColumnDomain, value, method)
}
func NewUserGrantOrgNameQuery(value string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(OrgColumnName, value, method)
}
func NewUserGrantProjectNameQuery(value string, method TextComparison) (SearchQuery, error) {
return NewTextQuery(ProjectColumnName, value, method)
}
func NewUserGrantRoleQuery(value string) (SearchQuery, error) {
return NewTextQuery(UserGrantRoles, value, TextListContains)
}
func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) {
orgQuery, err := NewUserGrantResourceOwnerSearchQuery(owner)
if err != nil {
return nil, err
}
projectQuery, err := NewUserGrantProjectOwnerSearchQuery(owner)
if err != nil {
return nil, err
}
return newOrQuery(orgQuery, projectQuery)
}
func NewUserGrantContainsRolesSearchQuery(roles ...string) (SearchQuery, error) {
r := make([]interface{}, len(roles))
for i, role := range roles {
r[i] = role
}
return NewListQuery(UserGrantRoles, r, ListIn)
}
var (
userGrantTable = table{
name: projection.UserGrantProjectionTable,
}
UserGrantID = Column{
name: projection.UserGrantID,
table: userGrantTable,
}
UserGrantResourceOwner = Column{
name: projection.UserGrantResourceOwner,
table: userGrantTable,
}
UserGrantCreationDate = Column{
name: projection.UserGrantCreationDate,
table: userGrantTable,
}
UserGrantChangeDate = Column{
name: projection.UserGrantChangeDate,
table: userGrantTable,
}
UserGrantSequence = Column{
name: projection.UserGrantSequence,
table: userGrantTable,
}
UserGrantUserID = Column{
name: projection.UserGrantUserID,
table: userGrantTable,
}
UserGrantProjectID = Column{
name: projection.UserGrantProjectID,
table: userGrantTable,
}
UserGrantGrantID = Column{
name: projection.UserGrantGrantID,
table: userGrantTable,
}
UserGrantRoles = Column{
name: projection.UserGrantRoles,
table: userGrantTable,
}
UserGrantState = Column{
name: projection.UserGrantState,
table: userGrantTable,
}
)
func (q *Queries) UserGrantByID(ctx context.Context, id string, queries ...SearchQuery) (*UserGrant, error) {
query, scan := prepareUserGrantQuery()
for _, q := range queries {
query = q.toQuery(query)
}
stmt, args, err := query.Where(sq.Eq{
UserGrantID.identifier(): id,
}).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-Fa1KW", "Errors.Query.SQLStatement")
}
row := q.client.QueryRowContext(ctx, stmt, args...)
return scan(row)
}
func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries) (*UserGrants, error) {
query, scan := prepareUserGrantsQuery()
stmt, args, err := queries.toQuery(query).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-wXnQR", "Errors.Query.SQLStatement")
}
latestSequence, err := q.latestSequence(ctx, userGrantTable)
if err != nil {
return nil, err
}
rows, err := q.client.QueryContext(ctx, stmt, args...)
if err != nil {
return nil, err
}
grants, err := scan(rows)
if err != nil {
return nil, err
}
grants.LatestSequence = latestSequence
return grants, nil
}
func prepareUserGrantQuery() (sq.SelectBuilder, func(*sql.Row) (*UserGrant, error)) {
return sq.Select(
UserGrantID.identifier(),
UserGrantCreationDate.identifier(),
UserGrantChangeDate.identifier(),
UserGrantSequence.identifier(),
UserGrantGrantID.identifier(),
UserGrantRoles.identifier(),
UserGrantState.identifier(),
UserGrantUserID.identifier(),
UserUsernameCol.identifier(),
UserTypeCol.identifier(),
UserResourceOwnerCol.identifier(),
HumanFirstNameCol.identifier(),
HumanLastNameCol.identifier(),
HumanEmailCol.identifier(),
HumanDisplayNameCol.identifier(),
HumanAvaterURLCol.identifier(),
UserGrantResourceOwner.identifier(),
OrgColumnName.identifier(),
OrgColumnDomain.identifier(),
UserGrantProjectID.identifier(),
ProjectColumnName.identifier(),
).
From(userGrantTable.identifier()).
LeftJoin(join(UserIDCol, UserGrantUserID)).
LeftJoin(join(HumanUserIDCol, UserGrantUserID)).
LeftJoin(join(OrgColumnID, UserGrantResourceOwner)).
LeftJoin(join(ProjectColumnID, UserGrantProjectID)).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*UserGrant, error) {
g := new(UserGrant)
var (
roles = pq.StringArray{}
username sql.NullString
firstName sql.NullString
userType sql.NullInt32
userOwner sql.NullString
lastName sql.NullString
email sql.NullString
displayName sql.NullString
avatarURL sql.NullString
orgName sql.NullString
orgDomain sql.NullString
projectName sql.NullString
)
err := row.Scan(
&g.ID,
&g.CreationDate,
&g.ChangeDate,
&g.Sequence,
&g.GrantID,
&roles,
&g.State,
&g.UserID,
&username,
&userType,
&userOwner,
&firstName,
&lastName,
&email,
&displayName,
&avatarURL,
&g.ResourceOwner,
&orgName,
&orgDomain,
&g.ProjectID,
&projectName,
)
if err != nil {
if errs.Is(err, sql.ErrNoRows) {
return nil, errors.ThrowNotFound(err, "QUERY-wIPkA", "Errors.UserGrant.NotFound")
}
return nil, errors.ThrowInternal(err, "QUERY-oQPcP", "Errors.Internal")
}
g.Roles = roles
g.Username = username.String
g.UserType = domain.UserType(userType.Int32)
g.UserResourceOwner = userOwner.String
g.FirstName = firstName.String
g.LastName = lastName.String
g.Email = email.String
g.DisplayName = displayName.String
g.AvatarURL = avatarURL.String
g.OrgName = orgName.String
g.OrgPrimaryDomain = orgDomain.String
g.ProjectName = projectName.String
return g, nil
}
}
func prepareUserGrantsQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserGrants, error)) {
return sq.Select(
UserGrantID.identifier(),
UserGrantCreationDate.identifier(),
UserGrantChangeDate.identifier(),
UserGrantSequence.identifier(),
UserGrantGrantID.identifier(),
UserGrantRoles.identifier(),
UserGrantState.identifier(),
UserGrantUserID.identifier(),
UserUsernameCol.identifier(),
UserTypeCol.identifier(),
UserResourceOwnerCol.identifier(),
HumanFirstNameCol.identifier(),
HumanLastNameCol.identifier(),
HumanEmailCol.identifier(),
HumanDisplayNameCol.identifier(),
HumanAvaterURLCol.identifier(),
UserGrantResourceOwner.identifier(),
OrgColumnName.identifier(),
OrgColumnDomain.identifier(),
UserGrantProjectID.identifier(),
ProjectColumnName.identifier(),
countColumn.identifier(),
).
From(userGrantTable.identifier()).
LeftJoin(join(UserIDCol, UserGrantUserID)).
LeftJoin(join(HumanUserIDCol, UserGrantUserID)).
LeftJoin(join(OrgColumnID, UserGrantResourceOwner)).
LeftJoin(join(ProjectColumnID, UserGrantProjectID)).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*UserGrants, error) {
userGrants := make([]*UserGrant, 0)
var count uint64
for rows.Next() {
g := new(UserGrant)
var (
roles = pq.StringArray{}
username sql.NullString
userType sql.NullInt32
userOwner sql.NullString
firstName sql.NullString
lastName sql.NullString
email sql.NullString
displayName sql.NullString
avatarURL sql.NullString
orgName sql.NullString
orgDomain sql.NullString
projectName sql.NullString
)
err := rows.Scan(
&g.ID,
&g.CreationDate,
&g.ChangeDate,
&g.Sequence,
&g.GrantID,
&roles,
&g.State,
&g.UserID,
&username,
&userType,
&userOwner,
&firstName,
&lastName,
&email,
&displayName,
&avatarURL,
&g.ResourceOwner,
&orgName,
&orgDomain,
&g.ProjectID,
&projectName,
&count,
)
if err != nil {
return nil, err
}
g.Roles = roles
g.Username = username.String
g.UserType = domain.UserType(userType.Int32)
g.UserResourceOwner = userOwner.String
g.FirstName = firstName.String
g.LastName = lastName.String
g.Email = email.String
g.DisplayName = displayName.String
g.AvatarURL = avatarURL.String
g.OrgName = orgName.String
g.OrgPrimaryDomain = orgDomain.String
g.ProjectName = projectName.String
userGrants = append(userGrants, g)
}
if err := rows.Close(); err != nil {
return nil, errors.ThrowInternal(err, "QUERY-iGvmP", "Errors.Query.CloseRows")
}
return &UserGrants{
UserGrants: userGrants,
SearchResponse: SearchResponse{
Count: count,
},
}, nil
}
}

View File

@@ -0,0 +1,778 @@
package query
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"regexp"
"testing"
"github.com/lib/pq"
"github.com/caos/zitadel/internal/domain"
errs "github.com/caos/zitadel/internal/errors"
)
var (
userGrantStmt = regexp.QuoteMeta(
"SELECT zitadel.projections.user_grants.id" +
", zitadel.projections.user_grants.creation_date" +
", zitadel.projections.user_grants.change_date" +
", zitadel.projections.user_grants.sequence" +
", zitadel.projections.user_grants.grant_id" +
", zitadel.projections.user_grants.roles" +
", zitadel.projections.user_grants.state" +
", zitadel.projections.user_grants.user_id" +
", zitadel.projections.users.username" +
", zitadel.projections.users.type" +
", zitadel.projections.users.resource_owner" +
", zitadel.projections.users_humans.first_name" +
", zitadel.projections.users_humans.last_name" +
", zitadel.projections.users_humans.email" +
", zitadel.projections.users_humans.display_name" +
", zitadel.projections.users_humans.avatar_key" +
", zitadel.projections.user_grants.resource_owner" +
", zitadel.projections.orgs.name" +
", zitadel.projections.orgs.primary_domain" +
", zitadel.projections.user_grants.project_id" +
", zitadel.projections.projects.name" +
" FROM zitadel.projections.user_grants" +
" LEFT JOIN zitadel.projections.users ON zitadel.projections.user_grants.user_id = zitadel.projections.users.id" +
" LEFT JOIN zitadel.projections.users_humans ON zitadel.projections.user_grants.user_id = zitadel.projections.users_humans.user_id" +
" LEFT JOIN zitadel.projections.orgs ON zitadel.projections.user_grants.resource_owner = zitadel.projections.orgs.id" +
" LEFT JOIN zitadel.projections.projects ON zitadel.projections.user_grants.project_id = zitadel.projections.projects.id")
userGrantCols = []string{
"id",
"creation_date",
"change_date",
"sequence",
"grant_id",
"roles",
"state",
"user_id",
"username",
"type",
"resource_owner", //user resource owner
"first_name",
"last_name",
"email",
"display_name",
"avatar_key",
"resource_owner", //user_grant resource owner
"name", //org name
"primary_domain",
"project_id",
"name", //project name
}
userGrantsStmt = regexp.QuoteMeta(
"SELECT zitadel.projections.user_grants.id" +
", zitadel.projections.user_grants.creation_date" +
", zitadel.projections.user_grants.change_date" +
", zitadel.projections.user_grants.sequence" +
", zitadel.projections.user_grants.grant_id" +
", zitadel.projections.user_grants.roles" +
", zitadel.projections.user_grants.state" +
", zitadel.projections.user_grants.user_id" +
", zitadel.projections.users.username" +
", zitadel.projections.users.type" +
", zitadel.projections.users.resource_owner" +
", zitadel.projections.users_humans.first_name" +
", zitadel.projections.users_humans.last_name" +
", zitadel.projections.users_humans.email" +
", zitadel.projections.users_humans.display_name" +
", zitadel.projections.users_humans.avatar_key" +
", zitadel.projections.user_grants.resource_owner" +
", zitadel.projections.orgs.name" +
", zitadel.projections.orgs.primary_domain" +
", zitadel.projections.user_grants.project_id" +
", zitadel.projections.projects.name" +
", COUNT(*) OVER ()" +
" FROM zitadel.projections.user_grants" +
" LEFT JOIN zitadel.projections.users ON zitadel.projections.user_grants.user_id = zitadel.projections.users.id" +
" LEFT JOIN zitadel.projections.users_humans ON zitadel.projections.user_grants.user_id = zitadel.projections.users_humans.user_id" +
" LEFT JOIN zitadel.projections.orgs ON zitadel.projections.user_grants.resource_owner = zitadel.projections.orgs.id" +
" LEFT JOIN zitadel.projections.projects ON zitadel.projections.user_grants.project_id = zitadel.projections.projects.id")
userGrantsCols = append(
userGrantCols,
"count",
)
)
func Test_UserGrantPrepares(t *testing.T) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
}
tests := []struct {
name string
prepare interface{}
want want
object interface{}
}{
{
name: "prepareUserGrantQuery no result",
prepare: prepareUserGrantQuery,
want: want{
sqlExpectations: mockQueries(
userGrantStmt,
nil,
nil,
),
err: func(err error) (error, bool) {
if !errs.IsNotFound(err) {
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
}
return nil, true
},
},
object: (*UserGrant)(nil),
},
{
name: "prepareUserGrantQuery found",
prepare: prepareUserGrantQuery,
want: want{
sqlExpectations: mockQuery(
userGrantStmt,
userGrantCols,
[]driver.Value{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
"org-name",
"primary-domain",
"project-id",
"project-name",
},
),
},
object: &UserGrant{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
{
name: "prepareUserGrantQuery machine user found",
prepare: prepareUserGrantQuery,
want: want{
sqlExpectations: mockQuery(
userGrantStmt,
userGrantCols,
[]driver.Value{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeMachine,
"resource-owner",
nil,
nil,
nil,
nil,
nil,
"ro",
"org-name",
"primary-domain",
"project-id",
"project-name",
},
),
},
object: &UserGrant{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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: "",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
{
name: "prepareUserGrantQuery (no org) found",
prepare: prepareUserGrantQuery,
want: want{
sqlExpectations: mockQuery(
userGrantStmt,
userGrantCols,
[]driver.Value{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
nil,
nil,
"project-id",
"project-name",
},
),
},
object: &UserGrant{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "",
OrgPrimaryDomain: "",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
{
name: "prepareUserGrantQuery (no project) found",
prepare: prepareUserGrantQuery,
want: want{
sqlExpectations: mockQuery(
userGrantStmt,
userGrantCols,
[]driver.Value{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
"org-name",
"primary-domain",
"project-id",
nil,
},
),
},
object: &UserGrant{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "",
},
},
{
name: "prepareUserGrantQuery sql err",
prepare: prepareUserGrantQuery,
want: want{
sqlExpectations: mockQueryErr(
userGrantStmt,
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: nil,
},
{
name: "prepareUserGrantsQuery no result",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueries(
userGrantsStmt,
nil,
nil,
),
},
object: &UserGrants{UserGrants: []*UserGrant{}},
},
{
name: "prepareUserGrantsQuery one grant",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueries(
userGrantsStmt,
userGrantsCols,
[][]driver.Value{
{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
"org-name",
"primary-domain",
"project-id",
"project-name",
},
},
),
},
object: &UserGrants{
SearchResponse: SearchResponse{
Count: 1,
},
UserGrants: []*UserGrant{
{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
},
},
{
name: "prepareUserGrantsQuery one grant (machine user)",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueries(
userGrantsStmt,
userGrantsCols,
[][]driver.Value{
{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeMachine,
"resource-owner",
nil,
nil,
nil,
nil,
nil,
"ro",
"org-name",
"primary-domain",
"project-id",
"project-name",
},
},
),
},
object: &UserGrants{
SearchResponse: SearchResponse{
Count: 1,
},
UserGrants: []*UserGrant{
{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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: "",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
},
},
{
name: "prepareUserGrantsQuery one grant (no org)",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueries(
userGrantsStmt,
userGrantsCols,
[][]driver.Value{
{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeMachine,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
nil,
nil,
"project-id",
"project-name",
},
},
),
},
object: &UserGrants{
SearchResponse: SearchResponse{
Count: 1,
},
UserGrants: []*UserGrant{
{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "",
OrgPrimaryDomain: "",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
},
},
{
name: "prepareUserGrantsQuery one grant (no project)",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueries(
userGrantsStmt,
userGrantsCols,
[][]driver.Value{
{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
"org-name",
"primary-domain",
"project-id",
nil,
},
},
),
},
object: &UserGrants{
SearchResponse: SearchResponse{
Count: 1,
},
UserGrants: []*UserGrant{
{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "",
},
},
},
},
{
name: "prepareUserGrantsQuery multiple grants",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueries(
userGrantsStmt,
userGrantsCols,
[][]driver.Value{
{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
"org-name",
"primary-domain",
"project-id",
"project-name",
},
{
"id",
testNow,
testNow,
20211111,
"grant-id",
pq.StringArray{"role-key"},
domain.UserGrantStateActive,
"user-id",
"username",
domain.UserTypeHuman,
"resource-owner",
"first-name",
"last-name",
"email",
"display-name",
"avatar-key",
"ro",
"org-name",
"primary-domain",
"project-id",
"project-name",
},
},
),
},
object: &UserGrants{
SearchResponse: SearchResponse{
Count: 2,
},
UserGrants: []*UserGrant{
{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
},
{
ID: "id",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211111,
Roles: []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",
ResourceOwner: "ro",
OrgName: "org-name",
OrgPrimaryDomain: "primary-domain",
ProjectID: "project-id",
ProjectName: "project-name",
},
},
},
},
{
name: "prepareUserGrantsQuery sql err",
prepare: prepareUserGrantsQuery,
want: want{
sqlExpectations: mockQueryErr(
userGrantsStmt,
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: 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)
})
}
}