Ongoing OIDC: return the primary account info along with the user. (#1145)
This commit is contained in:
33 files changed
+1514
-643
No files matched your search
@@ -4,6 +4,20 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
|
||||
## Next
|
||||
|
||||
### New and Improved
|
||||
|
||||
* cli/api/sdk: New User's attributes for:
|
||||
* Primary Account ID
|
||||
* Login Name
|
||||
* Full Name
|
||||
* Email
|
||||
|
||||
These new user attributes correspond to attributes
|
||||
from the user's primary auth method account. These attributes will be empty
|
||||
when the user has no account in the primary auth method for their scope, or
|
||||
there is no designated primary auth method for their scope.
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cors: Fix allowing all origins by default
|
||||
|
||||
@@ -24,6 +24,10 @@ type User struct {
|
||||
AccountIds []string `json:"account_ids,omitempty"`
|
||||
Accounts []*Account `json:"accounts,omitempty"`
|
||||
AuthorizedActions []string `json:"authorized_actions,omitempty"`
|
||||
LoginName string `json:"login_name,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
PrimaryAccountId string `json:"primary_account_id,omitempty"`
|
||||
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ go 1.16
|
||||
|
||||
replace github.com/hashicorp/boundary/api => ./api
|
||||
|
||||
replace github.com/hashicorp/boundary/sdk => ./sdk
|
||||
|
||||
require (
|
||||
github.com/armon/go-metrics v0.3.6
|
||||
github.com/bufbuild/buf v0.37.0
|
||||
|
||||
@@ -436,6 +436,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 h1:IvO4FbbQL6n3v3M1rQNobZ61SGL0gJLdvKA5KETM7Xs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0/go.mod h1:d2gYTOTUQklu06xp0AJYYmRdTVU1VKrqhkYfYag2L08=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hashicorp/boundary/sdk v0.0.4 h1:l3uuXOL5ZQwYc63Bm8sLY36XGUlIN/4Q2r2SqkmGTyw=
|
||||
github.com/hashicorp/boundary/sdk v0.0.4/go.mod h1:MQ8FJhseJrKpwvFDTn+AUAcJ1vdk1NKUxk7DxQgH7oQ=
|
||||
github.com/hashicorp/cap v0.0.0-20210408110729-0dd65efe5473 h1:qEikB8/ychrgsll9feuGjVLt0qo5oktfyTWi7BLsUrg=
|
||||
github.com/hashicorp/cap v0.0.0-20210408110729-0dd65efe5473/go.mod h1:hjMYKmFvk9AOL69NbqrahCHoMPZHRWqvRcdr7ciPU7g=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestArgon2Credential_New(t *testing.T) {
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
auts := TestAuthMethods(t, conn, o.GetPublicId(), 1)
|
||||
aut := auts[0]
|
||||
accts := TestAccounts(t, conn, aut.PublicId, 5)
|
||||
accts := TestMultipleAccounts(t, conn, aut.PublicId, 5)
|
||||
confs := testArgon2Confs(t, conn, accts[0].AuthMethodId, 1)
|
||||
|
||||
type args struct {
|
||||
|
||||
@@ -349,7 +349,7 @@ func TestRepository_LookupAccount(t *testing.T) {
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
authMethod := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
account := TestAccounts(t, conn, authMethod.GetPublicId(), 1)[0]
|
||||
account := TestAccount(t, conn, authMethod.GetPublicId(), "name1")
|
||||
|
||||
newAcctId, err := newAccountId()
|
||||
require.NoError(t, err)
|
||||
@@ -405,7 +405,7 @@ func TestRepository_DeleteAccount(t *testing.T) {
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
authMethod := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
account := TestAccounts(t, conn, authMethod.GetPublicId(), 1)[0]
|
||||
account := TestAccount(t, conn, authMethod.GetPublicId(), "name1")
|
||||
|
||||
newAcctId, err := newAccountId()
|
||||
require.NoError(t, err)
|
||||
@@ -462,8 +462,8 @@ func TestRepository_ListAccounts(t *testing.T) {
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
authMethods := TestAuthMethods(t, conn, org.GetPublicId(), 3)
|
||||
accounts1 := TestAccounts(t, conn, authMethods[0].GetPublicId(), 3)
|
||||
accounts2 := TestAccounts(t, conn, authMethods[1].GetPublicId(), 4)
|
||||
accounts1 := TestMultipleAccounts(t, conn, authMethods[0].GetPublicId(), 3)
|
||||
accounts2 := TestMultipleAccounts(t, conn, authMethods[1].GetPublicId(), 4)
|
||||
_ = accounts2
|
||||
|
||||
tests := []struct {
|
||||
@@ -522,7 +522,7 @@ func TestRepository_ListAccounts_Limits(t *testing.T) {
|
||||
am := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
|
||||
accountCount := 10
|
||||
_ = TestAccounts(t, conn, am.GetPublicId(), accountCount)
|
||||
_ = TestMultipleAccounts(t, conn, am.GetPublicId(), accountCount)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -982,7 +982,7 @@ func TestRepository_UpdateAccount_DupeNames(t *testing.T) {
|
||||
name := "test-dup-name"
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
am := TestAuthMethods(t, conn, org.PublicId, 1)[0]
|
||||
acts := TestAccounts(t, conn, am.PublicId, 2)
|
||||
acts := TestMultipleAccounts(t, conn, am.PublicId, 2)
|
||||
|
||||
aa := acts[0]
|
||||
ab := acts[1]
|
||||
@@ -1060,7 +1060,7 @@ func TestRepository_UpdateAccount_DupeNames(t *testing.T) {
|
||||
loginName := "kazmierczak12"
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
am := TestAuthMethods(t, conn, org.PublicId, 1)[0]
|
||||
acts := TestAccounts(t, conn, am.PublicId, 2)
|
||||
acts := TestMultipleAccounts(t, conn, am.PublicId, 2)
|
||||
|
||||
aa := acts[0]
|
||||
ab := acts[1]
|
||||
@@ -1137,8 +1137,8 @@ func TestRepository_UpdateAccount_DupeNames(t *testing.T) {
|
||||
ama := ams[0]
|
||||
amb := ams[1]
|
||||
|
||||
aa := TestAccounts(t, conn, ama.PublicId, 1)[0]
|
||||
ab := TestAccounts(t, conn, amb.PublicId, 1)[0]
|
||||
aa := TestMultipleAccounts(t, conn, ama.PublicId, 1)[0]
|
||||
ab := TestMultipleAccounts(t, conn, amb.PublicId, 1)[0]
|
||||
|
||||
assert.NotEqual(aa.AuthMethodId, ab.AuthMethodId)
|
||||
orig := aa.clone()
|
||||
|
||||
@@ -49,32 +49,42 @@ func TestAuthMethods(t *testing.T, conn *gorm.DB, scopeId string, count int) []*
|
||||
return auts
|
||||
}
|
||||
|
||||
// TestAccounts creates count number of password account to the provided DB
|
||||
// TestMultipleAccounts creates count number of password account to the provided DB
|
||||
// with the provided auth method id. The auth method must have been created previously.
|
||||
// If any errors are encountered during the creation of the account, the test will fail.
|
||||
func TestAccounts(t *testing.T, conn *gorm.DB, authMethodId string, count int) []*Account {
|
||||
func TestMultipleAccounts(t *testing.T, conn *gorm.DB, authMethodId string, count int) []*Account {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
var auts []*Account
|
||||
for i := 0; i < count; i++ {
|
||||
cat, err := NewAccount(authMethodId, WithLoginName(fmt.Sprintf("name%d", i)))
|
||||
assert.NoError(err)
|
||||
require.NotNil(cat)
|
||||
id, err := newAccountId()
|
||||
assert.NoError(err)
|
||||
require.NotEmpty(id)
|
||||
cat.PublicId = id
|
||||
|
||||
ctx := context.Background()
|
||||
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
return iw.Create(ctx, cat)
|
||||
},
|
||||
)
|
||||
|
||||
require.NoError(err2)
|
||||
auts = append(auts, cat)
|
||||
auts = append(auts, TestAccount(t, conn, authMethodId, fmt.Sprintf("name%d", i)))
|
||||
}
|
||||
return auts
|
||||
}
|
||||
|
||||
// TestAccount creates a password account to the provided DB with the provided
|
||||
// auth method id and loginName. The auth method must have been created
|
||||
// previously. See password.NewAccount(...) for a list of supported options.
|
||||
// If any errors are encountered during the creation of the account, the test will fail.
|
||||
func TestAccount(t *testing.T, conn *gorm.DB, authMethodId, loginName string, opt ...Option) *Account {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NotEmpty(loginName)
|
||||
w := db.New(conn)
|
||||
opt = append(opt, WithLoginName(loginName))
|
||||
cat, err := NewAccount(authMethodId, opt...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(cat)
|
||||
id, err := newAccountId()
|
||||
assert.NoError(err)
|
||||
require.NotEmpty(id)
|
||||
cat.PublicId = id
|
||||
|
||||
ctx := context.Background()
|
||||
_, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, iw db.Writer) error {
|
||||
return iw.Create(ctx, cat)
|
||||
},
|
||||
)
|
||||
require.NoError(err2)
|
||||
return cat
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func Test_TestAuthMethods(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_TestAccounts(t *testing.T) {
|
||||
func Test_TestMultipleAccounts(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
@@ -37,7 +37,7 @@ func Test_TestAccounts(t *testing.T) {
|
||||
am := TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
|
||||
count := 4
|
||||
accounts := TestAccounts(t, conn, am.GetPublicId(), count)
|
||||
accounts := TestMultipleAccounts(t, conn, am.GetPublicId(), count)
|
||||
assert.Len(accounts, count)
|
||||
for _, a := range accounts {
|
||||
assert.NotEmpty(a.GetPublicId())
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestAuthToken_DbUpdate(t *testing.T) {
|
||||
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
|
||||
newAuthTokId, err := NewAuthTokenId()
|
||||
require.NoError(t, err)
|
||||
@@ -129,7 +129,7 @@ func TestAuthToken_DbCreate(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
createdAuthToken := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
|
||||
testAuthTokenId := func() string {
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestRepository_CreateAuthToken(t *testing.T) {
|
||||
|
||||
org1, _ := iam.TestScopes(t, repo)
|
||||
am := password.TestAuthMethods(t, conn, org1.GetPublicId(), 1)[0]
|
||||
aAcct := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0]
|
||||
aAcct := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
iam.TestSetPrimaryAuthMethod(t, repo, org1, am.PublicId)
|
||||
u1 := iam.TestUser(t, repo, org1.PublicId, iam.WithAccountIds(aAcct.PublicId))
|
||||
|
||||
@@ -795,7 +795,7 @@ func Test_CloseExpiredPendingTokens(t *testing.T) {
|
||||
authMethods := password.TestAuthMethods(t, conn, org.PublicId, 1)
|
||||
authMethodId := authMethods[0].PublicId
|
||||
|
||||
accts := password.TestAccounts(t, conn, authMethodId, cnt)
|
||||
accts := password.TestMultipleAccounts(t, conn, authMethodId, cnt)
|
||||
for i := 0; i < cnt; i++ {
|
||||
at := allocAuthToken()
|
||||
id, err := NewAuthTokenId()
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestAuthToken(t *testing.T, conn *gorm.DB, kms *kms.Kms, scopeId string, op
|
||||
authMethod := password.TestAuthMethods(t, conn, scopeId, 1)[0]
|
||||
// auth account is only used to join auth method to user.
|
||||
// We don't do anything else with the auth account in the test setup.
|
||||
acct := password.TestAccounts(t, conn, authMethod.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "name1")
|
||||
|
||||
ctx := context.Background()
|
||||
rw := db.New(conn)
|
||||
|
||||
@@ -179,6 +179,27 @@ func (c *Command) printListTable(items []*users.User) string {
|
||||
fmt.Sprintf(" Description: %s", u.Description),
|
||||
)
|
||||
}
|
||||
if u.PrimaryAccountId != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Primary Account ID: %s", u.PrimaryAccountId),
|
||||
)
|
||||
}
|
||||
if u.LoginName != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Login Name: %s", u.LoginName),
|
||||
)
|
||||
}
|
||||
if u.FullName != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Full Name: %s", u.FullName),
|
||||
)
|
||||
}
|
||||
if u.Email != "" {
|
||||
output = append(output,
|
||||
fmt.Sprintf(" Email: %s", u.Email),
|
||||
)
|
||||
}
|
||||
|
||||
if len(u.AuthorizedActions) > 0 {
|
||||
output = append(output,
|
||||
" Authorized Actions:",
|
||||
@@ -204,6 +225,18 @@ func printItemTable(in *users.User) string {
|
||||
if in.Description != "" {
|
||||
nonAttributeMap["Description"] = in.Description
|
||||
}
|
||||
if in.PrimaryAccountId != "" {
|
||||
nonAttributeMap["PrimaryAccountId"] = in.PrimaryAccountId
|
||||
}
|
||||
if in.LoginName != "" {
|
||||
nonAttributeMap["LoginName"] = in.LoginName
|
||||
}
|
||||
if in.FullName != "" {
|
||||
nonAttributeMap["FullName"] = in.FullName
|
||||
}
|
||||
if in.Email != "" {
|
||||
nonAttributeMap["Email"] = in.Email
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil)
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
begin;
|
||||
|
||||
|
||||
|
||||
-- this constraint is intended to ensure that a user cannot have more than one
|
||||
-- account per auth_method.
|
||||
--
|
||||
-- If this constraint causes the migrations to fail the operartor can run the
|
||||
-- following query to get a list of user ids which have more that one account
|
||||
-- within the same auth method. At that point, the operator will need to pick
|
||||
-- which account to keep.
|
||||
----------------------------------------------------------------------------
|
||||
-- with too_many_accounts(iam_user_id, acct_count) as (
|
||||
-- select
|
||||
-- iam_user_id,
|
||||
-- count(*) as acct_count
|
||||
-- from
|
||||
-- auth_account
|
||||
-- group by auth_method_id, iam_user_id
|
||||
-- )
|
||||
-- select
|
||||
-- *
|
||||
-- from
|
||||
-- too_many_accounts
|
||||
-- where
|
||||
-- acct_count > 1;
|
||||
alter table auth_account
|
||||
add constraint auth_account_auth_method_id_public_id_uq
|
||||
unique(auth_method_id, iam_user_id);
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,59 @@
|
||||
begin;
|
||||
|
||||
drop view iam_user_acct_info;
|
||||
drop view iam_acct_info;
|
||||
|
||||
-- iam_user_acct_info provides account info for users by determining which
|
||||
-- auth_method is designated as for "account info" in the user's scope via the
|
||||
-- scope's primary_auth_method_id. Every sub-type of auth_account must be
|
||||
-- added to this view's union.
|
||||
create view iam_acct_info as
|
||||
select
|
||||
aa.iam_user_id,
|
||||
oa.subject as login_name,
|
||||
oa.public_id as primary_account_id,
|
||||
oa.full_name as full_name,
|
||||
oa.email as email
|
||||
from
|
||||
iam_scope s,
|
||||
auth_account aa,
|
||||
auth_oidc_account oa
|
||||
where
|
||||
aa.public_id = oa.public_id and
|
||||
aa.auth_method_id = s.primary_auth_method_id
|
||||
union
|
||||
select
|
||||
aa.iam_user_id,
|
||||
pa.public_id as primary_account_id,
|
||||
pa.login_name,
|
||||
'' as full_name,
|
||||
'' as email
|
||||
from
|
||||
iam_scope s,
|
||||
auth_account aa,
|
||||
auth_password_account pa
|
||||
where
|
||||
aa.public_id = pa.public_id and
|
||||
aa.auth_method_id = s.primary_auth_method_id;
|
||||
|
||||
|
||||
-- iam_user_acct_info provides a simple way to retrieve entries that include
|
||||
-- both the iam_user fields with an outer join to the user's account info.
|
||||
create view iam_user_acct_info as
|
||||
select
|
||||
u.public_id,
|
||||
u.scope_id,
|
||||
u.name,
|
||||
u.description,
|
||||
u.create_time,
|
||||
u.update_time,
|
||||
u.version,
|
||||
i.primary_account_id,
|
||||
i.login_name,
|
||||
i.full_name,
|
||||
i.email
|
||||
from
|
||||
iam_user u
|
||||
left outer join iam_acct_info i on u.public_id = i.iam_user_id;
|
||||
|
||||
commit;
|
||||
@@ -4,7 +4,7 @@ package schema
|
||||
|
||||
func init() {
|
||||
migrationStates["postgres"] = migrationState{
|
||||
binarySchemaVersion: 2020,
|
||||
binarySchemaVersion: 3002,
|
||||
upMigrations: map[int][]byte{
|
||||
1: []byte(`
|
||||
create domain wt_public_id as text
|
||||
@@ -5867,6 +5867,90 @@ from
|
||||
left outer join iam_scope s on am.public_id = s.primary_auth_method_id;
|
||||
comment on view auth_password_method_with_is_primary is
|
||||
'password auth method with an is_primary_auth_method bool';
|
||||
`),
|
||||
3001: []byte(`
|
||||
-- this constraint is intended to ensure that a user cannot have more than one
|
||||
-- account per auth_method.
|
||||
--
|
||||
-- If this constraint causes the migrations to fail the operartor can run the
|
||||
-- following query to get a list of user ids which have more that one account
|
||||
-- within the same auth method. At that point, the operator will need to pick
|
||||
-- which account to keep.
|
||||
----------------------------------------------------------------------------
|
||||
-- with too_many_accounts(iam_user_id, acct_count) as (
|
||||
-- select
|
||||
-- iam_user_id,
|
||||
-- count(*) as acct_count
|
||||
-- from
|
||||
-- auth_account
|
||||
-- group by auth_method_id, iam_user_id
|
||||
-- )
|
||||
-- select
|
||||
-- *
|
||||
-- from
|
||||
-- too_many_accounts
|
||||
-- where
|
||||
-- acct_count > 1;
|
||||
alter table auth_account
|
||||
add constraint auth_account_auth_method_id_public_id_uq
|
||||
unique(auth_method_id, iam_user_id);
|
||||
`),
|
||||
3002: []byte(`
|
||||
drop view iam_user_acct_info;
|
||||
drop view iam_acct_info;
|
||||
|
||||
-- iam_user_acct_info provides account info for users by determining which
|
||||
-- auth_method is designated as for "account info" in the user's scope via the
|
||||
-- scope's primary_auth_method_id. Every sub-type of auth_account must be
|
||||
-- added to this view's union.
|
||||
create view iam_acct_info as
|
||||
select
|
||||
aa.iam_user_id,
|
||||
oa.subject as login_name,
|
||||
oa.public_id as primary_account_id,
|
||||
oa.full_name as full_name,
|
||||
oa.email as email
|
||||
from
|
||||
iam_scope s,
|
||||
auth_account aa,
|
||||
auth_oidc_account oa
|
||||
where
|
||||
aa.public_id = oa.public_id and
|
||||
aa.auth_method_id = s.primary_auth_method_id
|
||||
union
|
||||
select
|
||||
aa.iam_user_id,
|
||||
pa.public_id as primary_account_id,
|
||||
pa.login_name,
|
||||
'' as full_name,
|
||||
'' as email
|
||||
from
|
||||
iam_scope s,
|
||||
auth_account aa,
|
||||
auth_password_account pa
|
||||
where
|
||||
aa.public_id = pa.public_id and
|
||||
aa.auth_method_id = s.primary_auth_method_id;
|
||||
|
||||
|
||||
-- iam_user_acct_info provides a simple way to retrieve entries that include
|
||||
-- both the iam_user fields with an outer join to the user's account info.
|
||||
create view iam_user_acct_info as
|
||||
select
|
||||
u.public_id,
|
||||
u.scope_id,
|
||||
u.name,
|
||||
u.description,
|
||||
u.create_time,
|
||||
u.update_time,
|
||||
u.version,
|
||||
i.primary_account_id,
|
||||
i.login_name,
|
||||
i.full_name,
|
||||
i.email
|
||||
from
|
||||
iam_user u
|
||||
left outer join iam_acct_info i on u.public_id = i.iam_user_id;
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3643,6 +3643,26 @@
|
||||
},
|
||||
"description": "Output only. The available actions on this resource for this user.",
|
||||
"readOnly": true
|
||||
},
|
||||
"login_name": {
|
||||
"type": "string",
|
||||
"title": "Output only. login_name is a string that maps to the user's account \"login\nname\" from the scope's primary auth method",
|
||||
"readOnly": true
|
||||
},
|
||||
"full_name": {
|
||||
"type": "string",
|
||||
"title": "Output only. full_name is a string that maps to the user's account name\nfrom the scope's primary auth method",
|
||||
"readOnly": true
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"title": "Output only. email is a string that maps to the user's account email from\nthe scope's primary auth method",
|
||||
"readOnly": true
|
||||
},
|
||||
"primary_account_id": {
|
||||
"type": "string",
|
||||
"title": "Output only. primary_account_id is a string that maps to the user's account\npublic_id from the scope's primary auth method",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"title": "User contains all fields related to a User resource"
|
||||
|
||||
@@ -110,6 +110,18 @@ type User struct {
|
||||
Accounts []*Account `protobuf:"bytes,100,rep,name=accounts,proto3" json:"accounts,omitempty"`
|
||||
// Output only. The available actions on this resource for this user.
|
||||
AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"`
|
||||
// Output only. login_name is a string that maps to the user's account "login
|
||||
// name" from the scope's primary auth method
|
||||
LoginName string `protobuf:"bytes,110,opt,name=login_name,proto3" json:"login_name,omitempty"`
|
||||
// Output only. full_name is a string that maps to the user's account name
|
||||
// from the scope's primary auth method
|
||||
FullName string `protobuf:"bytes,120,opt,name=full_name,proto3" json:"full_name,omitempty"`
|
||||
// Output only. email is a string that maps to the user's account email from
|
||||
// the scope's primary auth method
|
||||
Email string `protobuf:"bytes,130,opt,name=email,proto3" json:"email,omitempty"`
|
||||
// Output only. primary_account_id is a string that maps to the user's account
|
||||
// public_id from the scope's primary auth method
|
||||
PrimaryAccountId string `protobuf:"bytes,140,opt,name=primary_account_id,proto3" json:"primary_account_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *User) Reset() {
|
||||
@@ -221,6 +233,34 @@ func (x *User) GetAuthorizedActions() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *User) GetLoginName() string {
|
||||
if x != nil {
|
||||
return x.LoginName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetFullName() string {
|
||||
if x != nil {
|
||||
return x.FullName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetPrimaryAccountId() string {
|
||||
if x != nil {
|
||||
return x.PrimaryAccountId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_api_resources_users_v1_user_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_api_resources_users_v1_user_proto_rawDesc = []byte{
|
||||
@@ -242,7 +282,7 @@ var file_controller_api_resources_users_v1_user_proto_rawDesc = []byte{
|
||||
0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xd8, 0x04, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e,
|
||||
0x70, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xde, 0x05, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63,
|
||||
@@ -280,12 +320,21 @@ var file_controller_api_resources_users_v1_user_proto_rawDesc = []byte{
|
||||
0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75,
|
||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68,
|
||||
0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72,
|
||||
0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65,
|
||||
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x3b, 0x75, 0x73,
|
||||
0x65, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x6e,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x1c, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x78, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15,
|
||||
0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||
0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2f, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
|
||||
0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x8c, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x12, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
|
||||
0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x75, 0x73,
|
||||
0x65, 0x72, 0x73, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -6,12 +6,12 @@ package iam
|
||||
const (
|
||||
// whereUserAccount - given an auth account id, return the associated user.
|
||||
whereUserAccount = `
|
||||
select iam_user.*
|
||||
from iam_user
|
||||
select iam_user_acct_info.*
|
||||
from iam_user_acct_info
|
||||
inner join auth_account
|
||||
on iam_user.public_id = auth_account.iam_user_id
|
||||
on iam_user_acct_info.public_id = auth_account.iam_user_id
|
||||
where
|
||||
iam_user.scope_id = auth_account.scope_id and
|
||||
iam_user_acct_info.scope_id = auth_account.scope_id and
|
||||
auth_account.public_id = $1`
|
||||
|
||||
// whereValidAuthMethod - determine if an auth method public_id within a scope_id
|
||||
|
||||
+107
-34
@@ -39,6 +39,16 @@ func (r *Repository) CreateUser(ctx context.Context, user *User, opt ...Option)
|
||||
u.PublicId = id
|
||||
}
|
||||
|
||||
// There's no need to use r.lookupUser(...) here, because the new user cannot
|
||||
// be associated with any accounts yet. Why would you typically want to
|
||||
// call r.lookupUser(...) here vs returning the create resource? Well, the
|
||||
// created resource doesn't include the user's primary account info (email,
|
||||
// full name, etc), since you can't run DML against the view which does
|
||||
// provide these output only attributes. But in this case, there's no way a
|
||||
// newly created user could have any accounts, so we don't need to use
|
||||
// r.lookupUser(...). I'm adding this comment so a future version of myself
|
||||
// doesn't come along and decide to start using r.lookupUser(...) here which
|
||||
// would just be an unnecessary database lookup. You're welcome future me.
|
||||
resource, err := r.create(ctx, u)
|
||||
if err != nil {
|
||||
if errors.IsUniqueError(err) {
|
||||
@@ -133,13 +143,15 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, version uint32,
|
||||
// return err, which will result in a rollback of the update
|
||||
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
|
||||
}
|
||||
// we need a new repo, that's using the same reader/writer as this TxHandler
|
||||
txRepo := &Repository{
|
||||
reader: reader,
|
||||
writer: w,
|
||||
kms: r.kms,
|
||||
// intentionally not setting the defaultLimit, so we'll get all
|
||||
// the account ids without a limit
|
||||
// intentionally not setting the defaultLimit
|
||||
}
|
||||
returnedUser, err = txRepo.lookupUser(ctx, user.PublicId)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current user after update"))
|
||||
}
|
||||
currentAccountIds, err = txRepo.ListUserAccounts(ctx, user.PublicId)
|
||||
if err != nil {
|
||||
@@ -164,20 +176,15 @@ func (r *Repository) LookupUser(ctx context.Context, userId string, _ ...Option)
|
||||
if userId == "" {
|
||||
return nil, nil, errors.New(errors.InvalidParameter, op, "missing public id")
|
||||
}
|
||||
|
||||
user := allocUser()
|
||||
user.PublicId = userId
|
||||
if err := r.reader.LookupByPublicId(ctx, &user); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", userId)))
|
||||
user, err := r.lookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
currentAccountIds, err := r.ListUserAccounts(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to retrieve current account ids"))
|
||||
}
|
||||
return &user, currentAccountIds, nil
|
||||
return user, currentAccountIds, nil
|
||||
}
|
||||
|
||||
// DeleteUser will delete a user from the repository
|
||||
@@ -186,7 +193,7 @@ func (r *Repository) DeleteUser(ctx context.Context, withPublicId string, _ ...O
|
||||
if withPublicId == "" {
|
||||
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing public id")
|
||||
}
|
||||
user := allocUser()
|
||||
user := AllocUser()
|
||||
user.PublicId = withPublicId
|
||||
if err := r.reader.LookupByPublicId(ctx, &user); err != nil {
|
||||
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("for %s", withPublicId)))
|
||||
@@ -204,8 +211,7 @@ func (r *Repository) ListUsers(ctx context.Context, withScopeIds []string, opt .
|
||||
if len(withScopeIds) == 0 {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
var users []*User
|
||||
err := r.list(ctx, &users, "scope_id in (?)", []interface{}{withScopeIds}, opt...)
|
||||
users, err := r.getUsers(ctx, "", withScopeIds, opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
@@ -266,7 +272,7 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string,
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0, 2)
|
||||
ticket, err := w.GetTicket(&acct)
|
||||
if err != nil {
|
||||
@@ -302,6 +308,16 @@ func (r *Repository) LookupUserWithLogin(ctx context.Context, accountId string,
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(err, op)
|
||||
}
|
||||
txRepo := &Repository{
|
||||
reader: reader,
|
||||
writer: w,
|
||||
kms: r.kms,
|
||||
// intentionally not setting the defaultLimit
|
||||
}
|
||||
obtainedUser, err = txRepo.lookupUser(ctx, obtainedUser.PublicId)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve user"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
@@ -336,7 +352,7 @@ func (r *Repository) getUserWithAccount(ctx context.Context, withAccountId strin
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to query account %s", withAccountId)))
|
||||
}
|
||||
defer rows.Close()
|
||||
u := allocUser()
|
||||
u := AllocUser()
|
||||
if rows.Next() {
|
||||
err = r.reader.ScanRows(rows, &u)
|
||||
if err != nil {
|
||||
@@ -388,10 +404,7 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing account ids")
|
||||
}
|
||||
|
||||
user := allocUser()
|
||||
user.PublicId = userId
|
||||
|
||||
err := r.reader.LookupById(ctx, &user)
|
||||
user, err := r.lookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup user %s", userId)))
|
||||
}
|
||||
@@ -406,11 +419,11 @@ func (r *Repository) AddUserAccounts(ctx context.Context, userId string, userVer
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
userTicket, err := w.GetTicket(&user)
|
||||
userTicket, err := w.GetTicket(user)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
updatedUser := allocUser()
|
||||
updatedUser := AllocUser()
|
||||
updatedUser.PublicId = userId
|
||||
updatedUser.Version = userVersion + 1
|
||||
var userOplogMsg oplog.Message
|
||||
@@ -470,9 +483,7 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing account ids")
|
||||
}
|
||||
|
||||
user := allocUser()
|
||||
user.PublicId = userId
|
||||
err := r.reader.LookupById(ctx, &user)
|
||||
user, err := r.lookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup user %s", userId)))
|
||||
}
|
||||
@@ -487,11 +498,11 @@ func (r *Repository) DeleteUserAccounts(ctx context.Context, userId string, user
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
userTicket, err := w.GetTicket(&user)
|
||||
userTicket, err := w.GetTicket(user)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
updatedUser := allocUser()
|
||||
updatedUser := AllocUser()
|
||||
updatedUser.PublicId = userId
|
||||
updatedUser.Version = userVersion + 1
|
||||
var userOplogMsg oplog.Message
|
||||
@@ -548,9 +559,7 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing version")
|
||||
}
|
||||
|
||||
user := allocUser()
|
||||
user.PublicId = userId
|
||||
err := r.reader.LookupById(ctx, &user)
|
||||
user, err := r.lookupUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup user %s", userId)))
|
||||
}
|
||||
@@ -585,11 +594,11 @@ func (r *Repository) SetUserAccounts(ctx context.Context, userId string, userVer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
userTicket, err := w.GetTicket(&user)
|
||||
userTicket, err := w.GetTicket(user)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
updatedUser := allocUser()
|
||||
updatedUser := AllocUser()
|
||||
updatedUser.PublicId = userId
|
||||
updatedUser.Version = userVersion + 1
|
||||
var userOplogMsg oplog.Message
|
||||
@@ -666,7 +675,7 @@ func associateUserWithAccounts(ctx context.Context, repoKms *kms.Kms, reader db.
|
||||
for _, accountId := range accountIds {
|
||||
acct := allocAccount()
|
||||
acct.PublicId = accountId
|
||||
err := reader.LookupByPublicId(context.Background(), &acct)
|
||||
err := reader.LookupByPublicId(ctx, &acct)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup account %s", accountId)))
|
||||
}
|
||||
@@ -823,3 +832,67 @@ func associationChanges(ctx context.Context, reader db.Reader, userId string, ac
|
||||
}
|
||||
return associateIds, disassociateIds, nil
|
||||
}
|
||||
|
||||
// lookupUser will lookup a single user and returns nil, nil when no user is found.
|
||||
func (r *Repository) lookupUser(ctx context.Context, userId string, opt ...Option) (*User, error) {
|
||||
const op = "iam.(Repository).lookupUser"
|
||||
users, err := r.getUsers(ctx, userId, nil, opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
switch {
|
||||
case len(users) == 0:
|
||||
return nil, nil // not an error to return no rows for a lookup
|
||||
case len(users) > 1:
|
||||
return nil, errors.New(errors.NotSpecificIntegrity, op, fmt.Sprintf("%s matched more than 1 ", userId))
|
||||
default:
|
||||
return users[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
// getUsers allows the caller to specify to either lookup a specific User via
|
||||
// its ID or search for a set of Users within a set of scopes. Passing both
|
||||
// scopeIds and a userId is an error. The WithLimit option is supported and all
|
||||
// other options are ignored.
|
||||
//
|
||||
// When no record is found then it returns nil, nil
|
||||
func (r *Repository) getUsers(ctx context.Context, userId string, scopeIds []string, opt ...Option) ([]*User, error) {
|
||||
const op = "iam.(Repository).getUsers"
|
||||
if userId == "" && len(scopeIds) == 0 {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing search criteria: both user id and scope ids are empty")
|
||||
}
|
||||
if userId != "" && len(scopeIds) > 0 {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "searching for both a specific user id and scope ids is not supported")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
dbArgs := []db.Option{}
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
dbArgs = append(dbArgs, db.WithLimit(limit))
|
||||
|
||||
var args []interface{}
|
||||
var where []string
|
||||
switch {
|
||||
case userId != "":
|
||||
where, args = append(where, "public_id = ?"), append(args, userId)
|
||||
default:
|
||||
where, args = append(where, "scope_id in(?)"), append(args, scopeIds)
|
||||
}
|
||||
var usersAcctInfo []*userAccountInfo
|
||||
err := r.reader.SearchWhere(ctx, &usersAcctInfo, strings.Join(where, " and "), args, dbArgs...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
if len(usersAcctInfo) == 0 { // we're done if nothing is found.
|
||||
return nil, nil
|
||||
}
|
||||
users := make([]*User, 0, len(usersAcctInfo))
|
||||
for _, u := range usersAcctInfo {
|
||||
users = append(users, u.shallowConversion())
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbassert "github.com/hashicorp/boundary/internal/db/assert"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRepository_associateUserWithAccounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo := TestRepo(t, conn, wrapper)
|
||||
org, _ := TestScopes(t, repo)
|
||||
authMethodId := testAuthMethod(t, conn, org.PublicId)
|
||||
authMethodId2 := testAuthMethod(t, conn, org.PublicId)
|
||||
|
||||
type Ids struct {
|
||||
user string
|
||||
accts []string
|
||||
}
|
||||
type args struct {
|
||||
Ids Ids
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *User
|
||||
want1 *authAccount
|
||||
wantErr bool
|
||||
wantErrCode errors.Code
|
||||
wantAssoc bool
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
a2 := testAccount(t, conn, org.PublicId, authMethodId2, "")
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId, a2.PublicId}}
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two-accounts",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
a2 := testAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId, a2.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.NotUnique,
|
||||
},
|
||||
{
|
||||
name: "missing-acctId",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
|
||||
return Ids{user: id}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-userId",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
return Ids{accts: []string{id}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "already-properly-assoc",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, u.PublicId)
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
opt: []Option{WithDisassociate(true)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "assoc-with-diff-user",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
diffUser := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, diffUser.PublicId)
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
{
|
||||
name: "assoc-with-diff-user-withDisassociateOption",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
diffUser := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, diffUser.PublicId)
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
{
|
||||
name: "bad-acct-id",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
id := testId(t)
|
||||
return Ids{user: u.PublicId, accts: []string{id}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.RecordNotFound,
|
||||
},
|
||||
{
|
||||
name: "bad-user-id-not-associated-account",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
return Ids{user: id, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-user-id",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
testUser := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, testUser.PublicId)
|
||||
return Ids{user: id, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
err := associateUserWithAccounts(context.Background(), kms, rw, rw, tt.args.Ids.user, tt.args.Ids.accts, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
for _, id := range tt.args.Ids.accts {
|
||||
acct := allocAccount()
|
||||
acct.PublicId = id
|
||||
err = rw.LookupByPublicId(context.Background(), &acct)
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.args.Ids.user, acct.IamUserId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_dissociateUserWithAccount(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo := TestRepo(t, conn, wrapper)
|
||||
org, _ := TestScopes(t, repo)
|
||||
authMethodId := testAuthMethod(t, conn, org.PublicId)
|
||||
authMethodId2 := testAuthMethod(t, conn, org.PublicId)
|
||||
|
||||
type Ids struct {
|
||||
user string
|
||||
accts []string
|
||||
}
|
||||
type args struct {
|
||||
Ids Ids
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *User
|
||||
want1 *authAccount
|
||||
wantErr bool
|
||||
wantErrCode errors.Code
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, u.PublicId)
|
||||
a2 := testAccount(t, conn, org.PublicId, authMethodId2, u.PublicId)
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId, a2.PublicId}}
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing-acctId",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
|
||||
return Ids{user: id}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-userId",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
return Ids{accts: []string{id}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "already-properly-disassoc",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
{
|
||||
name: "assoc-with-diff-user",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
diffUser := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, diffUser.PublicId)
|
||||
return Ids{user: u.PublicId, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
{
|
||||
name: "bad-acct-id",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
id := testId(t)
|
||||
return Ids{user: u.PublicId, accts: []string{id}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.RecordNotFound,
|
||||
},
|
||||
{
|
||||
name: "bad-user-id-not-associated-account",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, "")
|
||||
return Ids{user: id, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
{
|
||||
name: "bad-user-id",
|
||||
args: args{
|
||||
Ids: func() Ids {
|
||||
id := testId(t)
|
||||
testUser := TestUser(t, repo, org.PublicId)
|
||||
a := testAccount(t, conn, org.PublicId, authMethodId, testUser.PublicId)
|
||||
return Ids{user: id, accts: []string{a.PublicId}}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrCode: errors.AccountAlreadyAssociated,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
dbassert := dbassert.New(t, conn.DB())
|
||||
err := dissociateUserFromAccounts(context.Background(), kms, rw, rw, tt.args.Ids.user, tt.args.Ids.accts, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
|
||||
for _, id := range tt.args.Ids.accts {
|
||||
acct := allocAccount()
|
||||
acct.PublicId = id
|
||||
dbassert.IsNull(&acct, "IamUserId")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -25,7 +25,7 @@ func Test_LookupScope(t *testing.T) {
|
||||
require.NoError(err)
|
||||
assert.Equal(foundScope.PublicId, user.ScopeId)
|
||||
|
||||
user2 := allocUser()
|
||||
user2 := AllocUser()
|
||||
user2.PublicId = user.PublicId
|
||||
foundScope, err = LookupScope(context.Background(), w, user)
|
||||
require.NoError(err)
|
||||
@@ -46,7 +46,7 @@ func Test_LookupScope(t *testing.T) {
|
||||
assert.Nil(s)
|
||||
assert.Equal("iam.LookupScope: missing resource: parameter violation: error #100", err.Error())
|
||||
|
||||
user2 := allocUser()
|
||||
user2 := AllocUser()
|
||||
s, err = LookupScope(context.Background(), w, &user2)
|
||||
assert.Nil(s)
|
||||
assert.Contains(err.Error(), "iam.LookupScope: missing public id: parameter violation: error #100")
|
||||
|
||||
@@ -49,6 +49,22 @@ type User struct {
|
||||
// version allows optimistic locking of the user
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
|
||||
// Output only. login_name is a string that maps to the user's account "login
|
||||
// name" from the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
LoginName string `protobuf:"bytes,90,opt,name=login_name,proto3" json:"login_name,omitempty" gorm:"-"`
|
||||
// Output only. full_name is a string that maps to the user's account name
|
||||
// from the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
FullName string `protobuf:"bytes,100,opt,name=full_name,proto3" json:"full_name,omitempty" gorm:"-"`
|
||||
// Output only. email is a string that maps to the user's account email from
|
||||
// the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
Email string `protobuf:"bytes,110,opt,name=email,proto3" json:"email,omitempty" gorm:"-"`
|
||||
// Output only. primary_account_id is a string that maps to the user's account
|
||||
// public_id from the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
PrimaryAccountId string `protobuf:"bytes,120,opt,name=primary_account_id,proto3" json:"primary_account_id,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (x *User) Reset() {
|
||||
@@ -132,6 +148,34 @@ func (x *User) GetVersion() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *User) GetLoginName() string {
|
||||
if x != nil {
|
||||
return x.LoginName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetFullName() string {
|
||||
if x != nil {
|
||||
return x.FullName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetPrimaryAccountId() string {
|
||||
if x != nil {
|
||||
return x.PrimaryAccountId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_iam_store_v1_user_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_iam_store_v1_user_proto_rawDesc = []byte{
|
||||
@@ -145,7 +189,7 @@ var file_controller_storage_iam_store_v1_user_proto_rawDesc = []byte{
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f,
|
||||
0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x02, 0x0a, 0x04, 0x55,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xde, 0x03, 0x0a, 0x04, 0x55,
|
||||
0x73, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64,
|
||||
0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01,
|
||||
@@ -167,11 +211,19 @@ var file_controller_storage_iam_store_v1_user_proto_rawDesc = []byte{
|
||||
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
|
||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72,
|
||||
0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x69, 0x6e,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x6f, 0x67,
|
||||
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x6e,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x12, 0x70,
|
||||
0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
|
||||
0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x38, 0x5a, 0x36, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63,
|
||||
0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -48,6 +48,10 @@ func TestSetPrimaryAuthMethod(t *testing.T, repo *Repository, s *Scope, authMeth
|
||||
s.PrimaryAuthMethodId = authMethodId
|
||||
_, _, err := repo.UpdateScope(context.Background(), s, s.Version, []string{"PrimaryAuthMethodId"})
|
||||
require.NoError(err)
|
||||
|
||||
updated, err := repo.LookupScope(context.Background(), s.PublicId)
|
||||
require.NoError(err)
|
||||
require.Equalf(authMethodId, updated.PrimaryAuthMethodId, "expected %s to be the primary auth method for scope: %s", authMethodId, updated.PublicId)
|
||||
}
|
||||
|
||||
// TestScopes creates an org and project suitable for testing.
|
||||
@@ -253,7 +257,7 @@ func testAccount(t *testing.T, conn *gorm.DB, scopeId, authMethodId, userId stri
|
||||
require.NotEmpty(authMethodId)
|
||||
|
||||
if userId != "" {
|
||||
foundUser := allocUser()
|
||||
foundUser := AllocUser()
|
||||
foundUser.PublicId = userId
|
||||
err := rw.LookupByPublicId(context.Background(), &foundUser)
|
||||
require.NoError(err)
|
||||
|
||||
+36
-2
@@ -13,7 +13,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserTableName = "iam_user"
|
||||
defaultUserTableName = "iam_user"
|
||||
defaultUserAccountInfoTableName = "iam_user_acct_info"
|
||||
)
|
||||
|
||||
// User defines boundary users which are scoped to an Org
|
||||
@@ -48,7 +49,8 @@ func NewUser(scopeId string, opt ...Option) (*User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func allocUser() User {
|
||||
// AllocUser will allocate an empty user
|
||||
func AllocUser() User {
|
||||
return User{
|
||||
User: &store.User{},
|
||||
}
|
||||
@@ -106,3 +108,35 @@ func (u *User) TableName() string {
|
||||
func (u *User) SetTableName(n string) {
|
||||
u.tableName = n
|
||||
}
|
||||
|
||||
// userAccountInfo provides a way to represent a user along with the user's
|
||||
// account info from the scope's primary auth method
|
||||
type userAccountInfo struct {
|
||||
*store.User
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
func (u *userAccountInfo) shallowConversion() *User {
|
||||
return &User{
|
||||
User: u.User,
|
||||
}
|
||||
}
|
||||
|
||||
// TableName provides an overridden gorm table name..
|
||||
func (u *userAccountInfo) TableName() string {
|
||||
if u.tableName != "" {
|
||||
return u.tableName
|
||||
}
|
||||
return defaultUserAccountInfoTableName
|
||||
}
|
||||
|
||||
// SetTableName sets the table name for the resource. If the caller attempts to
|
||||
// set the name to "" the name will be reset to the default name.
|
||||
func (u *userAccountInfo) SetTableName(n string) {
|
||||
switch n {
|
||||
case "":
|
||||
u.tableName = defaultUserAccountInfoTableName
|
||||
default:
|
||||
u.tableName = n
|
||||
}
|
||||
}
|
||||
+24
-11
@@ -80,7 +80,7 @@ func Test_UserHardcoded(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
for _, v := range []string{"u_anon", "u_auth"} {
|
||||
foundUser := allocUser()
|
||||
foundUser := AllocUser()
|
||||
foundUser.PublicId = v
|
||||
err := w.LookupByPublicId(context.Background(), &foundUser)
|
||||
require.NoError(err)
|
||||
@@ -107,7 +107,7 @@ func Test_UserCreate(t *testing.T) {
|
||||
require.NoError(err)
|
||||
require.NotEmpty(user.PublicId)
|
||||
|
||||
foundUser := allocUser()
|
||||
foundUser := AllocUser()
|
||||
foundUser.PublicId = user.PublicId
|
||||
err = w.LookupByPublicId(context.Background(), &foundUser)
|
||||
require.NoError(err)
|
||||
@@ -135,12 +135,14 @@ func Test_UserUpdate(t *testing.T) {
|
||||
repo := TestRepo(t, conn, wrapper)
|
||||
id := testId(t)
|
||||
org, proj := TestScopes(t, repo)
|
||||
org2, _ := TestScopes(t, repo)
|
||||
|
||||
type args struct {
|
||||
name string
|
||||
description string
|
||||
fieldMaskPaths []string
|
||||
ScopeId string
|
||||
dbOpts []db.Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -201,6 +203,17 @@ func Test_UserUpdate(t *testing.T) {
|
||||
wantDup: true,
|
||||
wantErrMsg: `db.Update: duplicate key value violates unique constraint "iam_user_name_scope_id_key": unique constraint violation: integrity violation: error #1002`,
|
||||
},
|
||||
{
|
||||
name: "modified-scope",
|
||||
args: args{
|
||||
name: "modified-scope" + id,
|
||||
fieldMaskPaths: []string{"ScopeId"},
|
||||
ScopeId: org2.PublicId,
|
||||
dbOpts: []db.Option{db.WithSkipVetForWrite(true)},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: `db.Update: immutable column: iam_user.scope_id: integrity violation: error #1003`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -208,19 +221,19 @@ func Test_UserUpdate(t *testing.T) {
|
||||
if tt.wantDup {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
u.Name = tt.args.name
|
||||
_, err := rw.Update(context.Background(), u, tt.args.fieldMaskPaths, nil)
|
||||
_, err := rw.Update(context.Background(), u, tt.args.fieldMaskPaths, nil, tt.args.dbOpts...)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
|
||||
updateUser := allocUser()
|
||||
updateUser := AllocUser()
|
||||
updateUser.PublicId = u.PublicId
|
||||
updateUser.ScopeId = tt.args.ScopeId
|
||||
updateUser.Name = tt.args.name
|
||||
updateUser.Description = tt.args.description
|
||||
|
||||
updatedRows, err := rw.Update(context.Background(), &updateUser, tt.args.fieldMaskPaths, nil)
|
||||
updatedRows, err := rw.Update(context.Background(), &updateUser, tt.args.fieldMaskPaths, nil, tt.args.dbOpts...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.Equal(0, updatedRows)
|
||||
@@ -262,19 +275,19 @@ func Test_UserDelete(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "bad-id",
|
||||
user: func() *User { u := allocUser(); u.PublicId = id; return &u }(),
|
||||
user: func() *User { u := AllocUser(); u.PublicId = id; return &u }(),
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
{
|
||||
name: "anon-user",
|
||||
user: func() *User { u := allocUser(); u.PublicId = "u_anon"; return &u }(),
|
||||
user: func() *User { u := AllocUser(); u.PublicId = "u_anon"; return &u }(),
|
||||
wantErr: true,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
{
|
||||
name: "auth-user",
|
||||
user: func() *User { u := allocUser(); u.PublicId = "u_auth"; return &u }(),
|
||||
user: func() *User { u := AllocUser(); u.PublicId = "u_auth"; return &u }(),
|
||||
wantErr: true,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
@@ -282,7 +295,7 @@ func Test_UserDelete(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
deleteUser := allocUser()
|
||||
deleteUser := AllocUser()
|
||||
deleteUser.PublicId = tt.user.GetPublicId()
|
||||
deletedRows, err := rw.Delete(context.Background(), &deleteUser)
|
||||
if tt.wantRowsDeleted == 0 {
|
||||
@@ -368,7 +381,7 @@ func TestUser_Actions(t *testing.T) {
|
||||
|
||||
func TestUser_ResourceType(t *testing.T) {
|
||||
t.Parallel()
|
||||
u := allocUser()
|
||||
u := AllocUser()
|
||||
assert.Equal(t, resource.User, u.ResourceType())
|
||||
}
|
||||
|
||||
@@ -396,7 +409,7 @@ func TestUser_SetTableName(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
def := allocUser()
|
||||
def := AllocUser()
|
||||
require.Equal(defaultTableName, def.TableName())
|
||||
s := &User{
|
||||
User: &store.User{},
|
||||
|
||||
@@ -10,46 +10,62 @@ import "controller/api/resources/scopes/v1/scope.proto";
|
||||
import "controller/custom_options/v1/options.proto";
|
||||
|
||||
message Account {
|
||||
// Output only. The ID of the Account.
|
||||
string id = 10;
|
||||
// Output only. The ID of the Account.
|
||||
string id = 10;
|
||||
|
||||
// Output only. The Scope containing the Account.
|
||||
string scope_id = 20 [json_name="scope_id"];
|
||||
// Output only. The Scope containing the Account.
|
||||
string scope_id = 20 [json_name = "scope_id"];
|
||||
}
|
||||
|
||||
// User contains all fields related to a User resource
|
||||
message User {
|
||||
// Output only. The ID of the User.
|
||||
string id = 10;
|
||||
// Output only. The ID of the User.
|
||||
string id = 10;
|
||||
|
||||
// The ID of the Scope this resource is in.
|
||||
string scope_id = 20 [json_name="scope_id"];
|
||||
// The ID of the Scope this resource is in.
|
||||
string scope_id = 20 [json_name = "scope_id"];
|
||||
|
||||
// Output only. Scope information for this resource.
|
||||
resources.scopes.v1.ScopeInfo scope = 30;
|
||||
|
||||
// Optional name for identification purposes.
|
||||
google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"name" that: "name"}];
|
||||
// Output only. Scope information for this resource.
|
||||
resources.scopes.v1.ScopeInfo scope = 30;
|
||||
|
||||
// Optional user-set description for identification purposes.
|
||||
google.protobuf.StringValue description = 50 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"description" that: "description"}];
|
||||
// Optional name for identification purposes.
|
||||
google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "name" that: "name" }];
|
||||
|
||||
// Output only. The time this resource was created.
|
||||
google.protobuf.Timestamp created_time = 60 [json_name="created_time"];
|
||||
// Optional user-set description for identification purposes.
|
||||
google.protobuf.StringValue description = 50 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "description" that: "description" }];
|
||||
|
||||
// Output only. The time this resource was last updated.
|
||||
google.protobuf.Timestamp updated_time = 70 [json_name="updated_time"];
|
||||
// Output only. The time this resource was created.
|
||||
google.protobuf.Timestamp created_time = 60 [json_name = "created_time"];
|
||||
|
||||
// Version is used in mutation requests, after the initial creation, to ensure this resource has not changed.
|
||||
// The mutation will fail if the version does not match the latest known good version.
|
||||
uint32 version = 80;
|
||||
// Output only. The time this resource was last updated.
|
||||
google.protobuf.Timestamp updated_time = 70 [json_name = "updated_time"];
|
||||
|
||||
// Output only. Contains the list of Account IDs linked to this User.
|
||||
repeated string account_ids = 90 [json_name="account_ids"];
|
||||
// Version is used in mutation requests, after the initial creation, to ensure this resource has not changed.
|
||||
// The mutation will fail if the version does not match the latest known good version.
|
||||
uint32 version = 80;
|
||||
|
||||
// Output only. The Accounts linked to this User.
|
||||
repeated Account accounts = 100;
|
||||
// Output only. Contains the list of Account IDs linked to this User.
|
||||
repeated string account_ids = 90 [json_name = "account_ids"];
|
||||
|
||||
// Output only. The available actions on this resource for this user.
|
||||
repeated string authorized_actions = 300 [json_name="authorized_actions"];
|
||||
// Output only. The Accounts linked to this User.
|
||||
repeated Account accounts = 100;
|
||||
|
||||
// Output only. The available actions on this resource for this user.
|
||||
repeated string authorized_actions = 300 [json_name = "authorized_actions"];
|
||||
|
||||
// Output only. login_name is a string that maps to the user's account "login
|
||||
// name" from the scope's primary auth method
|
||||
string login_name = 110 [json_name = "login_name"];
|
||||
|
||||
// Output only. full_name is a string that maps to the user's account name
|
||||
// from the scope's primary auth method
|
||||
string full_name = 120 [json_name = "full_name"];
|
||||
|
||||
// Output only. email is a string that maps to the user's account email from
|
||||
// the scope's primary auth method
|
||||
string email = 130 [json_name = "email"];
|
||||
|
||||
// Output only. primary_account_id is a string that maps to the user's account
|
||||
// public_id from the scope's primary auth method
|
||||
string primary_account_id = 140 [json_name = "primary_account_id"];
|
||||
}
|
||||
@@ -18,11 +18,11 @@ message User {
|
||||
// name is the optional friendly name used to
|
||||
// access the User via an API
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string name = 30 [(custom_options.v1.mask_mapping) = {this:"name" that: "name"}];
|
||||
string name = 30 [(custom_options.v1.mask_mapping) = { this: "name" that: "name" }];
|
||||
|
||||
// description of the user
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string description = 40 [(custom_options.v1.mask_mapping) = {this:"description" that: "description"}];
|
||||
string description = 40 [(custom_options.v1.mask_mapping) = { this: "description" that: "description" }];
|
||||
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
@@ -35,4 +35,24 @@ message User {
|
||||
// version allows optimistic locking of the user
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 version = 70;
|
||||
|
||||
// Output only. login_name is a string that maps to the user's account "login
|
||||
// name" from the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
string login_name = 90 [json_name = "login_name"];
|
||||
|
||||
// Output only. full_name is a string that maps to the user's account name
|
||||
// from the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
string full_name = 100 [json_name = "full_name"];
|
||||
|
||||
// Output only. email is a string that maps to the user's account email from
|
||||
// the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
string email = 110 [json_name = "email"];
|
||||
|
||||
// Output only. primary_account_id is a string that maps to the user's account
|
||||
// public_id from the scope's primary auth method
|
||||
// @inject_tag: `gorm:"-"`
|
||||
string primary_account_id = 120 [json_name = "primary_account_id"];
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func TestGet(t *testing.T) {
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
|
||||
am := password.TestAuthMethods(t, conn, org.GetPublicId(), 1)[0]
|
||||
pwA := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0]
|
||||
pwA := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
|
||||
pwWireAccount := pb.Account{
|
||||
Id: pwA.GetPublicId(),
|
||||
@@ -236,7 +236,7 @@ func TestListPassword(t *testing.T) {
|
||||
amNoAccounts, amSomeAccounts, amOtherAccounts := ams[0], ams[1], ams[2]
|
||||
|
||||
var wantSomeAccounts []*pb.Account
|
||||
for _, aa := range password.TestAccounts(t, conn, amSomeAccounts.GetPublicId(), 3) {
|
||||
for _, aa := range password.TestMultipleAccounts(t, conn, amSomeAccounts.GetPublicId(), 3) {
|
||||
wantSomeAccounts = append(wantSomeAccounts, &pb.Account{
|
||||
Id: aa.GetPublicId(),
|
||||
AuthMethodId: aa.GetAuthMethodId(),
|
||||
@@ -251,7 +251,7 @@ func TestListPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
var wantOtherAccounts []*pb.Account
|
||||
for _, aa := range password.TestAccounts(t, conn, amOtherAccounts.GetPublicId(), 3) {
|
||||
for _, aa := range password.TestMultipleAccounts(t, conn, amOtherAccounts.GetPublicId(), 3) {
|
||||
wantOtherAccounts = append(wantOtherAccounts, &pb.Account{
|
||||
Id: aa.GetPublicId(),
|
||||
AuthMethodId: aa.GetAuthMethodId(),
|
||||
@@ -487,7 +487,7 @@ func TestDelete(t *testing.T) {
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
am1 := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
||||
ac := password.TestAccounts(t, conn, am1.GetPublicId(), 1)[0]
|
||||
ac := password.TestAccount(t, conn, am1.GetPublicId(), "name1")
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
@@ -577,7 +577,7 @@ func TestDelete_twice(t *testing.T) {
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
||||
ac := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0]
|
||||
ac := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
|
||||
s, err := accounts.NewService(pwRepoFn, oidcRepoFn)
|
||||
require.NoError(err, "Error when getting new user service")
|
||||
@@ -611,7 +611,7 @@ func TestCreatePassword(t *testing.T) {
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0]
|
||||
defaultAccount := password.TestAccounts(t, conn, am.GetPublicId(), 1)[0]
|
||||
defaultAccount := password.TestAccount(t, conn, am.GetPublicId(), "name1")
|
||||
defaultCreated := defaultAccount.GetCreateTime().GetTimestamp()
|
||||
require.NoError(t, err, "Error converting proto to timestamp.")
|
||||
|
||||
|
||||
@@ -473,6 +473,18 @@ func toProto(in *iam.User, accts []string) *pb.User {
|
||||
if in.GetName() != "" {
|
||||
out.Name = &wrapperspb.StringValue{Value: in.GetName()}
|
||||
}
|
||||
if in.GetPrimaryAccountId() != "" {
|
||||
out.PrimaryAccountId = in.GetPrimaryAccountId()
|
||||
}
|
||||
if in.GetLoginName() != "" {
|
||||
out.LoginName = in.GetLoginName()
|
||||
}
|
||||
if in.GetFullName() != "" {
|
||||
out.FullName = in.GetFullName()
|
||||
}
|
||||
if in.GetEmail() != "" {
|
||||
out.Email = in.GetEmail()
|
||||
}
|
||||
for _, a := range accts {
|
||||
out.Accounts = append(out.Accounts, &pb.Account{
|
||||
Id: a,
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/internal/auth"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
@@ -25,13 +25,14 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createDefaultUserAndRepo(t *testing.T) (*iam.User, func() (*iam.Repository, error)) {
|
||||
func createDefaultUserAndRepo(t *testing.T, withAccts bool) (*iam.User, []string, func() (*iam.Repository, error)) {
|
||||
t.Helper()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrap := db.TestWrapper(t)
|
||||
@@ -41,11 +42,43 @@ func createDefaultUserAndRepo(t *testing.T) (*iam.User, func() (*iam.Repository,
|
||||
}
|
||||
o, _ := iam.TestScopes(t, repo)
|
||||
u := iam.TestUser(t, repo, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"))
|
||||
return u, repoFn
|
||||
|
||||
switch withAccts {
|
||||
case false:
|
||||
return u, nil, repoFn
|
||||
default:
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
kmsCache := kms.TestKms(t, conn, wrap)
|
||||
databaseWrap, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
primaryAm := oidc.TestAuthMethod(t, conn, databaseWrap, o.PublicId, oidc.ActivePublicState, "alice-rp", "fido",
|
||||
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice-eve-smith.com")[0]),
|
||||
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "http://localhost:9200")[0]),
|
||||
oidc.WithSigningAlgs(oidc.RS256),
|
||||
)
|
||||
iam.TestSetPrimaryAuthMethod(t, repo, o, primaryAm.PublicId)
|
||||
|
||||
oidcAcct := oidc.TestAccount(t, conn, primaryAm, "alice", oidc.WithFullName("Alice Eve Smith"), oidc.WithEmail("alice@smith.com"))
|
||||
|
||||
secondaryAm := password.TestAuthMethods(t, conn, o.PublicId, 1)
|
||||
require.Len(secondaryAm, 1)
|
||||
pwAcct := password.TestAccount(t, conn, secondaryAm[0].PublicId, "alice")
|
||||
|
||||
added, err := repo.AddUserAccounts(ctx, u.PublicId, u.Version, []string{oidcAcct.PublicId, pwAcct.PublicId})
|
||||
require.NoError(err)
|
||||
require.Len(added, 2)
|
||||
|
||||
// reload the user with their accounts
|
||||
u, accts, err := repo.LookupUser(ctx, u.PublicId)
|
||||
require.NoError(err)
|
||||
return u, accts, repoFn
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
u, repoFn := createDefaultUserAndRepo(t)
|
||||
u, uAccts, repoFn := createDefaultUserAndRepo(t, true)
|
||||
|
||||
toMerge := &pbs.GetUserRequest{
|
||||
Id: u.GetPublicId(),
|
||||
}
|
||||
@@ -58,8 +91,14 @@ func TestGet(t *testing.T) {
|
||||
Description: &wrapperspb.StringValue{Value: u.GetDescription()},
|
||||
CreatedTime: u.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: u.UpdateTime.GetTimestamp(),
|
||||
Version: 1,
|
||||
Version: u.Version,
|
||||
AuthorizedActions: []string{"read", "update", "delete", "add-accounts", "set-accounts", "remove-accounts"},
|
||||
LoginName: u.LoginName,
|
||||
FullName: u.GetFullName(),
|
||||
Email: u.GetEmail(),
|
||||
PrimaryAccountId: u.GetPrimaryAccountId(),
|
||||
AccountIds: uAccts,
|
||||
Accounts: []*pb.Account{{Id: uAccts[0], ScopeId: u.ScopeId}, {Id: uAccts[1], ScopeId: u.ScopeId}},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -124,7 +163,21 @@ func TestList(t *testing.T) {
|
||||
oNoUsers, _ := iam.TestScopes(t, repo)
|
||||
oWithUsers, _ := iam.TestScopes(t, repo)
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, wrap)
|
||||
databaseWrap, err := kmsCache.GetWrapper(context.Background(), oWithUsers.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
primaryAm := oidc.TestAuthMethod(t, conn, databaseWrap, oWithUsers.PublicId, oidc.ActivePublicState, "alice-rp", "fido",
|
||||
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://alice-eve-smith.com")[0]),
|
||||
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "http://localhost:9200")[0]),
|
||||
oidc.WithSigningAlgs(oidc.RS256),
|
||||
)
|
||||
iam.TestSetPrimaryAuthMethod(t, repo, oWithUsers, primaryAm.PublicId)
|
||||
|
||||
secondaryAm := password.TestAuthMethods(t, conn, oWithUsers.PublicId, 1)
|
||||
require.Len(t, secondaryAm, 1)
|
||||
|
||||
s, err := users.NewService(repoFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wantUsers []*pb.User
|
||||
|
||||
@@ -147,14 +200,27 @@ func TestList(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
u, err := repo.CreateUser(context.Background(), newU)
|
||||
require.NoError(t, err)
|
||||
oidcAcct := oidc.TestAccount(t, conn, primaryAm, fmt.Sprintf("alice+%d", i), oidc.WithFullName("Alice Eve Smith"), oidc.WithEmail("alice@smith.com"))
|
||||
pwAcct := password.TestAccount(t, conn, secondaryAm[0].PublicId, fmt.Sprintf("alice+%d", i))
|
||||
|
||||
added, err := repo.AddUserAccounts(ctx, u.PublicId, u.Version, []string{oidcAcct.PublicId, pwAcct.PublicId})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, added, 2)
|
||||
|
||||
u, _, err = repo.LookupUser(ctx, u.PublicId)
|
||||
require.NoError(t, err)
|
||||
wantUsers = append(wantUsers, &pb.User{
|
||||
Id: u.GetPublicId(),
|
||||
ScopeId: u.GetScopeId(),
|
||||
Scope: &scopes.ScopeInfo{Id: u.GetScopeId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: u.GetUpdateTime().GetTimestamp(),
|
||||
Version: 1,
|
||||
Version: 2,
|
||||
AuthorizedActions: []string{"read", "update", "delete", "add-accounts", "set-accounts", "remove-accounts"},
|
||||
LoginName: oidcAcct.GetSubject(),
|
||||
FullName: oidcAcct.GetFullName(),
|
||||
Email: oidcAcct.GetEmail(),
|
||||
PrimaryAccountId: oidcAcct.GetPublicId(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -215,13 +281,35 @@ func TestList(t *testing.T) {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListUsers(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
}
|
||||
gotUsers := sortableUsers{
|
||||
users: got.GetItems(),
|
||||
}
|
||||
resUsers := sortableUsers{
|
||||
users: tc.res.GetItems(),
|
||||
}
|
||||
if got != nil {
|
||||
sort.Sort(gotUsers)
|
||||
got.Items = gotUsers.users
|
||||
}
|
||||
if tc.res != nil {
|
||||
sort.Sort(resUsers)
|
||||
tc.res.Items = resUsers.users
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "ListUsers(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type sortableUsers struct {
|
||||
users []*pb.User
|
||||
}
|
||||
|
||||
func (s sortableUsers) Len() int { return len(s.users) }
|
||||
func (s sortableUsers) Less(i, j int) bool { return s.users[i].GetId() < s.users[j].GetId() }
|
||||
func (s sortableUsers) Swap(i, j int) { s.users[i], s.users[j] = s.users[j], s.users[i] }
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
u, repoFn := createDefaultUserAndRepo(t)
|
||||
u, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
|
||||
s, err := users.NewService(repoFn)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
@@ -268,7 +356,7 @@ func TestDelete(t *testing.T) {
|
||||
|
||||
func TestDelete_twice(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
u, repoFn := createDefaultUserAndRepo(t)
|
||||
u, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
|
||||
s, err := users.NewService(repoFn)
|
||||
require.NoError(err, "Error when getting new user service")
|
||||
@@ -284,9 +372,8 @@ func TestDelete_twice(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
defaultUser, repoFn := createDefaultUserAndRepo(t)
|
||||
defaultCreated, err := ptypes.Timestamp(defaultUser.GetCreateTime().GetTimestamp())
|
||||
require.NoError(t, err, "Error converting proto to timestamp.")
|
||||
defaultUser, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
defaultCreated := defaultUser.GetCreateTime().GetTimestamp().AsTime()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -345,7 +432,7 @@ func TestCreate(t *testing.T) {
|
||||
name: "Can't specify Created Time",
|
||||
req: &pbs.CreateUserRequest{Item: &pb.User{
|
||||
ScopeId: defaultUser.GetScopeId(),
|
||||
CreatedTime: ptypes.TimestampNow(),
|
||||
CreatedTime: timestamppb.Now(),
|
||||
}},
|
||||
res: nil,
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
@@ -354,7 +441,7 @@ func TestCreate(t *testing.T) {
|
||||
name: "Can't specify Update Time",
|
||||
req: &pbs.CreateUserRequest{Item: &pb.User{
|
||||
ScopeId: defaultUser.GetScopeId(),
|
||||
UpdatedTime: ptypes.TimestampNow(),
|
||||
UpdatedTime: timestamppb.Now(),
|
||||
}},
|
||||
res: nil,
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
@@ -374,10 +461,8 @@ func TestCreate(t *testing.T) {
|
||||
if got != nil {
|
||||
assert.Contains(got.GetUri(), tc.res.Uri)
|
||||
assert.True(strings.HasPrefix(got.GetItem().GetId(), iam.UserPrefix+"_"))
|
||||
gotCreateTime, err := ptypes.Timestamp(got.GetItem().GetCreatedTime())
|
||||
require.NoError(err, "Error converting proto to timestamp.")
|
||||
gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime())
|
||||
require.NoError(err, "Error converting proto to timestamp.")
|
||||
gotCreateTime := got.GetItem().GetCreatedTime().AsTime()
|
||||
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
|
||||
// Verify it is a user created after the test setup's default user
|
||||
assert.True(gotCreateTime.After(defaultCreated), "New user should have been created after default user. Was created %v, which is after %v", gotCreateTime, defaultCreated)
|
||||
assert.True(gotUpdateTime.After(defaultCreated), "New user should have been updated after default user. Was updated %v, which is after %v", gotUpdateTime, defaultCreated)
|
||||
@@ -393,12 +478,11 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
u, repoFn := createDefaultUserAndRepo(t)
|
||||
u, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
tested, err := users.NewService(repoFn)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
created, err := ptypes.Timestamp(u.GetCreateTime().GetTimestamp())
|
||||
require.NoError(t, err, "Error converting proto to timestamp")
|
||||
created := u.GetCreateTime().GetTimestamp().AsTime()
|
||||
toMerge := &pbs.UpdateUserRequest{
|
||||
Id: u.GetPublicId(),
|
||||
}
|
||||
@@ -602,7 +686,7 @@ func TestUpdate(t *testing.T) {
|
||||
Paths: []string{"created_time"},
|
||||
},
|
||||
Item: &pb.User{
|
||||
CreatedTime: ptypes.TimestampNow(),
|
||||
CreatedTime: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -615,7 +699,7 @@ func TestUpdate(t *testing.T) {
|
||||
Paths: []string{"updated_time"},
|
||||
},
|
||||
Item: &pb.User{
|
||||
UpdatedTime: ptypes.TimestampNow(),
|
||||
UpdatedTime: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
res: nil,
|
||||
@@ -649,8 +733,7 @@ func TestUpdate(t *testing.T) {
|
||||
|
||||
if got != nil {
|
||||
assert.NotNilf(tc.res, "Expected UpdateUser response to be nil, but was %v", got)
|
||||
gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime())
|
||||
require.NoError(err, "Error converting proto to timestamp")
|
||||
gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime()
|
||||
// Verify it is a user updated after it was created
|
||||
assert.True(gotUpdateTime.After(created), "Updated user should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
|
||||
|
||||
@@ -676,10 +759,16 @@ func TestAddAccount(t *testing.T) {
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
||||
accts := password.TestAccounts(t, conn, amId, 3)
|
||||
acctCnt := 3
|
||||
accts := make([]*password.Account, 0, acctCnt)
|
||||
for i := 0; i < acctCnt; i++ {
|
||||
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
||||
newAcct := password.TestAccount(t, conn, amId, "name1")
|
||||
accts = append(accts, newAcct)
|
||||
}
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
oidcAm := oidc.TestAuthMethod(
|
||||
t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
@@ -733,7 +822,7 @@ func TestAddAccount(t *testing.T) {
|
||||
{
|
||||
name: "Add empty on populated user",
|
||||
setup: func(u *iam.User) {
|
||||
iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
_, err := iamRepo.SetUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
u.Version = u.Version + 1
|
||||
@@ -815,10 +904,16 @@ func TestSetAccount(t *testing.T) {
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
||||
accts := password.TestAccounts(t, conn, amId, 3)
|
||||
acctCnt := 3
|
||||
accts := make([]*password.Account, 0, acctCnt)
|
||||
for i := 0; i < acctCnt; i++ {
|
||||
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
||||
newAcct := password.TestAccount(t, conn, amId, "name1")
|
||||
accts = append(accts, newAcct)
|
||||
}
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
oidcAm := oidc.TestAuthMethod(
|
||||
t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
@@ -850,7 +945,7 @@ func TestSetAccount(t *testing.T) {
|
||||
{
|
||||
name: "Set account on populated user",
|
||||
setup: func(u *iam.User) {
|
||||
iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
_, err := iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
[]string{accts[0].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
u.Version = u.Version + 1
|
||||
@@ -861,7 +956,7 @@ func TestSetAccount(t *testing.T) {
|
||||
{
|
||||
name: "Set duplicate account on populated user",
|
||||
setup: func(u *iam.User) {
|
||||
iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
_, err := iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
[]string{accts[0].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
u.Version = u.Version + 1
|
||||
@@ -872,7 +967,7 @@ func TestSetAccount(t *testing.T) {
|
||||
{
|
||||
name: "Set empty on populated user",
|
||||
setup: func(u *iam.User) {
|
||||
iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
_, err := iamRepo.AddUserAccounts(context.Background(), u.GetPublicId(), u.GetVersion(),
|
||||
[]string{accts[0].GetPublicId(), accts[1].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
u.Version = u.Version + 1
|
||||
@@ -886,7 +981,7 @@ func TestSetAccount(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
||||
defer func() {
|
||||
iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
||||
_, _ = iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
||||
}()
|
||||
|
||||
tc.setup(usr)
|
||||
@@ -955,8 +1050,13 @@ func TestRemoveAccount(t *testing.T) {
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
||||
accts := password.TestAccounts(t, conn, amId, 3)
|
||||
acctCnt := 3
|
||||
accts := make([]*password.Account, 0, acctCnt)
|
||||
for i := 0; i < acctCnt; i++ {
|
||||
amId := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0].GetPublicId()
|
||||
newAcct := password.TestAccount(t, conn, amId, "name1")
|
||||
accts = append(accts, newAcct)
|
||||
}
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
@@ -1043,7 +1143,7 @@ func TestRemoveAccount(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
usr := iam.TestUser(t, iamRepo, o.GetPublicId())
|
||||
defer func() {
|
||||
iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
||||
_, _ = iamRepo.DeleteUser(context.Background(), usr.GetPublicId())
|
||||
}()
|
||||
tc.setup(usr)
|
||||
req := &pbs.RemoveUserAccountsRequest{
|
||||
|
||||
@@ -137,7 +137,7 @@ func TestSessionParams(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, ia
|
||||
require.NoError(err)
|
||||
|
||||
authMethod := password.TestAuthMethods(t, conn, org.PublicId, 1)[0]
|
||||
acct := password.TestAccounts(t, conn, authMethod.GetPublicId(), 1)[0]
|
||||
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "name1")
|
||||
user := iam.TestUser(t, iamRepo, org.PublicId, iam.WithAccountIds(acct.PublicId))
|
||||
|
||||
authTokenRepo, err := authtoken.NewRepository(rw, rw, kms)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/accounts"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/api/users"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller"
|
||||
@@ -14,6 +15,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const global = "global"
|
||||
|
||||
func TestCustom(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
tc := controller.NewTestController(t, nil)
|
||||
@@ -24,13 +27,18 @@ func TestCustom(t *testing.T) {
|
||||
client.SetToken(token.Token)
|
||||
uClient := users.NewClient(client)
|
||||
aClient := accounts.NewClient(client)
|
||||
amClient := authmethods.NewClient(client)
|
||||
|
||||
usr1, err := uClient.Create(tc.Context(), "global")
|
||||
userAm, err := amClient.Create(tc.Context(), "password", global,
|
||||
authmethods.WithName("bar"))
|
||||
require.NoError(err)
|
||||
|
||||
usr1, err := uClient.Create(tc.Context(), global)
|
||||
require.NoError(err)
|
||||
|
||||
acct1, err := aClient.Create(tc.Context(), token.AuthMethodId, accounts.WithPasswordAccountLoginName("accountname"))
|
||||
require.NoError(err)
|
||||
acct2, err := aClient.Create(tc.Context(), token.AuthMethodId, accounts.WithPasswordAccountLoginName("accountname2"))
|
||||
acct2, err := aClient.Create(tc.Context(), userAm.Item.Id, accounts.WithPasswordAccountLoginName("accountname2"))
|
||||
require.NoError(err)
|
||||
|
||||
addResult, err := uClient.AddAccounts(tc.Context(), usr1.Item.Id, 0, []string{acct1.Item.Id, acct2.Item.Id}, users.WithAutomaticVersioning(true))
|
||||
|
||||
Reference in new issue
Block a user