From df72864d096a7b10d0a4f69eff00a24d9b29916b Mon Sep 17 00:00:00 2001 From: Abin Simon Date: Wed, 6 Apr 2022 14:51:12 +0530 Subject: [PATCH] Add option to filter by user auth mode Pass in type=password or type=oidc to pick the mode --- internal/dao/user.go | 10 +++- internal/models/kratosidentities.go | 33 +++++++++--- master.rest | 2 +- pkg/service/user.go | 4 +- pkg/service/user_test.go | 84 +++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 12 deletions(-) diff --git a/internal/dao/user.go b/internal/dao/user.go index c0b7833..b2861e3 100644 --- a/internal/dao/user.go +++ b/internal/dao/user.go @@ -97,8 +97,16 @@ func GetQueryFilteredUsers(ctx context.Context, db bun.IDB, partner, org, group, } // ListFilteredUsers will return the list of users fileterd by query -func ListFilteredUsers(ctx context.Context, db bun.IDB, users *[]models.KratosIdentities, fusers []uuid.UUID, query string, orderBy string, order string, limit int, offset int) (*[]models.KratosIdentities, error) { +func ListFilteredUsers(ctx context.Context, db bun.IDB, users *[]models.KratosIdentities, fusers []uuid.UUID, query string, utype string, orderBy string, order string, limit int, offset int) (*[]models.KratosIdentities, error) { q := db.NewSelect().Model(users) + + if utype != "" { + q.Relation("IdentityCredential"). + Relation("IdentityCredential.IdentityCredentialType", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Where("name = ?", utype) + }) + } + if len(fusers) > 0 { // filter with precomputed users if we have any q.Where("id IN (?)", bun.In(fusers)) diff --git a/internal/models/kratosidentities.go b/internal/models/kratosidentities.go index db23845..5aee37e 100644 --- a/internal/models/kratosidentities.go +++ b/internal/models/kratosidentities.go @@ -7,15 +7,32 @@ import ( "github.com/uptrace/bun" ) +type KratosIdentityCredentialTypes struct { + bun.BaseModel `bun:"table:identity_credential_types,alias:ict"` + + ID uuid.UUID `bun:"id,type:uuid,pk"` + Name string `bun:"name,type:string"` +} + +type KratosIdentityCredentials struct { + bun.BaseModel `bun:"table:identity_credentials,alias:ic"` + + ID uuid.UUID `bun:"id,type:uuid,pk"` + IdentityID uuid.UUID `bun:"identity_id,type:uuid"` + IdentityCredentialTypeID uuid.UUID `bun:"identity_credential_type_id,type:uuid"` + IdentityCredentialType KratosIdentityCredentialTypes `bun:"rel:has-one,join:identity_credential_type_id=id"` +} + type KratosIdentities struct { bun.BaseModel `bun:"table:identities,alias:identities"` - ID uuid.UUID `bun:"id,type:uuid,pk"` - SchemaId string `bun:"schema_id,notnull"` - Traits map[string]interface{} `bun:"traits,type:jsonb"` - CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"` - UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp"` - State string `bun:"state,notnull"` - StateChangedAt time.Time `bun:"state_changed_at,notnull,default:current_timestamp"` - NId uuid.UUID `bun:"nid,type:uuid,pk"` + ID uuid.UUID `bun:"id,type:uuid,pk"` + SchemaId string `bun:"schema_id,notnull"` + Traits map[string]interface{} `bun:"traits,type:jsonb"` + CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp"` + State string `bun:"state,notnull"` + StateChangedAt time.Time `bun:"state_changed_at,notnull,default:current_timestamp"` + NId uuid.UUID `bun:"nid,type:uuid,pk"` + IdentityCredential KratosIdentityCredentials `bun:"rel:has-one,join:id=identity_id"` } diff --git a/master.rest b/master.rest index 1da1ad5..31f36cd 100644 --- a/master.rest +++ b/master.rest @@ -276,7 +276,7 @@ spec: project: :project # Get all users -GET :host/auth/v3/users +GET :host/auth/v3/users?partner=:partner&organization=:org&type=password Content-Type: application/yaml X-Session-Token: :token diff --git a/pkg/service/user.go b/pkg/service/user.go index f584151..dc1e7ce 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -718,7 +718,7 @@ func (s *userService) List(ctx context.Context, opts ...query.Option) (*userv3.U if len(uids) != 0 { // TODO: maybe merge this with the previous one into single sql usrs, err = dao.ListFilteredUsers(ctx, s.db, &accs, - uids, queryOptions.Q, + uids, queryOptions.Q, queryOptions.Type, queryOptions.OrderBy, queryOptions.Order, int(queryOptions.Limit), int(queryOptions.Offset)) if err != nil { @@ -728,7 +728,7 @@ func (s *userService) List(ctx context.Context, opts ...query.Option) (*userv3.U } else { // If no filters are available we have to list just using identities table usrs, err = dao.ListFilteredUsers(ctx, s.db, &accs, - []uuid.UUID{}, queryOptions.Q, + []uuid.UUID{}, queryOptions.Q, queryOptions.Type, queryOptions.OrderBy, queryOptions.Order, int(queryOptions.Limit), int(queryOptions.Offset)) if err != nil { diff --git a/pkg/service/user_test.go b/pkg/service/user_test.go index 5bc4b4c..8fea08c 100644 --- a/pkg/service/user_test.go +++ b/pkg/service/user_test.go @@ -490,6 +490,90 @@ func TestUserList(t *testing.T) { performBasicAuthProviderChecks(t, *ap, 0, 0, 0, 0) } + +func TestUserListWithType(t *testing.T) { + // TODO: merge these tests + db, mock := getDB(t) + defer db.Close() + + ap := &mockAuthProvider{} + mazc := mockAuthzClient{} + us := NewUserService(ap, db, &mazc, nil, common.CliConfigDownloadData{}) + + uuuid1 := uuid.New().String() + uuuid2 := uuid.New().String() + puuid := uuid.New().String() + ouuid := uuid.New().String() + guuid := uuid.New().String() + ruuid := uuid.New().String() + pruuid := uuid.New().String() + + mock.ExpectQuery(`SELECT "partner"."id" FROM "authsrv_partner" AS "partner"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(puuid)) + mock.ExpectQuery(`SELECT "organization"."id" FROM "authsrv_organization" AS "organization"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(ouuid)) + mock.ExpectQuery(`SELECT "identities"."id", "identities"."schema_id", "identities"."traits", "identities"."created_at", "identities"."updated_at", "identities"."state", "identities"."state_changed_at", "identities"."nid", "identity_credential"."id" AS "identity_credential__id", "identity_credential"."identity_id" AS "identity_credential__identity_id", "identity_credential"."identity_credential_type_id" AS "identity_credential__identity_credential_type_id", "identity_credential__identity_credential_type"."id" AS "identity_credential__identity_credential_type__id", "identity_credential__identity_credential_type"."name" AS "identity_credential__identity_credential_type__name" FROM "identities" LEFT JOIN "identity_credentials" AS "identity_credential" ON ."identity_credential"."identity_id" = "identities"."id". LEFT JOIN "identity_credential_types" AS "identity_credential__identity_credential_type" ON ."identity_credential__identity_credential_type"."id" = "identity_credential"."identity_credential_type_id". WHERE .name = 'password'. LIMIT 10`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"id", "traits"}). + AddRow(uuuid1, []byte(`{"email":"johndoe@provider.com", "first_name": "John", "last_name": "Doe", "organization_id": "`+ouuid+`", "partner_id": "`+puuid+`", "description": "My awesome user"}`)). + AddRow(uuuid2, []byte(`{"email":"johndoe@provider.com", "first_name": "John", "last_name": "Doe", "organization_id": "`+ouuid+`", "partner_id": "`+puuid+`", "description": "My awesome user"}`))) + + mock.ExpectQuery(`SELECT "group"."id".* FROM "authsrv_group" AS "group" JOIN authsrv_groupaccount ON authsrv_groupaccount.group_id="group".id WHERE .authsrv_groupaccount.account_id = '` + uuuid1 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"name"}). + AddRow("group-" + guuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_group.name as group FROM "authsrv_grouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_grouprole.role_id JOIN authsrv_group ON authsrv_group.id=authsrv_grouprole.group_id WHERE`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "group"}).AddRow("role-"+ruuid, "group-"+guuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group FROM "authsrv_projectgrouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgrouprole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectgrouprole.project_id JOIN authsrv_group ON authsrv_group.id=authsrv_projectgrouprole.group_id WHERE`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project"}).AddRow("role-"+ruuid, "project-"+puuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_project.name as project, namespace_id as namespace, authsrv_group.name as group FROM "authsrv_projectgroupnamespacerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project"}).AddRow("role-"+ruuid, "project-"+puuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role FROM "authsrv_accountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_accountresourcerole.role_id WHERE .authsrv_accountresourcerole.account_id = '` + uuuid1 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role"}).AddRow("role-" + ruuid)) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project FROM "authsrv_projectaccountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountresourcerole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectaccountresourcerole.project_id WHERE .authsrv_projectaccountresourcerole.account_id = '` + uuuid1 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project"}).AddRow("role-"+ruuid, "project-"+pruuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_project.name as project, namespace_id as namespace FROM "authsrv_projectaccountnamespacerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountnamespacerole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectaccountnamespacerole.project_id WHERE .authsrv_projectaccountnamespacerole.account_id = '` + uuuid1 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "namespace"}).AddRow("role-"+ruuid, "project-"+pruuid, 9)) + + mock.ExpectQuery(`SELECT "group"."id".* FROM "authsrv_group" AS "group" JOIN authsrv_groupaccount ON authsrv_groupaccount.group_id="group".id WHERE .authsrv_groupaccount.account_id = '` + uuuid2 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"name"}). + AddRow("group-" + guuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_group.name as group FROM "authsrv_grouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_grouprole.role_id JOIN authsrv_group ON authsrv_group.id=authsrv_grouprole.group_id WHERE`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "group"}).AddRow("role-"+ruuid, "group-"+guuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_project.name as project, authsrv_group.name as group FROM "authsrv_projectgrouprole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectgrouprole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectgrouprole.project_id JOIN authsrv_group ON authsrv_group.id=authsrv_projectgrouprole.group_id WHERE`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project"}).AddRow("role-"+ruuid, "project-"+puuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_project.name as project, namespace_id as namespace, authsrv_group.name as group FROM "authsrv_projectgroupnamespacerole"`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project"}).AddRow("role-"+ruuid, "project-"+puuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role FROM "authsrv_accountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_accountresourcerole.role_id WHERE .authsrv_accountresourcerole.account_id = '` + uuuid2 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role"}).AddRow("role-" + ruuid)) + mock.ExpectQuery(`SELECT distinct authsrv_resourcerole.name as role, authsrv_project.name as project FROM "authsrv_projectaccountresourcerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountresourcerole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectaccountresourcerole.project_id WHERE .authsrv_projectaccountresourcerole.account_id = '` + uuuid2 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project"}).AddRow("role-"+ruuid, "project-"+pruuid)) + mock.ExpectQuery(`SELECT authsrv_resourcerole.name as role, authsrv_project.name as project, namespace_id as namespace FROM "authsrv_projectaccountnamespacerole" JOIN authsrv_resourcerole ON authsrv_resourcerole.id=authsrv_projectaccountnamespacerole.role_id JOIN authsrv_project ON authsrv_project.id=authsrv_projectaccountnamespacerole.project_id WHERE .authsrv_projectaccountnamespacerole.account_id = '` + uuuid2 + `'`). + WithArgs().WillReturnRows(sqlmock.NewRows([]string{"role", "project", "namespace"}).AddRow("role-"+ruuid, "project-"+pruuid, 9)) + + qo := &commonv3.QueryOptions{Organization: ouuid, Partner: puuid, Type: "password"} + userlist, err := us.List(context.Background(), query.WithOptions(qo)) + if err != nil { + t.Fatal("could not list users:", err) + } + if userlist.Metadata.Count != 2 { + t.Fatalf("incorrect number of users returned, expected 2; got %v", userlist.Metadata.Count) + } + if userlist.Items[0].Metadata.Name != "johndoe@provider.com" || userlist.Items[1].Metadata.Name != "johndoe@provider.com" { + t.Errorf("incorrect user names returned when listing; expected '%v' and '%v'; got '%v' and '%v'", "johndoe@provider.com", "johndoe@provider.com", userlist.Items[0].Metadata.Name, userlist.Items[1].Metadata.Name) + } + if len(userlist.Items[0].GetSpec().GetGroups()) != 1 { + t.Errorf("invalid number of groups returned for user, expected 1; got '%v'", len(userlist.Items[0].GetSpec().GetGroups())) + } + + if len(userlist.Items[0].GetSpec().GetProjectNamespaceRoles()) != 6 { + t.Errorf("invalid number of roles returned for user, expected 6; got '%v'", len(userlist.Items[0].GetSpec().GetProjectNamespaceRoles())) + } + if userlist.Items[0].GetSpec().GetProjectNamespaceRoles()[2].GetNamespace() != 9 { + t.Errorf("invalid namespace in role returned for user, expected 9; got '%v'", userlist.Items[0].GetSpec().GetProjectNamespaceRoles()[2].Namespace) + } + + performBasicAuthProviderChecks(t, *ap, 0, 0, 0, 0) +} + func TestUserFiltered(t *testing.T) { db, mock := getDB(t) defer db.Close()