add principal roles (users and groups) to iam (#163)
This commit is contained in:
23 files changed
+3248
-53
No files matched your search
@@ -67,6 +67,9 @@ protobuild:
|
||||
@protoc --proto_path=internal/proto/local --proto_path=internal/proto/third_party --swagger_out=logtostderr=true,disable_default_errors=true,include_package_in_tags=true,fqn_for_swagger_name=true,allow_merge,merge_file_name=controller:internal/gen/. internal/proto/local/controller/api/services/v1/*.proto
|
||||
@protoc-go-inject-tag -input=./internal/oplog/store/oplog.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/oplog/oplog_test/oplog_test.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/group.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/role.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/principal_role.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/role.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/user.pb.go
|
||||
@protoc-go-inject-tag -input=./internal/iam/store/scope.pb.go
|
||||
|
||||
@@ -245,14 +245,14 @@ values
|
||||
('iam_group_member_user', 1),
|
||||
('iam_role', 1),
|
||||
('iam_role_grant', 1),
|
||||
('iam_role_group', 1),
|
||||
('iam_role_user', 1),
|
||||
('iam_group_role', 1),
|
||||
('iam_user_role', 1),
|
||||
('db_test_user', 1),
|
||||
('db_test_car', 1),
|
||||
('db_test_rental', 1),
|
||||
('db_test_scooter', 1),
|
||||
('auth_account', 1);
|
||||
;
|
||||
('auth_account', 1),
|
||||
('iam_principal_role', 1);
|
||||
|
||||
|
||||
commit;
|
||||
@@ -418,11 +418,17 @@ drop table iam_scope_organization cascade;
|
||||
drop table iam_scope cascade;
|
||||
drop table iam_scope_type_enm cascade;
|
||||
drop table iam_role cascade;
|
||||
|
||||
drop view iam_principal_role cascade;
|
||||
drop table iam_group_role cascade;
|
||||
drop table iam_user_role cascade;
|
||||
|
||||
drop function iam_sub_names cascade;
|
||||
drop function iam_immutable_scope_type_func cascade;
|
||||
drop function iam_sub_scopes_func cascade;
|
||||
drop function iam_immutable_role cascade;
|
||||
drop function iam_user_role_scope_check cascade;
|
||||
drop function iam_group_role_scope_check cascade;
|
||||
drop function immutable_scope_id_func cascade;
|
||||
|
||||
COMMIT;
|
||||
|
||||
@@ -443,7 +449,24 @@ values
|
||||
('organization'),
|
||||
('project');
|
||||
|
||||
|
||||
|
||||
create or replace function
|
||||
iam_immutable_scope_id_func()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.scope_id is distinct from old.scope_id then
|
||||
raise exception 'scope_id cannot be set to %', new.scope_id;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
comment on function
|
||||
iam_immutable_scope_id_func()
|
||||
is
|
||||
'function used in before update triggers to make scope_id column is immutable';
|
||||
|
||||
create table iam_scope (
|
||||
public_id wt_public_id primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -573,7 +596,6 @@ before
|
||||
update on iam_scope
|
||||
for each row execute procedure iam_sub_names();
|
||||
|
||||
|
||||
create table iam_user (
|
||||
public_id wt_public_id not null primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -607,6 +629,11 @@ before
|
||||
insert on iam_user
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_scope_id_user
|
||||
before
|
||||
update on iam_user
|
||||
for each row execute procedure iam_immutable_scope_id_func();
|
||||
|
||||
create table iam_role (
|
||||
public_id wt_public_id not null primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -615,9 +642,23 @@ create table iam_role (
|
||||
description text,
|
||||
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
|
||||
unique(name, scope_id),
|
||||
disabled boolean not null default false
|
||||
disabled boolean not null default false,
|
||||
-- version allows optimistic locking of the role when modifying the role
|
||||
-- itself and when modifying dependent items like principal roles.
|
||||
-- TODO (jlambert 6/2020) add before update trigger to automatically
|
||||
-- increment the version when needed. This trigger can be addded when PR
|
||||
-- #126 is merged and update_version_column() is available.
|
||||
version bigint not null default 1,
|
||||
|
||||
-- add unique index so a composite fk can be declared.
|
||||
unique(scope_id, public_id)
|
||||
);
|
||||
|
||||
create trigger
|
||||
update_version_column
|
||||
after update on iam_role
|
||||
for each row execute procedure update_version_column();
|
||||
|
||||
create trigger
|
||||
update_time_column
|
||||
before update on iam_role
|
||||
@@ -643,7 +684,9 @@ create table iam_group (
|
||||
description text,
|
||||
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
|
||||
unique(name, scope_id),
|
||||
disabled boolean not null default false
|
||||
disabled boolean not null default false,
|
||||
-- add unique index so a composite fk can be declared.
|
||||
unique(scope_id, public_id)
|
||||
);
|
||||
|
||||
create trigger
|
||||
@@ -662,7 +705,147 @@ create trigger
|
||||
before
|
||||
insert on iam_group
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_scope_id_group
|
||||
before
|
||||
update on iam_group
|
||||
for each row execute procedure iam_immutable_scope_id_func();
|
||||
|
||||
-- iam_user_role contains roles that have been assigned to users. Users can only
|
||||
-- be assigned roles which are within its organization, or the role is within a project within its
|
||||
-- organization. There's no way to declare this constraint, so it will be
|
||||
-- maintained with a before insert trigger using iam_user_role_scope_check().
|
||||
-- The rows in this table must be immutable after insert, which will be ensured
|
||||
-- with a before update trigger using iam_immutable_role().
|
||||
create table iam_user_role (
|
||||
create_time wt_timestamp,
|
||||
scope_id wt_public_id not null,
|
||||
role_id wt_public_id not null,
|
||||
principal_id wt_public_id not null references iam_user(public_id) on delete cascade on update cascade,
|
||||
primary key (role_id, principal_id),
|
||||
foreign key (scope_id, role_id)
|
||||
references iam_role(scope_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade
|
||||
);
|
||||
|
||||
-- iam_group_role contains roles that have been assigned to groups. Groups can
|
||||
-- only be assigned roles which are within its scope (organization or project)
|
||||
-- and that integrity can be maintained with a foreign key. The rows in this
|
||||
-- table must be immutable after insert, which will be ensured with a before
|
||||
-- update trigger using iam_immutable_role().
|
||||
create table iam_group_role (
|
||||
create_time wt_timestamp,
|
||||
scope_id wt_public_id not null,
|
||||
role_id wt_public_id not null,
|
||||
principal_id wt_public_id not null,
|
||||
primary key (role_id, principal_id),
|
||||
foreign key (scope_id, role_id)
|
||||
references iam_role(scope_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
foreign key (scope_id, principal_id)
|
||||
references iam_group(scope_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade
|
||||
);
|
||||
|
||||
-- iam_principle_role provides a consolidated view all principal roles assigned
|
||||
-- (user and group roles).
|
||||
create view iam_principal_role as
|
||||
select
|
||||
-- intentionally using * to specify the view which requires that the concrete role assignment tables match
|
||||
*, 'user' as type
|
||||
from iam_user_role
|
||||
union
|
||||
select
|
||||
-- intentionally using * to specify the view which requires that the concrete role assignment tables match
|
||||
*, 'group' as type
|
||||
from iam_group_role;
|
||||
|
||||
-- iam_user_role_scope_check() ensures that the user is only assigned roles
|
||||
-- which are within its organization, or the role is within a project within its
|
||||
-- organization.
|
||||
create or replace function
|
||||
iam_user_role_scope_check()
|
||||
returns trigger
|
||||
as $$
|
||||
declare cnt int;
|
||||
begin
|
||||
select count(*) into cnt
|
||||
from iam_user
|
||||
where
|
||||
public_id = new.principal_id and
|
||||
scope_id in(
|
||||
-- check to see if they have the same org scope
|
||||
select s.public_id
|
||||
from iam_scope s, iam_role r
|
||||
where s.public_id = r.scope_id and r.public_id = new.role_id and r.scope_id = new.scope_id
|
||||
union
|
||||
-- check to see if the role has a parent that's the same org
|
||||
select s.parent_id as public_id
|
||||
from iam_scope s, iam_role r
|
||||
where s.public_id = r.scope_id and r.public_id = new.role_id and r.scope_id = new.scope_id
|
||||
);
|
||||
if cnt = 0 then
|
||||
raise exception 'user and role do not belong to the same organization';
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
-- iam_immutable_role() ensures that roles assigned to principals are immutable.
|
||||
create or replace function
|
||||
iam_immutable_role()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
if row(new.*) is distinct from row(old.*) then
|
||||
raise exception 'roles are immutable';
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger iam_user_role_scope_check
|
||||
before
|
||||
insert on iam_user_role
|
||||
for each row execute procedure iam_user_role_scope_check();
|
||||
|
||||
create trigger immutable_role
|
||||
before
|
||||
update on iam_user_role
|
||||
for each row execute procedure iam_immutable_role();
|
||||
|
||||
create trigger
|
||||
immutable_create_time
|
||||
before
|
||||
update on iam_user_role
|
||||
for each row execute procedure immutable_create_time_func();
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on iam_user_role
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_role
|
||||
before
|
||||
update on iam_group_role
|
||||
for each row execute procedure iam_immutable_role();
|
||||
|
||||
create trigger
|
||||
immutable_create_time
|
||||
before
|
||||
update on iam_group_role
|
||||
for each row execute procedure immutable_create_time_func();
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on iam_group_role
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
commit;
|
||||
|
||||
`),
|
||||
|
||||
@@ -94,14 +94,14 @@ values
|
||||
('iam_group_member_user', 1),
|
||||
('iam_role', 1),
|
||||
('iam_role_grant', 1),
|
||||
('iam_role_group', 1),
|
||||
('iam_role_user', 1),
|
||||
('iam_group_role', 1),
|
||||
('iam_user_role', 1),
|
||||
('db_test_user', 1),
|
||||
('db_test_car', 1),
|
||||
('db_test_rental', 1),
|
||||
('db_test_scooter', 1),
|
||||
('auth_account', 1);
|
||||
;
|
||||
('auth_account', 1),
|
||||
('iam_principal_role', 1);
|
||||
|
||||
|
||||
commit;
|
||||
|
||||
@@ -7,10 +7,16 @@ drop table iam_scope_organization cascade;
|
||||
drop table iam_scope cascade;
|
||||
drop table iam_scope_type_enm cascade;
|
||||
drop table iam_role cascade;
|
||||
|
||||
drop view iam_principal_role cascade;
|
||||
drop table iam_group_role cascade;
|
||||
drop table iam_user_role cascade;
|
||||
|
||||
drop function iam_sub_names cascade;
|
||||
drop function iam_immutable_scope_type_func cascade;
|
||||
drop function iam_sub_scopes_func cascade;
|
||||
drop function iam_immutable_role cascade;
|
||||
drop function iam_user_role_scope_check cascade;
|
||||
drop function iam_group_role_scope_check cascade;
|
||||
drop function immutable_scope_id_func cascade;
|
||||
|
||||
COMMIT;
|
||||
@@ -10,7 +10,24 @@ values
|
||||
('organization'),
|
||||
('project');
|
||||
|
||||
|
||||
|
||||
create or replace function
|
||||
iam_immutable_scope_id_func()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
if new.scope_id is distinct from old.scope_id then
|
||||
raise exception 'scope_id cannot be set to %', new.scope_id;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
comment on function
|
||||
iam_immutable_scope_id_func()
|
||||
is
|
||||
'function used in before update triggers to make scope_id column is immutable';
|
||||
|
||||
create table iam_scope (
|
||||
public_id wt_public_id primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -140,7 +157,6 @@ before
|
||||
update on iam_scope
|
||||
for each row execute procedure iam_sub_names();
|
||||
|
||||
|
||||
create table iam_user (
|
||||
public_id wt_public_id not null primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -174,6 +190,11 @@ before
|
||||
insert on iam_user
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_scope_id_user
|
||||
before
|
||||
update on iam_user
|
||||
for each row execute procedure iam_immutable_scope_id_func();
|
||||
|
||||
create table iam_role (
|
||||
public_id wt_public_id not null primary key,
|
||||
create_time wt_timestamp,
|
||||
@@ -182,9 +203,23 @@ create table iam_role (
|
||||
description text,
|
||||
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
|
||||
unique(name, scope_id),
|
||||
disabled boolean not null default false
|
||||
disabled boolean not null default false,
|
||||
-- version allows optimistic locking of the role when modifying the role
|
||||
-- itself and when modifying dependent items like principal roles.
|
||||
-- TODO (jlambert 6/2020) add before update trigger to automatically
|
||||
-- increment the version when needed. This trigger can be addded when PR
|
||||
-- #126 is merged and update_version_column() is available.
|
||||
version bigint not null default 1,
|
||||
|
||||
-- add unique index so a composite fk can be declared.
|
||||
unique(scope_id, public_id)
|
||||
);
|
||||
|
||||
create trigger
|
||||
update_version_column
|
||||
after update on iam_role
|
||||
for each row execute procedure update_version_column();
|
||||
|
||||
create trigger
|
||||
update_time_column
|
||||
before update on iam_role
|
||||
@@ -210,7 +245,9 @@ create table iam_group (
|
||||
description text,
|
||||
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
|
||||
unique(name, scope_id),
|
||||
disabled boolean not null default false
|
||||
disabled boolean not null default false,
|
||||
-- add unique index so a composite fk can be declared.
|
||||
unique(scope_id, public_id)
|
||||
);
|
||||
|
||||
create trigger
|
||||
@@ -229,5 +266,145 @@ create trigger
|
||||
before
|
||||
insert on iam_group
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_scope_id_group
|
||||
before
|
||||
update on iam_group
|
||||
for each row execute procedure iam_immutable_scope_id_func();
|
||||
|
||||
-- iam_user_role contains roles that have been assigned to users. Users can only
|
||||
-- be assigned roles which are within its organization, or the role is within a project within its
|
||||
-- organization. There's no way to declare this constraint, so it will be
|
||||
-- maintained with a before insert trigger using iam_user_role_scope_check().
|
||||
-- The rows in this table must be immutable after insert, which will be ensured
|
||||
-- with a before update trigger using iam_immutable_role().
|
||||
create table iam_user_role (
|
||||
create_time wt_timestamp,
|
||||
scope_id wt_public_id not null,
|
||||
role_id wt_public_id not null,
|
||||
principal_id wt_public_id not null references iam_user(public_id) on delete cascade on update cascade,
|
||||
primary key (role_id, principal_id),
|
||||
foreign key (scope_id, role_id)
|
||||
references iam_role(scope_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade
|
||||
);
|
||||
|
||||
-- iam_group_role contains roles that have been assigned to groups. Groups can
|
||||
-- only be assigned roles which are within its scope (organization or project)
|
||||
-- and that integrity can be maintained with a foreign key. The rows in this
|
||||
-- table must be immutable after insert, which will be ensured with a before
|
||||
-- update trigger using iam_immutable_role().
|
||||
create table iam_group_role (
|
||||
create_time wt_timestamp,
|
||||
scope_id wt_public_id not null,
|
||||
role_id wt_public_id not null,
|
||||
principal_id wt_public_id not null,
|
||||
primary key (role_id, principal_id),
|
||||
foreign key (scope_id, role_id)
|
||||
references iam_role(scope_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
foreign key (scope_id, principal_id)
|
||||
references iam_group(scope_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade
|
||||
);
|
||||
|
||||
-- iam_principle_role provides a consolidated view all principal roles assigned
|
||||
-- (user and group roles).
|
||||
create view iam_principal_role as
|
||||
select
|
||||
-- intentionally using * to specify the view which requires that the concrete role assignment tables match
|
||||
*, 'user' as type
|
||||
from iam_user_role
|
||||
union
|
||||
select
|
||||
-- intentionally using * to specify the view which requires that the concrete role assignment tables match
|
||||
*, 'group' as type
|
||||
from iam_group_role;
|
||||
|
||||
-- iam_user_role_scope_check() ensures that the user is only assigned roles
|
||||
-- which are within its organization, or the role is within a project within its
|
||||
-- organization.
|
||||
create or replace function
|
||||
iam_user_role_scope_check()
|
||||
returns trigger
|
||||
as $$
|
||||
declare cnt int;
|
||||
begin
|
||||
select count(*) into cnt
|
||||
from iam_user
|
||||
where
|
||||
public_id = new.principal_id and
|
||||
scope_id in(
|
||||
-- check to see if they have the same org scope
|
||||
select s.public_id
|
||||
from iam_scope s, iam_role r
|
||||
where s.public_id = r.scope_id and r.public_id = new.role_id and r.scope_id = new.scope_id
|
||||
union
|
||||
-- check to see if the role has a parent that's the same org
|
||||
select s.parent_id as public_id
|
||||
from iam_scope s, iam_role r
|
||||
where s.public_id = r.scope_id and r.public_id = new.role_id and r.scope_id = new.scope_id
|
||||
);
|
||||
if cnt = 0 then
|
||||
raise exception 'user and role do not belong to the same organization';
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
-- iam_immutable_role() ensures that roles assigned to principals are immutable.
|
||||
create or replace function
|
||||
iam_immutable_role()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
if row(new.*) is distinct from row(old.*) then
|
||||
raise exception 'roles are immutable';
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger iam_user_role_scope_check
|
||||
before
|
||||
insert on iam_user_role
|
||||
for each row execute procedure iam_user_role_scope_check();
|
||||
|
||||
create trigger immutable_role
|
||||
before
|
||||
update on iam_user_role
|
||||
for each row execute procedure iam_immutable_role();
|
||||
|
||||
create trigger
|
||||
immutable_create_time
|
||||
before
|
||||
update on iam_user_role
|
||||
for each row execute procedure immutable_create_time_func();
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on iam_user_role
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger immutable_role
|
||||
before
|
||||
update on iam_group_role
|
||||
for each row execute procedure iam_immutable_role();
|
||||
|
||||
create trigger
|
||||
immutable_create_time
|
||||
before
|
||||
update on iam_group_role
|
||||
for each row execute procedure immutable_create_time_func();
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on iam_group_role
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
commit;
|
||||
@@ -34,6 +34,8 @@ type Options struct {
|
||||
|
||||
// WithVersion must be accessible from other packages
|
||||
WithVersion int
|
||||
|
||||
withSkipVetForWrite bool
|
||||
}
|
||||
|
||||
type oplogOpts struct {
|
||||
@@ -124,3 +126,11 @@ func WithVersion(version int) Option {
|
||||
o.WithVersion = version
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkipVetForWrite provides an option to allow skipping vet checks to allow
|
||||
// testing lower-level SQL triggers and constraints
|
||||
func WithSkipVetForWrite(enable bool) Option {
|
||||
return func(o *Options) {
|
||||
o.withSkipVetForWrite = enable
|
||||
}
|
||||
}
|
||||
@@ -132,4 +132,18 @@ func Test_getOpts(t *testing.T) {
|
||||
testOpts.WithVersion = 2
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("WithSkipVetForWrite", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// test default of false
|
||||
opts := GetOpts()
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withSkipVetForWrite = false
|
||||
assert.Equal(opts, testOpts)
|
||||
|
||||
// try setting to false
|
||||
opts = GetOpts(WithSkipVetForWrite(true))
|
||||
testOpts = getDefaultOptions()
|
||||
testOpts.withSkipVetForWrite = true
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -241,9 +241,11 @@ func (rw *Db) Create(ctx context.Context, i interface{}, opt ...Option) error {
|
||||
// db to manage them
|
||||
setFieldsToNil(i, []string{"CreateTime", "UpdateTime"})
|
||||
|
||||
if vetter, ok := i.(VetForWriter); ok {
|
||||
if err := vetter.VetForWrite(ctx, rw, CreateOp); err != nil {
|
||||
return fmt.Errorf("create: vet for write failed %w", err)
|
||||
if !opts.withSkipVetForWrite {
|
||||
if vetter, ok := i.(VetForWriter); ok {
|
||||
if err := vetter.VetForWrite(ctx, rw, CreateOp); err != nil {
|
||||
return fmt.Errorf("create: vet for write failed %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
var ticket *store.Ticket
|
||||
@@ -407,9 +409,11 @@ func (rw *Db) Update(ctx context.Context, i interface{}, fieldMaskPaths []string
|
||||
return NoRowsAffected, fmt.Errorf("update: oplog validation failed %w", err)
|
||||
}
|
||||
}
|
||||
if vetter, ok := i.(VetForWriter); ok {
|
||||
if err := vetter.VetForWrite(ctx, rw, UpdateOp, WithFieldMaskPaths(fieldMaskPaths), WithNullPaths(setToNullPaths)); err != nil {
|
||||
return NoRowsAffected, fmt.Errorf("update: vet for write failed %w", err)
|
||||
if !opts.withSkipVetForWrite {
|
||||
if vetter, ok := i.(VetForWriter); ok {
|
||||
if err := vetter.VetForWrite(ctx, rw, UpdateOp, WithFieldMaskPaths(fieldMaskPaths), WithNullPaths(setToNullPaths)); err != nil {
|
||||
return NoRowsAffected, fmt.Errorf("update: vet for write failed %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
var ticket *store.Ticket
|
||||
|
||||
+24
-14
@@ -14,24 +14,26 @@ type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withPublicId string
|
||||
withName string
|
||||
withScope *Scope
|
||||
withDescription string
|
||||
withParentId *string
|
||||
withLimit int
|
||||
withAutoVivify bool
|
||||
withPublicId string
|
||||
withName string
|
||||
withScope *Scope
|
||||
withDescription string
|
||||
withParentId *string
|
||||
withLimit int
|
||||
withAutoVivify bool
|
||||
withSkipVetForWrite bool
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{
|
||||
withPublicId: "",
|
||||
withScope: nil,
|
||||
withDescription: "",
|
||||
withName: "",
|
||||
withParentId: nil,
|
||||
withLimit: 0,
|
||||
withAutoVivify: false,
|
||||
withPublicId: "",
|
||||
withScope: nil,
|
||||
withDescription: "",
|
||||
withName: "",
|
||||
withParentId: nil,
|
||||
withLimit: 0,
|
||||
withAutoVivify: false,
|
||||
withSkipVetForWrite: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,3 +81,11 @@ func WithAutoVivify(enable bool) Option {
|
||||
o.withAutoVivify = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkipVetForWrite provides an option to allow skipping vet checks to allow
|
||||
// testing lower-level SQL triggers and constraints
|
||||
func WithSkipVetForWrite(enable bool) Option {
|
||||
return func(o *options) {
|
||||
o.withSkipVetForWrite = enable
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// RoleType defines the possible types for roles.
|
||||
type RoleType uint32
|
||||
|
||||
const (
|
||||
UnknownRoleType RoleType = 0
|
||||
UserRoleType RoleType = 1
|
||||
GroupRoleType RoleType = 2
|
||||
)
|
||||
|
||||
// String returns a string representation of the role type.
|
||||
func (r RoleType) String() string {
|
||||
return [...]string{
|
||||
"unknown",
|
||||
"user",
|
||||
"group",
|
||||
}[r]
|
||||
}
|
||||
|
||||
const principalRoleViewDefaultTable = "iam_principal_role"
|
||||
|
||||
// PrincipalRole declares a common interface for all roles assigned to resources (Users and Groups).
|
||||
type PrincipalRole interface {
|
||||
GetRoleId() string
|
||||
GetPrincipalId() string
|
||||
GetType() string
|
||||
GetScopeId() string
|
||||
Clone() interface{}
|
||||
}
|
||||
|
||||
// principalRoleView provides a common way to return roles regardless of their underlying type.
|
||||
type principalRoleView struct {
|
||||
*store.PrincipalRoleView
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// TableName provides an overridden gorm table name for principal roles.
|
||||
func (v *principalRoleView) TableName() string {
|
||||
if v.tableName != "" {
|
||||
return v.tableName
|
||||
}
|
||||
return principalRoleViewDefaultTable
|
||||
}
|
||||
|
||||
// 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 (v *principalRoleView) SetTableName(n string) {
|
||||
switch n {
|
||||
case "":
|
||||
v.tableName = principalRoleViewDefaultTable
|
||||
default:
|
||||
v.tableName = n
|
||||
}
|
||||
}
|
||||
|
||||
func (v principalRoleView) Clone() interface{} {
|
||||
cp := proto.Clone(v.PrincipalRoleView)
|
||||
return &principalRoleView{
|
||||
PrincipalRoleView: cp.(*store.PrincipalRoleView),
|
||||
}
|
||||
}
|
||||
|
||||
// UserRole is a role assigned to a user
|
||||
type UserRole struct {
|
||||
*store.UserRole
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// ensure that UserRole implements the interfaces of: Clonable, AssignedRole and db.VetForWriter
|
||||
var _ Clonable = (*UserRole)(nil)
|
||||
var _ PrincipalRole = (*UserRole)(nil)
|
||||
var _ db.VetForWriter = (*UserRole)(nil)
|
||||
|
||||
// NewUserRole creates a new user role in memory. Users can be assigned roles
|
||||
// which are within its organization, or the role is within a project within its
|
||||
// organization. This relationship will not be enforced until the user role is
|
||||
// written to the database. No options are supported currently.
|
||||
func NewUserRole(scopeId, roleId, userId string, opt ...Option) (PrincipalRole, error) {
|
||||
if roleId == "" {
|
||||
return nil, fmt.Errorf("new user role: missing role id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if userId == "" {
|
||||
return nil, fmt.Errorf("new user role: missing user id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
return &UserRole{
|
||||
UserRole: &store.UserRole{
|
||||
PrincipalId: userId,
|
||||
RoleId: roleId,
|
||||
ScopeId: scopeId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetType returns the user role type.
|
||||
func (r *UserRole) GetType() string {
|
||||
return UserRoleType.String()
|
||||
}
|
||||
|
||||
func allocUserRole() UserRole {
|
||||
return UserRole{
|
||||
UserRole: &store.UserRole{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone creates a clone of the UserRole.
|
||||
func (r *UserRole) Clone() interface{} {
|
||||
cp := proto.Clone(r.UserRole)
|
||||
return &UserRole{
|
||||
UserRole: cp.(*store.UserRole),
|
||||
}
|
||||
}
|
||||
|
||||
// VetForWrite implements db.VetForWrite() interface for user roles. The
|
||||
// constraint between user and role scopes will be enforced by the database via
|
||||
// constraints and triggers.
|
||||
func (role *UserRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
||||
if role.RoleId == "" {
|
||||
return fmt.Errorf("new user role: missing role id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if role.PrincipalId == "" {
|
||||
return fmt.Errorf("new user role: missing user id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name for
|
||||
// user roles.
|
||||
func (r *UserRole) TableName() string {
|
||||
if r.tableName != "" {
|
||||
return r.tableName
|
||||
}
|
||||
return "iam_user_role"
|
||||
}
|
||||
|
||||
// SetTableName sets the tablename and satisfies the ReplayableMessage interface
|
||||
func (r *UserRole) SetTableName(n string) {
|
||||
if r.tableName != "" {
|
||||
r.tableName = n
|
||||
}
|
||||
}
|
||||
|
||||
// GroupRole is a role assigned to a group
|
||||
type GroupRole struct {
|
||||
*store.GroupRole
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// ensure that GroupRole implements the interfaces of: Clonable, AssignedRole and db.VetForWriter
|
||||
var _ Clonable = (*GroupRole)(nil)
|
||||
var _ PrincipalRole = (*GroupRole)(nil)
|
||||
var _ db.VetForWriter = (*GroupRole)(nil)
|
||||
|
||||
// NewGroupRole creates a new group role in memory. Groups can only be
|
||||
// assigned roles within its scope (org or project). This relationship will not
|
||||
// be enforced until the group role is written to the database. No options are
|
||||
// supported currently.
|
||||
func NewGroupRole(scopeId, roleId, groupId string, opt ...Option) (PrincipalRole, error) {
|
||||
if roleId == "" {
|
||||
return nil, fmt.Errorf("new group role: missing role id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if groupId == "" {
|
||||
return nil, fmt.Errorf("new group role: missing group id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
return &GroupRole{
|
||||
GroupRole: &store.GroupRole{
|
||||
PrincipalId: groupId,
|
||||
RoleId: roleId,
|
||||
ScopeId: scopeId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetType returns the group role type.
|
||||
func (r *GroupRole) GetType() string {
|
||||
return GroupRoleType.String()
|
||||
}
|
||||
|
||||
func allocGroupRole() GroupRole {
|
||||
return GroupRole{
|
||||
GroupRole: &store.GroupRole{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone creates a clone of the GroupRole.
|
||||
func (r *GroupRole) Clone() interface{} {
|
||||
cp := proto.Clone(r.GroupRole)
|
||||
return &GroupRole{
|
||||
GroupRole: cp.(*store.GroupRole),
|
||||
}
|
||||
}
|
||||
|
||||
// VetForWrite implements db.VetForWrite() interface for group roles. The
|
||||
// constraint between groups and role scopes will be enforced by the database via
|
||||
// constraints and triggers.
|
||||
func (role *GroupRole) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType, opt ...db.Option) error {
|
||||
if role.RoleId == "" {
|
||||
return fmt.Errorf("new group role: missing role id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if role.PrincipalId == "" {
|
||||
return fmt.Errorf("new group role: missing user id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name for
|
||||
// group roles.
|
||||
func (r *GroupRole) TableName() string {
|
||||
if r.tableName != "" {
|
||||
return r.tableName
|
||||
}
|
||||
return "iam_group_role"
|
||||
}
|
||||
|
||||
// SetTableName sets the tablename and satisfies the ReplayableMessage interface
|
||||
// for group roles.
|
||||
func (r *GroupRole) SetTableName(n string) {
|
||||
if r.tableName != "" {
|
||||
r.tableName = n
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/iam/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestNewUserRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
orgRole := TestRole(t, conn, org.PublicId)
|
||||
projRole := TestRole(t, conn, proj.PublicId)
|
||||
user := TestUser(t, conn, org.PublicId)
|
||||
|
||||
type args struct {
|
||||
roleId string
|
||||
userId string
|
||||
scopeId string
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want PrincipalRole
|
||||
wantErr bool
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "valid-org",
|
||||
args: args{
|
||||
roleId: orgRole.PublicId,
|
||||
userId: user.PublicId,
|
||||
scopeId: org.PublicId,
|
||||
},
|
||||
want: func() PrincipalRole {
|
||||
r := allocUserRole()
|
||||
r.RoleId = orgRole.PublicId
|
||||
r.PrincipalId = user.PublicId
|
||||
r.ScopeId = org.PublicId
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "valid-proj",
|
||||
args: args{
|
||||
roleId: projRole.PublicId,
|
||||
userId: user.PublicId,
|
||||
scopeId: proj.PublicId,
|
||||
},
|
||||
want: func() PrincipalRole {
|
||||
r := allocUserRole()
|
||||
r.RoleId = projRole.PublicId
|
||||
r.PrincipalId = user.PublicId
|
||||
r.ScopeId = proj.PublicId
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "empty-role-id",
|
||||
args: args{
|
||||
roleId: "",
|
||||
userId: user.PublicId,
|
||||
scopeId: org.PublicId,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "empty-user-id",
|
||||
args: args{
|
||||
roleId: orgRole.PublicId,
|
||||
userId: "",
|
||||
scopeId: org.PublicId,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewUserRole(tt.args.scopeId, tt.args.roleId, tt.args.userId, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.True(errors.Is(err, tt.wantIsErr))
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserRole_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
org2, proj2 := TestScopes(t, conn)
|
||||
type args struct {
|
||||
role *UserRole
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantDup bool
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "valid-with-org",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
principalRole, err := NewUserRole(org.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid-with-proj",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
principalRole, err := NewUserRole(proj.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-org",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
role := TestRole(t, conn, org2.PublicId)
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
principalRole, err := NewUserRole(org.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-proj",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
role := TestRole(t, conn, proj2.PublicId)
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
principalRole, err := NewUserRole(proj.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "bad-role-id",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
id := testId(t)
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
principalRole, err := NewUserRole(org.PublicId, id, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "create: failed pq: user and role do not belong to the same organization",
|
||||
},
|
||||
{
|
||||
name: "bad-user-id",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
id := testId(t)
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
principalRole, err := NewUserRole(proj.PublicId, role.PublicId, id)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "create: failed pq: user and role do not belong to the same organization",
|
||||
},
|
||||
{
|
||||
name: "missing-role-id",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
return &UserRole{
|
||||
UserRole: &store.UserRole{
|
||||
RoleId: "",
|
||||
PrincipalId: principal.PublicId,
|
||||
ScopeId: org.PublicId,
|
||||
},
|
||||
}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "create: vet for write failed new user role: missing role id invalid parameter",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-user-id",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
return &UserRole{
|
||||
UserRole: &store.UserRole{
|
||||
RoleId: role.PublicId,
|
||||
PrincipalId: "",
|
||||
ScopeId: org.PublicId,
|
||||
},
|
||||
}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "create: vet for write failed new user role: missing user id invalid parameter",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "dup-at-org",
|
||||
args: args{
|
||||
role: func() *UserRole {
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
principal := TestUser(t, conn, org.PublicId)
|
||||
principalRole, err := NewUserRole(org.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*UserRole)
|
||||
}(),
|
||||
},
|
||||
wantDup: true,
|
||||
wantErr: true,
|
||||
wantErrMsg: `create: failed pq: duplicate key value violates unique constraint "iam_user_role_pkey"`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
if tt.wantDup {
|
||||
r := tt.args.role.Clone().(*UserRole)
|
||||
err := w.Create(context.Background(), r)
|
||||
require.NoError(err)
|
||||
}
|
||||
r := tt.args.role.Clone().(*UserRole)
|
||||
err := w.Create(context.Background(), r)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.Contains(err.Error(), tt.wantErrMsg)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.True(errors.Is(err, tt.wantIsErr))
|
||||
}
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
|
||||
found := allocUserRole()
|
||||
err = w.LookupWhere(context.Background(), &found, "role_id = ? and principal_id = ?", r.RoleId, r.PrincipalId)
|
||||
require.NoError(err)
|
||||
assert.Equal(r, &found)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserRole_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, _ := TestScopes(t, conn)
|
||||
rw := db.New(conn)
|
||||
|
||||
t.Run("updates not allowed", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r := TestRole(t, conn, org.PublicId)
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
u2 := TestUser(t, conn, org.PublicId)
|
||||
userRole := TestUserRole(t, conn, org.PublicId, r.PublicId, u.PublicId)
|
||||
updateRole := userRole.Clone().(*UserRole)
|
||||
updateRole.PrincipalId = u2.PublicId
|
||||
updatedRows, err := rw.Update(context.Background(), updateRole, []string{"PrincipalId"}, nil)
|
||||
require.Error(err)
|
||||
assert.Equal(0, updatedRows)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRole_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
id := testId(t)
|
||||
org, _ := TestScopes(t, conn)
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
r := TestRole(t, conn, org.PublicId)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
role *UserRole
|
||||
wantRowsDeleted int
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
role: TestUserRole(t, conn, org.PublicId, r.PublicId, u.PublicId),
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 1,
|
||||
},
|
||||
{
|
||||
name: "bad-id",
|
||||
role: func() *UserRole { r := allocUserRole(); r.PrincipalId = id; r.RoleId = id; return &r }(),
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
deleteRole := allocUserRole()
|
||||
deleteRole.RoleId = tt.role.GetRoleId()
|
||||
deleteRole.PrincipalId = tt.role.GetPrincipalId()
|
||||
deletedRows, err := rw.Delete(context.Background(), &deleteRole)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
if tt.wantRowsDeleted == 0 {
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
return
|
||||
}
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
found := allocUserRole()
|
||||
err = rw.LookupWhere(context.Background(), &found, "role_id = ? and principal_id = ?", tt.role.GetRoleId(), tt.role.GetPrincipalId())
|
||||
require.Error(err)
|
||||
assert.True(errors.Is(db.ErrRecordNotFound, err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserRole_Clone(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
user := TestUser(t, conn, org.PublicId)
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
userRole := TestUserRole(t, conn, org.PublicId, role.PublicId, user.PublicId)
|
||||
cp := userRole.Clone()
|
||||
assert.True(proto.Equal(cp.(*UserRole).UserRole, userRole.UserRole))
|
||||
})
|
||||
t.Run("not-equal", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
role2 := TestRole(t, conn, proj.PublicId)
|
||||
userRole := TestUserRole(t, conn, org.PublicId, role.PublicId, user.PublicId)
|
||||
userRole2 := TestUserRole(t, conn, proj.PublicId, role2.PublicId, user.PublicId)
|
||||
cp := userRole.Clone()
|
||||
assert.True(!proto.Equal(cp.(*UserRole).UserRole, userRole2.UserRole))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRole_GetType(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
r := &UserRole{}
|
||||
ty := r.GetType()
|
||||
assert.Equal("user", ty)
|
||||
}
|
||||
|
||||
func TestNewGroupRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
orgRole := TestRole(t, conn, org.PublicId)
|
||||
projRole := TestRole(t, conn, proj.PublicId)
|
||||
group := TestGroup(t, conn, org.PublicId)
|
||||
|
||||
type args struct {
|
||||
roleId string
|
||||
groupId string
|
||||
scopeId string
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want PrincipalRole
|
||||
wantErr bool
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "valid-org",
|
||||
args: args{
|
||||
roleId: orgRole.PublicId,
|
||||
groupId: group.PublicId,
|
||||
scopeId: org.PublicId,
|
||||
},
|
||||
want: func() PrincipalRole {
|
||||
r := allocGroupRole()
|
||||
r.RoleId = orgRole.PublicId
|
||||
r.PrincipalId = group.PublicId
|
||||
r.ScopeId = org.PublicId
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "valid-proj",
|
||||
args: args{
|
||||
roleId: projRole.PublicId,
|
||||
groupId: group.PublicId,
|
||||
scopeId: proj.PublicId,
|
||||
},
|
||||
want: func() PrincipalRole {
|
||||
r := allocGroupRole()
|
||||
r.RoleId = projRole.PublicId
|
||||
r.PrincipalId = group.PublicId
|
||||
r.ScopeId = proj.PublicId
|
||||
return &r
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "empty-role-id",
|
||||
args: args{
|
||||
roleId: "",
|
||||
groupId: group.PublicId,
|
||||
scopeId: org.PublicId,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "empty-group-id",
|
||||
args: args{
|
||||
roleId: orgRole.PublicId,
|
||||
groupId: "",
|
||||
scopeId: org.PublicId,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewGroupRole(tt.args.scopeId, tt.args.roleId, tt.args.groupId, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.True(errors.Is(err, tt.wantIsErr))
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupRole_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
type args struct {
|
||||
role *GroupRole
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantDup bool
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "valid-with-org",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
principal := TestGroup(t, conn, org.PublicId)
|
||||
principalRole, err := NewGroupRole(org.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*GroupRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid-with-proj",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
principal := TestGroup(t, conn, proj.PublicId)
|
||||
principalRole, err := NewGroupRole(proj.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*GroupRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-role-id",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
id := testId(t)
|
||||
principal := TestGroup(t, conn, org.PublicId)
|
||||
principalRole, err := NewGroupRole(org.PublicId, id, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*GroupRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: `create: failed pq: insert or update on table "iam_group_role" violates foreign key constraint "iam_group_role_scope_id_role_id_fkey"`,
|
||||
},
|
||||
{
|
||||
name: "bad-user-id",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
id := testId(t)
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
principalRole, err := NewGroupRole(org.PublicId, role.PublicId, id)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*GroupRole)
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: `create: failed pq: insert or update on table "iam_group_role" violates foreign key constraint "iam_group_role_scope_id_role_id_fkey"`,
|
||||
},
|
||||
{
|
||||
name: "missing-role-id",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
principal := TestGroup(t, conn, org.PublicId)
|
||||
return &GroupRole{
|
||||
GroupRole: &store.GroupRole{
|
||||
RoleId: "",
|
||||
PrincipalId: principal.PublicId,
|
||||
ScopeId: org.PublicId,
|
||||
},
|
||||
}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "create: vet for write failed new group role: missing role id invalid parameter",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "missing-user-id",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
return &GroupRole{
|
||||
GroupRole: &store.GroupRole{
|
||||
RoleId: role.PublicId,
|
||||
PrincipalId: "",
|
||||
ScopeId: proj.PublicId,
|
||||
},
|
||||
}
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "create: vet for write failed new group role: missing user id invalid parameter",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "dup-at-org",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
principal := TestGroup(t, conn, org.PublicId)
|
||||
principalRole, err := NewGroupRole(org.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*GroupRole)
|
||||
}(),
|
||||
},
|
||||
wantDup: true,
|
||||
wantErr: true,
|
||||
wantErrMsg: `create: failed pq: duplicate key value violates unique constraint`,
|
||||
},
|
||||
{
|
||||
name: "dup-at-proj",
|
||||
args: args{
|
||||
role: func() *GroupRole {
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
principal := TestGroup(t, conn, proj.PublicId)
|
||||
principalRole, err := NewGroupRole(proj.PublicId, role.PublicId, principal.PublicId)
|
||||
require.NoError(t, err)
|
||||
return principalRole.(*GroupRole)
|
||||
}(),
|
||||
},
|
||||
wantDup: true,
|
||||
wantErr: true,
|
||||
wantErrMsg: `create: failed pq: duplicate key value violates unique constraint`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w := db.New(conn)
|
||||
if tt.wantDup {
|
||||
r := tt.args.role.Clone().(*GroupRole)
|
||||
err := w.Create(context.Background(), r)
|
||||
require.NoError(err)
|
||||
}
|
||||
r := tt.args.role.Clone().(*GroupRole)
|
||||
err := w.Create(context.Background(), r)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.Contains(err.Error(), tt.wantErrMsg)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.True(errors.Is(err, tt.wantIsErr))
|
||||
}
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
|
||||
found := allocGroupRole()
|
||||
err = w.LookupWhere(context.Background(), &found, "role_id = ? and principal_id = ?", r.RoleId, r.PrincipalId)
|
||||
require.NoError(err)
|
||||
assert.Equal(r, &found)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupRole_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, _ := TestScopes(t, conn)
|
||||
rw := db.New(conn)
|
||||
|
||||
t.Run("updates not allowed", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r := TestRole(t, conn, org.PublicId)
|
||||
g := TestGroup(t, conn, org.PublicId)
|
||||
g2 := TestGroup(t, conn, org.PublicId)
|
||||
userRole := TestGroupRole(t, conn, org.PublicId, r.PublicId, g.PublicId)
|
||||
updateRole := userRole.Clone().(*GroupRole)
|
||||
updateRole.PrincipalId = g2.PublicId
|
||||
updatedRows, err := rw.Update(context.Background(), updateRole, []string{"PrincipalId"}, nil)
|
||||
require.Error(err)
|
||||
assert.Equal(0, updatedRows)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupRole_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
id := testId(t)
|
||||
org, _ := TestScopes(t, conn)
|
||||
g := TestGroup(t, conn, org.PublicId)
|
||||
r := TestRole(t, conn, org.PublicId)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
role *GroupRole
|
||||
wantRowsDeleted int
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
role: TestGroupRole(t, conn, org.PublicId, r.PublicId, g.PublicId),
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 1,
|
||||
},
|
||||
{
|
||||
name: "bad-id",
|
||||
role: func() *GroupRole { r := allocGroupRole(); r.PrincipalId = id; r.RoleId = id; r.ScopeId = id; return &r }(),
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
deleteRole := allocGroupRole()
|
||||
deleteRole.RoleId = tt.role.GetRoleId()
|
||||
deleteRole.PrincipalId = tt.role.GetPrincipalId()
|
||||
deletedRows, err := rw.Delete(context.Background(), &deleteRole)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
if tt.wantRowsDeleted == 0 {
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
return
|
||||
}
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
found := allocGroupRole()
|
||||
err = rw.LookupWhere(context.Background(), &found, "role_id = ? and principal_id = ?", tt.role.GetRoleId(), tt.role.GetPrincipalId())
|
||||
require.Error(err)
|
||||
assert.True(errors.Is(db.ErrRecordNotFound, err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupRole_Clone(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
grp := TestGroup(t, conn, org.PublicId)
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
grpRole := TestGroupRole(t, conn, org.PublicId, role.PublicId, grp.PublicId)
|
||||
cp := grpRole.Clone()
|
||||
assert.True(proto.Equal(cp.(*GroupRole).GroupRole, grpRole.GroupRole))
|
||||
})
|
||||
t.Run("not-equal", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
grp := TestGroup(t, conn, org.PublicId)
|
||||
grp2 := TestGroup(t, conn, proj.PublicId)
|
||||
role := TestRole(t, conn, org.PublicId)
|
||||
role2 := TestRole(t, conn, proj.PublicId)
|
||||
grpRole := TestGroupRole(t, conn, org.PublicId, role.PublicId, grp.PublicId)
|
||||
grpRole2 := TestGroupRole(t, conn, proj.PublicId, role2.PublicId, grp2.PublicId)
|
||||
cp := grpRole.Clone()
|
||||
assert.True(!proto.Equal(cp.(*GroupRole).GroupRole, grpRole2.GroupRole))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupRole_GetType(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
r := &GroupRole{}
|
||||
ty := r.GetType()
|
||||
assert.Equal("group", ty)
|
||||
}
|
||||
@@ -111,6 +111,12 @@ func (r *Repository) update(ctx context.Context, resource Resource, fieldMaskPat
|
||||
}
|
||||
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_UPDATE.String()}
|
||||
|
||||
dbOpts := []db.Option{db.WithOplog(r.wrapper, metadata)}
|
||||
opts := getOpts(opt...)
|
||||
if opts.withSkipVetForWrite {
|
||||
dbOpts = append(dbOpts, db.WithSkipVetForWrite(true))
|
||||
}
|
||||
|
||||
var rowsUpdated int
|
||||
var returnedResource interface{}
|
||||
_, err = r.writer.DoTx(
|
||||
@@ -125,7 +131,7 @@ func (r *Repository) update(ctx context.Context, resource Resource, fieldMaskPat
|
||||
returnedResource,
|
||||
fieldMaskPaths,
|
||||
setToNullPaths,
|
||||
db.WithOplog(r.wrapper, metadata),
|
||||
dbOpts...,
|
||||
)
|
||||
if err == nil && rowsUpdated > 1 {
|
||||
// return err, which will result in a rollback of the update
|
||||
|
||||
@@ -202,6 +202,7 @@ func TestRepository_UpdateGroup(t *testing.T) {
|
||||
wantErrMsg string
|
||||
wantIsError error
|
||||
wantDup bool
|
||||
directUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
@@ -378,6 +379,19 @@ func TestRepository_UpdateGroup(t *testing.T) {
|
||||
wantErrMsg: " already exists in organization " + org.PublicId,
|
||||
wantIsError: db.ErrNotUnique,
|
||||
},
|
||||
{
|
||||
name: "modified-scope",
|
||||
args: args{
|
||||
name: "modified-scope" + id,
|
||||
fieldMaskPaths: []string{"ScopeId"},
|
||||
ScopeId: proj.PublicId,
|
||||
opt: []Option{WithSkipVetForWrite(true)},
|
||||
},
|
||||
newScopeId: org.PublicId,
|
||||
wantErr: true,
|
||||
wantErrMsg: `update: failed pq: scope_id cannot be set`,
|
||||
directUpdate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -400,13 +414,25 @@ func TestRepository_UpdateGroup(t *testing.T) {
|
||||
updateGrp.Name = tt.args.name
|
||||
updateGrp.Description = tt.args.description
|
||||
|
||||
userAfterUpdate, updatedRows, err := repo.UpdateGroup(context.Background(), &updateGrp, tt.args.fieldMaskPaths, tt.args.opt...)
|
||||
var groupAfterUpdate *Group
|
||||
var updatedRows int
|
||||
var err error
|
||||
if tt.directUpdate {
|
||||
g := updateGrp.Clone()
|
||||
var resource interface{}
|
||||
resource, updatedRows, err = repo.update(context.Background(), g.(*Group), tt.args.fieldMaskPaths, nil, tt.args.opt...)
|
||||
if err == nil {
|
||||
groupAfterUpdate = resource.(*Group)
|
||||
}
|
||||
} else {
|
||||
groupAfterUpdate, updatedRows, err = repo.UpdateGroup(context.Background(), &updateGrp, tt.args.fieldMaskPaths, tt.args.opt...)
|
||||
}
|
||||
if tt.wantErr {
|
||||
assert.Error(err)
|
||||
if tt.wantIsError != nil {
|
||||
assert.True(errors.Is(err, tt.wantIsError))
|
||||
}
|
||||
assert.Nil(userAfterUpdate)
|
||||
assert.Nil(groupAfterUpdate)
|
||||
assert.Equal(0, updatedRows)
|
||||
assert.Contains(err.Error(), tt.wantErrMsg)
|
||||
err = db.TestVerifyOplog(t, rw, u.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
|
||||
@@ -418,13 +444,13 @@ func TestRepository_UpdateGroup(t *testing.T) {
|
||||
assert.Equal(tt.wantRowsUpdate, updatedRows)
|
||||
switch tt.name {
|
||||
case "valid-no-op":
|
||||
assert.Equal(u.UpdateTime, userAfterUpdate.UpdateTime)
|
||||
assert.Equal(u.UpdateTime, groupAfterUpdate.UpdateTime)
|
||||
default:
|
||||
assert.NotEqual(u.UpdateTime, userAfterUpdate.UpdateTime)
|
||||
assert.NotEqual(u.UpdateTime, groupAfterUpdate.UpdateTime)
|
||||
}
|
||||
foundGrp, err := repo.LookupGroup(context.Background(), u.PublicId)
|
||||
assert.NoError(err)
|
||||
assert.True(proto.Equal(userAfterUpdate, foundGrp))
|
||||
assert.True(proto.Equal(groupAfterUpdate, foundGrp))
|
||||
dbassert := dbassert.New(t, rw)
|
||||
if tt.args.name == "" {
|
||||
dbassert.IsNull(foundGrp, "name")
|
||||
|
||||
@@ -0,0 +1,453 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
)
|
||||
|
||||
// AddPrincipalRoles provides the ability to add principals (userIds and
|
||||
// groupIds) to a role (roleId). The role's current db version must match the
|
||||
// roleVersion or an error will be returned. The roles and groups must be in
|
||||
// the same scope. User can only be added to roles which are within the user's
|
||||
// organization, or the role is within a project within the user's organization.
|
||||
func (r *Repository) AddPrincipalRoles(ctx context.Context, roleId string, roleVersion int, userIds, groupIds []string, opt ...Option) ([]PrincipalRole, error) {
|
||||
// NOTE - we are intentionally not going to check that the scopes are
|
||||
// correct for the userIds and groupIds, given the roleId. We are going to
|
||||
// rely on the database constraints and triggers to maintain the integrity
|
||||
// of these scope relationships. The users and role need to either be in
|
||||
// the same organization or the role needs to be in a project of the user's
|
||||
// org. The groups and role have to be in the same scope (org or project).
|
||||
// There are constraints and triggers to enforce these relationships.
|
||||
if roleId == "" {
|
||||
return nil, fmt.Errorf("add principal roles: missing role id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if len(userIds) == 0 && len(groupIds) == 0 {
|
||||
return nil, fmt.Errorf("add principal roles: missing either user or groups to add: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
|
||||
role := allocRole()
|
||||
role.PublicId = roleId
|
||||
scope, err := role.GetScope(ctx, r.reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add principal roles: unable to get role %s scope: %w", roleId, err)
|
||||
}
|
||||
|
||||
newUserRoles := make([]interface{}, 0, len(userIds))
|
||||
for _, id := range userIds {
|
||||
usrRole, err := NewUserRole(scope.PublicId, roleId, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add principal roles: unable to create in memory user role: %w", err)
|
||||
}
|
||||
newUserRoles = append(newUserRoles, usrRole)
|
||||
}
|
||||
newGrpRoles := make([]interface{}, 0, len(groupIds))
|
||||
for _, id := range groupIds {
|
||||
grpRole, err := NewGroupRole(scope.PublicId, roleId, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add principal roles: unable to create in memory group role: %w", err)
|
||||
}
|
||||
newGrpRoles = append(newGrpRoles, grpRole)
|
||||
}
|
||||
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0, 2)
|
||||
roleTicket, err := w.GetTicket(&role)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add principal roles: unable to get ticket: %w", err)
|
||||
}
|
||||
updatedRole := allocRole()
|
||||
updatedRole.PublicId = roleId
|
||||
updatedRole.Version = uint32(roleVersion) + 1
|
||||
var roleOplogMsg oplog.Message
|
||||
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(roleVersion))
|
||||
if err != nil {
|
||||
return fmt.Errorf("add principal roles: unable to update role version: %w", err)
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return fmt.Errorf("add principal roles: updated role and %d rows updated", rowsUpdated)
|
||||
}
|
||||
msgs = append(msgs, &roleOplogMsg)
|
||||
if len(newUserRoles) > 0 {
|
||||
userOplogMsgs := make([]*oplog.Message, 0, len(newUserRoles))
|
||||
if err := w.CreateItems(ctx, newUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
|
||||
return fmt.Errorf("add principal roles: unable to add users: %w", err)
|
||||
}
|
||||
msgs = append(msgs, userOplogMsgs...)
|
||||
}
|
||||
if len(newGrpRoles) > 0 {
|
||||
grpOplogMsgs := make([]*oplog.Message, 0, len(newGrpRoles))
|
||||
if err := w.CreateItems(ctx, newGrpRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil {
|
||||
return fmt.Errorf("add principal roles: unable to add groups: %w", err)
|
||||
}
|
||||
msgs = append(msgs, grpOplogMsgs...)
|
||||
}
|
||||
metadata := oplog.Metadata{
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_CREATE.String()},
|
||||
"scope-id": []string{scope.PublicId},
|
||||
"scope-type": []string{scope.Type},
|
||||
"resource-public-id": []string{roleId},
|
||||
}
|
||||
if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil {
|
||||
return fmt.Errorf("add principal roles: unable to write oplog: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add principal roles: error creating roles: %w", err)
|
||||
}
|
||||
principalRoles := make([]PrincipalRole, 0, len(newUserRoles)+len(newUserRoles))
|
||||
for _, role := range newUserRoles {
|
||||
principalRoles = append(principalRoles, role.(*UserRole))
|
||||
}
|
||||
for _, role := range newGrpRoles {
|
||||
principalRoles = append(principalRoles, role.(*GroupRole))
|
||||
}
|
||||
return principalRoles, nil
|
||||
}
|
||||
|
||||
// SetPrincipalRoles will set the role's principals. Set add and/or delete
|
||||
// principals as need to reconcile the existing principals with the principals
|
||||
// requested. If both userIds and groupIds are empty, the principal roles will
|
||||
// be cleared.
|
||||
func (r *Repository) SetPrincipalRoles(ctx context.Context, roleId string, roleVersion int, userIds, groupIds []string, opt ...Option) ([]PrincipalRole, int, error) {
|
||||
// NOTE - we are intentionally not going to check that the scopes are
|
||||
// correct for the userIds and groupIds, given the roleId. We are going to
|
||||
// rely on the database constraints and triggers to maintain the integrity
|
||||
// of these scope relationships. The users and role need to either be in
|
||||
// the same organization or the role needs to be in a project of the user's
|
||||
// org. The groups and role have to be in the same scope (org or project).
|
||||
// There are constraints and triggers to enforce these relationships.
|
||||
if roleId == "" {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: missing role id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
role := allocRole()
|
||||
role.PublicId = roleId
|
||||
scope, err := role.GetScope(ctx, r.reader)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to get role %s scope: %w", roleId, err)
|
||||
}
|
||||
// it's "safe" to do this lookup outside the DoTx transaction because we
|
||||
// have a roleVersion so the principals can’t change without the version
|
||||
// changing.
|
||||
toSet, err := r.principalsToSet(ctx, &role, userIds, groupIds)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to determine set: %w", err)
|
||||
}
|
||||
|
||||
// handle no change to existing principal roles
|
||||
if len(toSet.addUserRoles) == 0 && len(toSet.addGroupRoles) == 0 && len(toSet.deleteUserRoles) == 0 && len(toSet.deleteGroupRoles) == 0 {
|
||||
results := make([]PrincipalRole, 0, len(userIds)+len(groupIds))
|
||||
for _, id := range userIds {
|
||||
role, err := NewUserRole(scope.PublicId, roleId, id)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("add principal roles: unable to create in memory user role: %w", err)
|
||||
}
|
||||
results = append(results, role)
|
||||
}
|
||||
for _, id := range groupIds {
|
||||
role, err := NewGroupRole(scope.PublicId, roleId, id)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("add principal roles: unable to create in memory group role: %w", err)
|
||||
}
|
||||
results = append(results, role)
|
||||
}
|
||||
return results, db.NoRowsAffected, nil
|
||||
}
|
||||
|
||||
var currentPrincipals []PrincipalRole
|
||||
var totalRowsAffected int
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
|
||||
// we need a roleTicket, which won't be redeemed until all the other
|
||||
// writes are successful. We can't just use a single ticket because
|
||||
// we need to write oplog entries for deletes and adds
|
||||
roleTicket, err := w.GetTicket(&role)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to get ticket for role: %w", err)
|
||||
}
|
||||
updatedRole := allocRole()
|
||||
updatedRole.PublicId = roleId
|
||||
updatedRole.Version = uint32(roleVersion) + 1
|
||||
var roleOplogMsg oplog.Message
|
||||
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(roleVersion))
|
||||
if err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to update role version: %w", err)
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return fmt.Errorf("set principal roles: updated role and %d rows updated", rowsUpdated)
|
||||
}
|
||||
msgs := make([]*oplog.Message, 0, 5)
|
||||
metadata := oplog.Metadata{
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
|
||||
"scope-id": []string{scope.PublicId},
|
||||
"scope-type": []string{scope.Type},
|
||||
"resource-public-id": []string{roleId},
|
||||
}
|
||||
msgs = append(msgs, &roleOplogMsg)
|
||||
|
||||
if len(toSet.deleteUserRoles) > 0 || len(toSet.deleteGroupRoles) > 0 {
|
||||
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String())
|
||||
if len(toSet.deleteUserRoles) > 0 {
|
||||
userOplogMsgs := make([]*oplog.Message, 0, len(toSet.deleteUserRoles))
|
||||
rowsDeleted, err := w.DeleteItems(ctx, toSet.deleteUserRoles, db.NewOplogMsgs(&userOplogMsgs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to delete user roles: %w", err)
|
||||
}
|
||||
if rowsDeleted != len(toSet.deleteUserRoles) {
|
||||
return fmt.Errorf("set principal roles: user roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteUserRoles))
|
||||
}
|
||||
totalRowsAffected += rowsDeleted
|
||||
msgs = append(msgs, userOplogMsgs...)
|
||||
}
|
||||
if len(toSet.deleteGroupRoles) > 0 {
|
||||
grpOplogMsgs := make([]*oplog.Message, 0, len(toSet.deleteGroupRoles))
|
||||
rowsDeleted, err := w.DeleteItems(ctx, toSet.deleteGroupRoles, db.NewOplogMsgs(&grpOplogMsgs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to delete groups: %w", err)
|
||||
}
|
||||
if rowsDeleted != len(toSet.deleteGroupRoles) {
|
||||
return fmt.Errorf("set principal roles: group roles deleted %d did not match request for %d", rowsDeleted, len(toSet.deleteGroupRoles))
|
||||
}
|
||||
totalRowsAffected += rowsDeleted
|
||||
msgs = append(msgs, grpOplogMsgs...)
|
||||
}
|
||||
}
|
||||
if len(toSet.addUserRoles) > 0 || len(toSet.addGroupRoles) > 0 {
|
||||
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String())
|
||||
if len(toSet.addUserRoles) > 0 {
|
||||
userOplogMsgs := make([]*oplog.Message, 0, len(toSet.addUserRoles))
|
||||
if err := w.CreateItems(ctx, toSet.addUserRoles, db.NewOplogMsgs(&userOplogMsgs)); err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to add users: %w", err)
|
||||
}
|
||||
totalRowsAffected += len(toSet.addUserRoles)
|
||||
msgs = append(msgs, userOplogMsgs...)
|
||||
}
|
||||
if len(toSet.addGroupRoles) > 0 {
|
||||
grpOplogMsgs := make([]*oplog.Message, 0, len(toSet.addGroupRoles))
|
||||
if err := w.CreateItems(ctx, toSet.addGroupRoles, db.NewOplogMsgs(&grpOplogMsgs)); err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to add groups: %w", err)
|
||||
}
|
||||
totalRowsAffected += len(toSet.addGroupRoles)
|
||||
msgs = append(msgs, grpOplogMsgs...)
|
||||
}
|
||||
}
|
||||
if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to write oplog for additions: %w", err)
|
||||
}
|
||||
|
||||
currentPrincipals, err = r.ListPrincipalRoles(ctx, roleId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set principal roles: unable to retrieve current principal roles after sets: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("set principal roles: unable to set principals: %w", err)
|
||||
}
|
||||
return currentPrincipals, totalRowsAffected, nil
|
||||
}
|
||||
|
||||
// DeletePrincipalRoles principals (userIds and/or groupIds) from a role
|
||||
// (roleId). The role's current db version must match the roleVersion or an
|
||||
// error will be returned.
|
||||
func (r *Repository) DeletePrincipalRoles(ctx context.Context, roleId string, roleVersion int, userIds, groupIds []string, opt ...Option) (int, error) {
|
||||
if roleId == "" {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete principal roles: missing role id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
if len(userIds) == 0 && len(groupIds) == 0 {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete principal roles: missing either user or groups to delete: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
role := allocRole()
|
||||
role.PublicId = roleId
|
||||
scope, err := role.GetScope(ctx, r.reader)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to get role %s scope to create metadata: %w", roleId, err)
|
||||
}
|
||||
deleteUserRoles := make([]interface{}, 0, len(userIds))
|
||||
for _, id := range userIds {
|
||||
usrRole, err := NewUserRole(scope.PublicId, roleId, id)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to create in memory user role: %w", err)
|
||||
}
|
||||
deleteUserRoles = append(deleteUserRoles, usrRole)
|
||||
}
|
||||
deleteGrpRoles := make([]interface{}, 0, len(groupIds))
|
||||
for _, id := range groupIds {
|
||||
grpRole, err := NewGroupRole(scope.PublicId, roleId, id)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete principal roles: unable to create in memory group role: %w", err)
|
||||
}
|
||||
deleteGrpRoles = append(deleteGrpRoles, grpRole)
|
||||
}
|
||||
|
||||
var totalRowsDeleted int
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(reader db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0, 2)
|
||||
roleTicket, err := w.GetTicket(&role)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete principal roles: unable to get ticket: %w", err)
|
||||
}
|
||||
updatedRole := allocRole()
|
||||
updatedRole.PublicId = roleId
|
||||
updatedRole.Version = uint32(roleVersion) + 1
|
||||
var roleOplogMsg oplog.Message
|
||||
rowsUpdated, err := w.Update(ctx, &updatedRole, []string{"Version"}, nil, db.NewOplogMsg(&roleOplogMsg), db.WithVersion(roleVersion))
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete principal roles: unable to update role version: %w", err)
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return fmt.Errorf("delete principal roles: updated role and %d rows updated", rowsUpdated)
|
||||
}
|
||||
msgs = append(msgs, &roleOplogMsg)
|
||||
if len(deleteUserRoles) > 0 {
|
||||
userOplogMsgs := make([]*oplog.Message, 0, len(deleteUserRoles))
|
||||
rowsDeleted, err := w.DeleteItems(ctx, deleteUserRoles, db.NewOplogMsgs(&userOplogMsgs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete principal roles: unable to delete user roles: %w", err)
|
||||
}
|
||||
if rowsDeleted != len(deleteUserRoles) {
|
||||
return fmt.Errorf("delete principal roles: user roles deleted %d did not match request for %d", rowsDeleted, len(deleteUserRoles))
|
||||
}
|
||||
totalRowsDeleted += rowsDeleted
|
||||
msgs = append(msgs, userOplogMsgs...)
|
||||
}
|
||||
if len(deleteGrpRoles) > 0 {
|
||||
grpOplogMsgs := make([]*oplog.Message, 0, len(deleteGrpRoles))
|
||||
rowsDeleted, err := w.DeleteItems(ctx, deleteGrpRoles, db.NewOplogMsgs(&grpOplogMsgs))
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete principal roles: unable to delete groups: %w", err)
|
||||
}
|
||||
if rowsDeleted != len(deleteGrpRoles) {
|
||||
return fmt.Errorf("delete principal roles: group roles deleted %d did not match request for %d", rowsDeleted, len(deleteGrpRoles))
|
||||
}
|
||||
totalRowsDeleted += rowsDeleted
|
||||
msgs = append(msgs, grpOplogMsgs...)
|
||||
}
|
||||
metadata := oplog.Metadata{
|
||||
"op-type": []string{oplog.OpType_OP_TYPE_DELETE.String()},
|
||||
"scope-id": []string{scope.PublicId},
|
||||
"scope-type": []string{scope.Type},
|
||||
"resource-public-id": []string{roleId},
|
||||
}
|
||||
if err := w.WriteOplogEntryWith(ctx, r.wrapper, roleTicket, metadata, msgs); err != nil {
|
||||
return fmt.Errorf("delete principal roles: unable to write oplog: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete principal roles: error deleting principal roles: %w", err)
|
||||
}
|
||||
return totalRowsDeleted, nil
|
||||
}
|
||||
|
||||
// ListPrincipalRoles returns the principal roles for the roleId and supports the WithLimit option.
|
||||
func (r *Repository) ListPrincipalRoles(ctx context.Context, roleId string, opt ...Option) ([]PrincipalRole, error) {
|
||||
if roleId == "" {
|
||||
return nil, fmt.Errorf("lookup principal roles: missing role id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
var roles []principalRoleView
|
||||
if err := r.list(ctx, &roles, "role_id = ?", []interface{}{roleId}, opt...); err != nil {
|
||||
return nil, fmt.Errorf("lookup principal role: unable to lookup roles: %w", err)
|
||||
}
|
||||
principals := make([]PrincipalRole, 0, len(roles))
|
||||
for _, r := range roles {
|
||||
principals = append(principals, r)
|
||||
}
|
||||
return principals, nil
|
||||
}
|
||||
|
||||
type principalSet struct {
|
||||
addUserRoles []interface{}
|
||||
addGroupRoles []interface{}
|
||||
deleteUserRoles []interface{}
|
||||
deleteGroupRoles []interface{}
|
||||
}
|
||||
|
||||
func (r *Repository) principalsToSet(ctx context.Context, role *Role, userIds, groupIds []string) (*principalSet, error) {
|
||||
if role == nil {
|
||||
return nil, fmt.Errorf("missing role: %w", db.ErrNilParameter)
|
||||
}
|
||||
existing, err := r.ListPrincipalRoles(ctx, role.PublicId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list existing principal role %s: %w", role.PublicId, err)
|
||||
}
|
||||
existingUsers := map[string]PrincipalRole{}
|
||||
existingGroups := map[string]PrincipalRole{}
|
||||
for _, p := range existing {
|
||||
switch p.GetType() {
|
||||
case UserRoleType.String():
|
||||
existingUsers[p.GetPrincipalId()] = p
|
||||
case GroupRoleType.String():
|
||||
existingGroups[p.GetPrincipalId()] = p
|
||||
default:
|
||||
return nil, fmt.Errorf("%s is unknown principal type %s", p.GetPrincipalId(), p.GetType())
|
||||
}
|
||||
}
|
||||
var newUserRoles []interface{}
|
||||
userIdsMap := map[string]struct{}{}
|
||||
for _, id := range userIds {
|
||||
userIdsMap[id] = struct{}{}
|
||||
if _, ok := existingUsers[id]; !ok {
|
||||
usrRole, err := NewUserRole(role.ScopeId, role.PublicId, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create in memory user role for add: %w", err)
|
||||
}
|
||||
newUserRoles = append(newUserRoles, usrRole)
|
||||
}
|
||||
}
|
||||
var newGrpRoles []interface{}
|
||||
groupIdsMap := map[string]struct{}{}
|
||||
for _, id := range groupIds {
|
||||
groupIdsMap[id] = struct{}{}
|
||||
if _, ok := existingGroups[id]; !ok {
|
||||
grpRole, err := NewGroupRole(role.ScopeId, role.PublicId, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create in memory group role for add: %w", err)
|
||||
}
|
||||
newGrpRoles = append(newGrpRoles, grpRole)
|
||||
}
|
||||
}
|
||||
var deleteUserRoles []interface{}
|
||||
for _, p := range existingUsers {
|
||||
if _, ok := userIdsMap[p.GetPrincipalId()]; !ok {
|
||||
usrRole, err := NewUserRole(p.GetScopeId(), p.GetRoleId(), p.GetPrincipalId())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create in memory user role for delete: %w", err)
|
||||
}
|
||||
deleteUserRoles = append(deleteUserRoles, usrRole)
|
||||
}
|
||||
}
|
||||
var deleteGrpRoles []interface{}
|
||||
for _, p := range existingGroups {
|
||||
if _, ok := groupIdsMap[p.GetPrincipalId()]; !ok {
|
||||
grpRole, err := NewGroupRole(p.GetScopeId(), p.GetRoleId(), p.GetPrincipalId())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create in memory group role for delete: %w", err)
|
||||
}
|
||||
deleteGrpRoles = append(deleteGrpRoles, grpRole)
|
||||
}
|
||||
}
|
||||
return &principalSet{
|
||||
addUserRoles: newUserRoles,
|
||||
addGroupRoles: newGrpRoles,
|
||||
deleteUserRoles: deleteUserRoles,
|
||||
deleteGroupRoles: deleteGrpRoles,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,730 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/watchtower/internal/db"
|
||||
"github.com/hashicorp/watchtower/internal/oplog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRepository_AddPrincipalRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
require.NoError(t, err)
|
||||
org, proj := TestScopes(t, conn)
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
createUsersFn := func() []string {
|
||||
results := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
results = append(results, u.PublicId)
|
||||
}
|
||||
return results
|
||||
}
|
||||
createGrpsFn := func() []string {
|
||||
results := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
g := TestGroup(t, conn, proj.PublicId)
|
||||
results = append(results, g.PublicId)
|
||||
}
|
||||
return results
|
||||
}
|
||||
type args struct {
|
||||
roleId string
|
||||
roleVersion int
|
||||
userIds []string
|
||||
groupIds []string
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
wantErrIs error
|
||||
}{
|
||||
{
|
||||
name: "valid-both-users-and-groups",
|
||||
args: args{
|
||||
roleId: role.PublicId,
|
||||
roleVersion: 1,
|
||||
userIds: createUsersFn(),
|
||||
groupIds: createGrpsFn(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid-just-groups",
|
||||
args: args{
|
||||
roleId: role.PublicId,
|
||||
roleVersion: 2,
|
||||
userIds: nil,
|
||||
groupIds: createGrpsFn(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid-just-users",
|
||||
args: args{
|
||||
roleId: role.PublicId,
|
||||
roleVersion: 3,
|
||||
userIds: createUsersFn(),
|
||||
groupIds: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-version",
|
||||
args: args{
|
||||
roleId: role.PublicId,
|
||||
roleVersion: 1000,
|
||||
userIds: createUsersFn(),
|
||||
groupIds: createGrpsFn(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no-principals",
|
||||
args: args{
|
||||
roleId: role.PublicId,
|
||||
roleVersion: 1,
|
||||
userIds: nil,
|
||||
groupIds: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NoError(conn.Where("1=1").Delete(allocUserRole()).Error)
|
||||
require.NoError(conn.Where("1=1").Delete(allocGroupRole()).Error)
|
||||
got, err := repo.AddPrincipalRoles(context.Background(), tt.args.roleId, tt.args.roleVersion, tt.args.userIds, tt.args.groupIds, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
if tt.wantErrIs != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
gotPrincipal := map[string]PrincipalRole{}
|
||||
for _, r := range got {
|
||||
gotPrincipal[r.GetPrincipalId()] = r
|
||||
}
|
||||
for _, id := range tt.args.userIds {
|
||||
assert.NotEmpty(gotPrincipal[id])
|
||||
u, err := repo.LookupUser(context.Background(), id)
|
||||
assert.NoError(err)
|
||||
assert.Equal(id, u.PublicId)
|
||||
}
|
||||
for _, id := range tt.args.groupIds {
|
||||
assert.NotEmpty(gotPrincipal[id])
|
||||
g, err := repo.LookupGroup(context.Background(), id)
|
||||
assert.NoError(err)
|
||||
assert.Equal(id, g.PublicId)
|
||||
}
|
||||
err = db.TestVerifyOplog(t, rw, role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
|
||||
assert.NoError(err)
|
||||
|
||||
foundRoles, err := repo.ListPrincipalRoles(context.Background(), role.PublicId)
|
||||
require.NoError(err)
|
||||
for _, r := range foundRoles {
|
||||
assert.NotEmpty(gotPrincipal[r.GetPrincipalId()])
|
||||
assert.Equal(gotPrincipal[r.GetPrincipalId()].GetRoleId(), r.GetRoleId())
|
||||
assert.Equal(gotPrincipal[r.GetPrincipalId()].GetScopeId(), r.GetScopeId())
|
||||
assert.Equal(gotPrincipal[r.GetPrincipalId()].GetType(), r.GetType())
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListPrincipalRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
const testLimit = 10
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper, WithLimit(testLimit))
|
||||
require.NoError(t, err)
|
||||
org, proj := TestScopes(t, conn)
|
||||
|
||||
type args struct {
|
||||
withRoleId string
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
createCnt int
|
||||
createScopeId string
|
||||
args args
|
||||
wantCnt int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no-limit",
|
||||
createCnt: repo.defaultLimit + 2,
|
||||
createScopeId: org.PublicId,
|
||||
args: args{
|
||||
opt: []Option{WithLimit(-1)},
|
||||
},
|
||||
wantCnt: repo.defaultLimit + 2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no-limit-proj-group",
|
||||
createCnt: repo.defaultLimit + 2,
|
||||
createScopeId: proj.PublicId,
|
||||
args: args{
|
||||
opt: []Option{WithLimit(-1)},
|
||||
},
|
||||
wantCnt: repo.defaultLimit + 2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "default-limit",
|
||||
createCnt: repo.defaultLimit + 2,
|
||||
createScopeId: org.PublicId,
|
||||
wantCnt: repo.defaultLimit,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "custom-limit",
|
||||
createCnt: repo.defaultLimit + 2,
|
||||
createScopeId: org.PublicId,
|
||||
args: args{
|
||||
opt: []Option{WithLimit(3)},
|
||||
},
|
||||
wantCnt: 3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-role-id",
|
||||
createCnt: 2,
|
||||
createScopeId: org.PublicId,
|
||||
args: args{
|
||||
withRoleId: "bad-id",
|
||||
},
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NoError(conn.Where("1=1").Delete(allocRole()).Error)
|
||||
role := TestRole(t, conn, tt.createScopeId)
|
||||
userRoles := make([]string, 0, tt.createCnt)
|
||||
groupRoles := make([]string, 0, tt.createCnt)
|
||||
for i := 0; i < tt.createCnt/2; i++ {
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
userRoles = append(userRoles, u.PublicId)
|
||||
g := TestGroup(t, conn, tt.createScopeId)
|
||||
groupRoles = append(groupRoles, g.PublicId)
|
||||
}
|
||||
testRoles, err := repo.AddPrincipalRoles(context.Background(), role.PublicId, int(role.Version), userRoles, groupRoles, tt.args.opt...)
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.createCnt, len(testRoles))
|
||||
|
||||
var roleId string
|
||||
switch {
|
||||
case tt.args.withRoleId != "":
|
||||
roleId = tt.args.withRoleId
|
||||
default:
|
||||
roleId = role.PublicId
|
||||
}
|
||||
got, err := repo.ListPrincipalRoles(context.Background(), roleId, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantCnt, len(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_DeletePrincipalRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
require.NoError(t, err)
|
||||
org, _ := TestScopes(t, conn)
|
||||
|
||||
type args struct {
|
||||
role *Role
|
||||
roleIdOverride *string
|
||||
createUserCnt int
|
||||
createGroupCnt int
|
||||
deleteUserCnt int
|
||||
deleteGroupCnt int
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantRowsDeleted int
|
||||
wantErr bool
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
createUserCnt: 5,
|
||||
createGroupCnt: 5,
|
||||
deleteUserCnt: 5,
|
||||
deleteGroupCnt: 5,
|
||||
},
|
||||
wantRowsDeleted: 10,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid-keeping-some",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
createUserCnt: 5,
|
||||
createGroupCnt: 5,
|
||||
deleteUserCnt: 2,
|
||||
deleteGroupCnt: 2,
|
||||
},
|
||||
wantRowsDeleted: 4,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no-deletes",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
createUserCnt: 5,
|
||||
createGroupCnt: 5,
|
||||
},
|
||||
wantRowsDeleted: 0,
|
||||
wantErr: true,
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "just-user-roles",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
createUserCnt: 5,
|
||||
deleteUserCnt: 5,
|
||||
},
|
||||
wantRowsDeleted: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "just-group-roles",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
createGroupCnt: 5,
|
||||
deleteGroupCnt: 5,
|
||||
},
|
||||
wantRowsDeleted: 5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not-found",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
roleIdOverride: func() *string { id := testId(t); return &id }(),
|
||||
createUserCnt: 5,
|
||||
createGroupCnt: 5,
|
||||
deleteUserCnt: 5,
|
||||
deleteGroupCnt: 5,
|
||||
},
|
||||
wantRowsDeleted: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing-role-id",
|
||||
args: args{
|
||||
role: TestRole(t, conn, org.PublicId),
|
||||
roleIdOverride: func() *string { id := ""; return &id }(),
|
||||
createUserCnt: 5,
|
||||
createGroupCnt: 5,
|
||||
deleteUserCnt: 5,
|
||||
deleteGroupCnt: 5,
|
||||
},
|
||||
wantRowsDeleted: 0,
|
||||
wantErr: true,
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
userIds := make([]string, 0, tt.args.createUserCnt)
|
||||
for i := 0; i < tt.args.createUserCnt; i++ {
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
userIds = append(userIds, u.PublicId)
|
||||
}
|
||||
groupIds := make([]string, 0, tt.args.createGroupCnt)
|
||||
for i := 0; i < tt.args.createGroupCnt; i++ {
|
||||
g := TestGroup(t, conn, tt.args.role.ScopeId)
|
||||
groupIds = append(groupIds, g.PublicId)
|
||||
}
|
||||
principalRoles, err := repo.AddPrincipalRoles(context.Background(), tt.args.role.PublicId, 1, userIds, groupIds, tt.args.opt...)
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.args.createUserCnt+tt.args.createGroupCnt, len(principalRoles))
|
||||
|
||||
deleteUserIds := make([]string, 0, tt.args.deleteUserCnt)
|
||||
for i := 0; i < tt.args.deleteUserCnt; i++ {
|
||||
deleteUserIds = append(deleteUserIds, userIds[i])
|
||||
}
|
||||
deleteGroupIds := make([]string, 0, tt.args.deleteGroupCnt)
|
||||
for i := 0; i < tt.args.deleteGroupCnt; i++ {
|
||||
deleteGroupIds = append(deleteGroupIds, groupIds[i])
|
||||
}
|
||||
var roleId string
|
||||
switch {
|
||||
case tt.args.roleIdOverride != nil:
|
||||
roleId = *tt.args.roleIdOverride
|
||||
default:
|
||||
roleId = tt.args.role.PublicId
|
||||
}
|
||||
deletedRows, err := repo.DeletePrincipalRoles(context.Background(), roleId, 2, deleteUserIds, deleteGroupIds, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
assert.Error(err)
|
||||
assert.Equal(0, deletedRows)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "unexpected error %s", err.Error())
|
||||
}
|
||||
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
|
||||
assert.Error(err)
|
||||
assert.True(errors.Is(db.ErrRecordNotFound, err))
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
|
||||
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
|
||||
assert.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_SetPrincipalRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
require.NoError(t, err)
|
||||
|
||||
org, proj := TestScopes(t, conn)
|
||||
testUser := TestUser(t, conn, org.PublicId)
|
||||
testGrp := TestGroup(t, conn, proj.PublicId)
|
||||
|
||||
createUsersFn := func() []string {
|
||||
results := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
results = append(results, u.PublicId)
|
||||
}
|
||||
return results
|
||||
}
|
||||
createGrpsFn := func() []string {
|
||||
results := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
g := TestGroup(t, conn, proj.PublicId)
|
||||
results = append(results, g.PublicId)
|
||||
}
|
||||
return results
|
||||
}
|
||||
setupFn := func(role *Role) ([]string, []string) {
|
||||
users := createUsersFn()
|
||||
grps := createGrpsFn()
|
||||
_, err := repo.AddPrincipalRoles(context.Background(), role.PublicId, 1, users, grps)
|
||||
require.NoError(t, err)
|
||||
return users, grps
|
||||
}
|
||||
type args struct {
|
||||
role *Role
|
||||
roleVersion int
|
||||
userIds []string
|
||||
groupIds []string
|
||||
addToOrigUsers bool
|
||||
addToOrigGrps bool
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(*Role) ([]string, []string)
|
||||
args args
|
||||
wantAffectedRows int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "clear",
|
||||
setup: setupFn,
|
||||
args: args{
|
||||
role: TestRole(t, conn, proj.PublicId),
|
||||
roleVersion: 2, // yep, since setupFn will increment it to 2
|
||||
userIds: []string{},
|
||||
groupIds: []string{},
|
||||
},
|
||||
wantErr: false,
|
||||
wantAffectedRows: 10,
|
||||
},
|
||||
{
|
||||
name: "no change",
|
||||
setup: setupFn,
|
||||
args: args{
|
||||
role: TestRole(t, conn, proj.PublicId),
|
||||
roleVersion: 2, // yep, since setupFn will increment it to 2
|
||||
userIds: []string{},
|
||||
groupIds: []string{},
|
||||
addToOrigUsers: true,
|
||||
addToOrigGrps: true,
|
||||
},
|
||||
wantErr: false,
|
||||
wantAffectedRows: 0,
|
||||
},
|
||||
{
|
||||
name: "add users and grps",
|
||||
setup: setupFn,
|
||||
args: args{
|
||||
role: TestRole(t, conn, proj.PublicId),
|
||||
roleVersion: 2, // yep, since setupFn will increment it to 2
|
||||
userIds: []string{testUser.PublicId},
|
||||
groupIds: []string{testGrp.PublicId},
|
||||
addToOrigUsers: true,
|
||||
addToOrigGrps: true,
|
||||
},
|
||||
wantErr: false,
|
||||
wantAffectedRows: 2,
|
||||
},
|
||||
{
|
||||
name: "remove existing and add users and grps",
|
||||
setup: setupFn,
|
||||
args: args{
|
||||
role: TestRole(t, conn, proj.PublicId),
|
||||
roleVersion: 2, // yep, since setupFn will increment it to 2
|
||||
userIds: []string{testUser.PublicId},
|
||||
groupIds: []string{testGrp.PublicId},
|
||||
addToOrigUsers: false,
|
||||
addToOrigGrps: false,
|
||||
},
|
||||
wantErr: false,
|
||||
wantAffectedRows: 12,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
var origUsers, origGrps []string
|
||||
if tt.setup != nil {
|
||||
origUsers, origGrps = tt.setup(tt.args.role)
|
||||
}
|
||||
setUsers := tt.args.userIds
|
||||
setGrps := tt.args.groupIds
|
||||
if tt.args.addToOrigUsers {
|
||||
setUsers = append(setUsers, origUsers...)
|
||||
}
|
||||
if tt.args.addToOrigGrps {
|
||||
setGrps = append(setGrps, origGrps...)
|
||||
}
|
||||
|
||||
got, affectedRows, err := repo.SetPrincipalRoles(context.Background(), tt.args.role.PublicId, tt.args.roleVersion, setUsers, setGrps, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantAffectedRows, affectedRows)
|
||||
var gotIds []string
|
||||
for _, r := range got {
|
||||
gotIds = append(gotIds, r.GetPrincipalId())
|
||||
}
|
||||
var wantIds []string
|
||||
wantIds = append(wantIds, tt.args.userIds...)
|
||||
wantIds = append(wantIds, tt.args.groupIds...)
|
||||
sort.Strings(wantIds)
|
||||
sort.Strings(gotIds)
|
||||
assert.Equal(wantIds, wantIds)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_principalsToSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo, err := NewRepository(rw, rw, wrapper)
|
||||
require.NoError(t, err)
|
||||
org, proj := TestScopes(t, conn)
|
||||
createUsersFn := func() []string {
|
||||
results := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
u := TestUser(t, conn, org.PublicId)
|
||||
results = append(results, u.PublicId)
|
||||
}
|
||||
return results
|
||||
}
|
||||
createGrpsFn := func() []string {
|
||||
results := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
g := TestGroup(t, conn, proj.PublicId)
|
||||
results = append(results, g.PublicId)
|
||||
}
|
||||
return results
|
||||
}
|
||||
setupFn := func() (*Role, []string, []string) {
|
||||
users := createUsersFn()
|
||||
grps := createGrpsFn()
|
||||
role := TestRole(t, conn, proj.PublicId)
|
||||
_, err := repo.AddPrincipalRoles(context.Background(), role.PublicId, 1, users, grps)
|
||||
require.NoError(t, err)
|
||||
return role, users, grps
|
||||
}
|
||||
|
||||
type args struct {
|
||||
userIds []string
|
||||
groupIds []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "all new",
|
||||
args: args{
|
||||
userIds: createUsersFn(),
|
||||
groupIds: createGrpsFn(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "clear all",
|
||||
args: args{
|
||||
userIds: nil,
|
||||
groupIds: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "just new users",
|
||||
args: args{
|
||||
userIds: createUsersFn(),
|
||||
groupIds: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "just new groups",
|
||||
args: args{
|
||||
userIds: nil,
|
||||
groupIds: createGrpsFn(),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
r, origUsers, origGrps := setupFn()
|
||||
got, err := repo.principalsToSet(context.Background(), r, tt.args.userIds, tt.args.groupIds)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assertSetResults(t, got, tt.args.userIds, tt.args.groupIds, origUsers, origGrps)
|
||||
})
|
||||
}
|
||||
t.Run("nil role", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, users, grps := setupFn()
|
||||
got, err := repo.principalsToSet(context.Background(), nil, users, grps)
|
||||
require.Error(err)
|
||||
assert.Nil(got)
|
||||
assert.Truef(errors.Is(err, db.ErrNilParameter), "unexpected error %s", err.Error())
|
||||
})
|
||||
t.Run("no change", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r, users, grps := setupFn()
|
||||
got, err := repo.principalsToSet(context.Background(), r, users, grps)
|
||||
require.NoError(err)
|
||||
assert.Empty(got.addUserRoles)
|
||||
assert.Empty(got.addGroupRoles)
|
||||
assert.Empty(got.deleteUserRoles)
|
||||
assert.Empty(got.deleteGroupRoles)
|
||||
})
|
||||
t.Run("mixed", func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
r, users, grps := setupFn()
|
||||
var wantSetUsers, wantSetGrps, wantDeleteUsers, wantDeleteGrps []string
|
||||
for i, id := range users {
|
||||
if i < 2 {
|
||||
wantSetUsers = append(wantSetUsers, id)
|
||||
} else {
|
||||
wantDeleteUsers = append(wantDeleteUsers, id)
|
||||
}
|
||||
}
|
||||
for i, id := range grps {
|
||||
if i < 2 {
|
||||
wantSetGrps = append(wantSetGrps, id)
|
||||
} else {
|
||||
wantDeleteGrps = append(wantDeleteGrps, id)
|
||||
}
|
||||
}
|
||||
newUser := TestUser(t, conn, org.PublicId)
|
||||
newGrp := TestGroup(t, conn, proj.PublicId)
|
||||
wantSetUsers = append(wantSetUsers, newUser.PublicId)
|
||||
wantSetGrps = append(wantSetGrps, newGrp.PublicId)
|
||||
|
||||
got, err := repo.principalsToSet(context.Background(), r, wantSetUsers, wantSetGrps)
|
||||
require.NoError(err)
|
||||
assertSetResults(t, got, []string{newUser.PublicId}, []string{newGrp.PublicId}, wantDeleteUsers, wantDeleteGrps)
|
||||
})
|
||||
}
|
||||
|
||||
func assertSetResults(t *testing.T, got *principalSet, wantAddUsers, wantAddGroups, wantDeleteUsers, wantDeleteGroups []string) {
|
||||
t.Helper()
|
||||
assert := assert.New(t)
|
||||
var gotAddUsers []string
|
||||
for _, r := range got.addUserRoles {
|
||||
gotAddUsers = append(gotAddUsers, r.(*UserRole).PrincipalId)
|
||||
}
|
||||
// sort.Strings(wantAddUsers)
|
||||
// sort.Strings(gotAddUsers)
|
||||
assert.Equal(wantAddUsers, gotAddUsers)
|
||||
|
||||
var gotAddGrps []string
|
||||
for _, r := range got.addGroupRoles {
|
||||
gotAddGrps = append(gotAddGrps, r.(*GroupRole).PrincipalId)
|
||||
}
|
||||
// sort.Strings(wantAddGroups)
|
||||
// sort.Strings(gotAddGrps)
|
||||
assert.Equal(wantAddGroups, gotAddGrps)
|
||||
|
||||
var gotDeleteUsers []string
|
||||
for _, r := range got.deleteUserRoles {
|
||||
gotDeleteUsers = append(gotDeleteUsers, r.(*UserRole).PrincipalId)
|
||||
}
|
||||
sort.Strings(wantDeleteUsers)
|
||||
sort.Strings(gotDeleteUsers)
|
||||
assert.Equal(wantDeleteUsers, gotDeleteUsers)
|
||||
|
||||
var gotDeleteGroups []string
|
||||
for _, r := range got.deleteGroupRoles {
|
||||
gotDeleteGroups = append(gotDeleteGroups, r.(*GroupRole).PrincipalId)
|
||||
}
|
||||
sort.Strings(wantDeleteGroups)
|
||||
sort.Strings(gotDeleteGroups)
|
||||
assert.Equal(wantDeleteGroups, gotDeleteGroups)
|
||||
}
|
||||
@@ -66,8 +66,9 @@ func (r *Repository) UpdateUser(ctx context.Context, user *User, fieldMaskPaths
|
||||
if len(dbMask) == 0 && len(nullFields) == 0 {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("update user: %w", db.ErrEmptyFieldMask)
|
||||
}
|
||||
|
||||
u := user.Clone()
|
||||
resource, rowsUpdated, err := r.update(ctx, u.(*User), dbMask, nullFields)
|
||||
resource, rowsUpdated, err := r.update(ctx, u.(*User), dbMask, nullFields, opt...)
|
||||
if err != nil {
|
||||
if db.IsUniqueError(err) {
|
||||
return nil, db.NoRowsAffected, fmt.Errorf("update user: user %s already exists in organization %s", user.Name, user.ScopeId)
|
||||
|
||||
@@ -136,6 +136,7 @@ func TestRepository_UpdateUser(t *testing.T) {
|
||||
wantErrMsg string
|
||||
wantIsErr error
|
||||
wantDup bool
|
||||
directUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
@@ -280,6 +281,18 @@ func TestRepository_UpdateUser(t *testing.T) {
|
||||
wantDup: true,
|
||||
wantErrMsg: `update user: user %s already exists in organization %s`,
|
||||
},
|
||||
{
|
||||
name: "modified-scope",
|
||||
args: args{
|
||||
name: "modified-scope" + id,
|
||||
fieldMaskPaths: []string{"ScopeId"},
|
||||
ScopeId: proj.PublicId,
|
||||
opt: []Option{WithSkipVetForWrite(true)},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: `update: failed pq: scope_id cannot be set`,
|
||||
directUpdate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -302,7 +315,19 @@ func TestRepository_UpdateUser(t *testing.T) {
|
||||
updateUser.Name = tt.args.name
|
||||
updateUser.Description = tt.args.description
|
||||
|
||||
userAfterUpdate, updatedRows, err := repo.UpdateUser(context.Background(), &updateUser, tt.args.fieldMaskPaths, tt.args.opt...)
|
||||
var userAfterUpdate *User
|
||||
var updatedRows int
|
||||
var err error
|
||||
if tt.directUpdate {
|
||||
u := updateUser.Clone()
|
||||
var resource interface{}
|
||||
resource, updatedRows, err = repo.update(context.Background(), u.(*User), tt.args.fieldMaskPaths, nil, tt.args.opt...)
|
||||
if err == nil {
|
||||
userAfterUpdate = resource.(*User)
|
||||
}
|
||||
} else {
|
||||
userAfterUpdate, updatedRows, err = repo.UpdateUser(context.Background(), &updateUser, tt.args.fieldMaskPaths, tt.args.opt...)
|
||||
}
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
if tt.wantIsErr != nil {
|
||||
@@ -314,7 +339,7 @@ func TestRepository_UpdateUser(t *testing.T) {
|
||||
case "dup-name":
|
||||
assert.Equal(fmt.Sprintf(tt.wantErrMsg, "dup-name"+id, org.PublicId), err.Error())
|
||||
default:
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
assert.True(strings.Contains(err.Error(), tt.wantErrMsg))
|
||||
}
|
||||
err = db.TestVerifyOplog(t, rw, u.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
|
||||
require.Error(err)
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.24.0
|
||||
// protoc v3.12.3
|
||||
// source: controller/storage/iam/store/v1/principal_role.proto
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
timestamp "github.com/hashicorp/watchtower/internal/db/timestamp"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type UserRole struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
CreateTime *timestamp.Timestamp `protobuf:"bytes,1,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// role_id is the role of this principal.
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
RoleId string `protobuf:"bytes,2,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty" gorm:"primary_key"`
|
||||
// principal_id is the public_id of the user (which is the principal)
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
PrincipalId string `protobuf:"bytes,3,opt,name=principal_id,json=principalId,proto3" json:"principal_id,omitempty" gorm:"primary_key"`
|
||||
// scope_id of the principal role
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
ScopeId string `protobuf:"bytes,4,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *UserRole) Reset() {
|
||||
*x = UserRole{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UserRole) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UserRole) ProtoMessage() {}
|
||||
|
||||
func (x *UserRole) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UserRole.ProtoReflect.Descriptor instead.
|
||||
func (*UserRole) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_iam_store_v1_principal_role_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *UserRole) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UserRole) GetRoleId() string {
|
||||
if x != nil {
|
||||
return x.RoleId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UserRole) GetPrincipalId() string {
|
||||
if x != nil {
|
||||
return x.PrincipalId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UserRole) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GroupRole struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
CreateTime *timestamp.Timestamp `protobuf:"bytes,1,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// role_id is the role of this principal.
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
RoleId string `protobuf:"bytes,2,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty" gorm:"primary_key"`
|
||||
// principal_id is the public_id of the group (which is the principal)
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
PrincipalId string `protobuf:"bytes,3,opt,name=principal_id,json=principalId,proto3" json:"principal_id,omitempty" gorm:"primary_key"`
|
||||
// scope_id of the principal role
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
ScopeId string `protobuf:"bytes,4,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *GroupRole) Reset() {
|
||||
*x = GroupRole{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GroupRole) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GroupRole) ProtoMessage() {}
|
||||
|
||||
func (x *GroupRole) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GroupRole.ProtoReflect.Descriptor instead.
|
||||
func (*GroupRole) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_iam_store_v1_principal_role_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GroupRole) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GroupRole) GetRoleId() string {
|
||||
if x != nil {
|
||||
return x.RoleId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GroupRole) GetPrincipalId() string {
|
||||
if x != nil {
|
||||
return x.PrincipalId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GroupRole) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PrincipalRoleView struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
CreateTime *timestamp.Timestamp `protobuf:"bytes,1,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// role_id is the role of this principal.
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
RoleId string `protobuf:"bytes,2,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty" gorm:"primary_key"`
|
||||
// Principal type (User or Group)
|
||||
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
PrincipalId string `protobuf:"bytes,4,opt,name=principal_id,json=principalId,proto3" json:"principal_id,omitempty" gorm:"primary_key"`
|
||||
// scope_id of the principal role
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
ScopeId string `protobuf:"bytes,5,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) Reset() {
|
||||
*x = PrincipalRoleView{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PrincipalRoleView) ProtoMessage() {}
|
||||
|
||||
func (x *PrincipalRoleView) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PrincipalRoleView.ProtoReflect.Descriptor instead.
|
||||
func (*PrincipalRoleView) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_iam_store_v1_principal_role_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) GetRoleId() string {
|
||||
if x != nil {
|
||||
return x.RoleId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) GetPrincipalId() string {
|
||||
if x != nil {
|
||||
return x.PrincipalId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PrincipalRoleView) GetScopeId() string {
|
||||
if x != nil {
|
||||
return x.ScopeId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_iam_store_v1_principal_role_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_iam_store_v1_principal_role_proto_rawDesc = []byte{
|
||||
0x0a, 0x34, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x61, 0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76,
|
||||
0x31, 0x2f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d,
|
||||
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae, 0x01, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6f,
|
||||
0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
|
||||
0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6e,
|
||||
0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x22, 0xaf, 0x01, 0x0a, 0x09, 0x47, 0x72, 0x6f, 0x75, 0x70,
|
||||
0x52, 0x6f, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72,
|
||||
0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0b, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x19, 0x0a,
|
||||
0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x22, 0xcb, 0x01, 0x0a, 0x11, 0x50, 0x72, 0x69,
|
||||
0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x52, 0x6f, 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x4b,
|
||||
0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
|
||||
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72,
|
||||
0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f,
|
||||
0x6c, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6e,
|
||||
0x63, 0x69, 0x70, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x77,
|
||||
0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 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 (
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_rawDescOnce sync.Once
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_rawDescData = file_controller_storage_iam_store_v1_principal_role_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_controller_storage_iam_store_v1_principal_role_proto_rawDescGZIP() []byte {
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_rawDescOnce.Do(func() {
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_iam_store_v1_principal_role_proto_rawDescData)
|
||||
})
|
||||
return file_controller_storage_iam_store_v1_principal_role_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_iam_store_v1_principal_role_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_controller_storage_iam_store_v1_principal_role_proto_goTypes = []interface{}{
|
||||
(*UserRole)(nil), // 0: controller.storage.iam.store.v1.UserRole
|
||||
(*GroupRole)(nil), // 1: controller.storage.iam.store.v1.GroupRole
|
||||
(*PrincipalRoleView)(nil), // 2: controller.storage.iam.store.v1.PrincipalRoleView
|
||||
(*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp
|
||||
}
|
||||
var file_controller_storage_iam_store_v1_principal_role_proto_depIdxs = []int32{
|
||||
3, // 0: controller.storage.iam.store.v1.UserRole.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 1: controller.storage.iam.store.v1.GroupRole.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // 2: controller.storage.iam.store.v1.PrincipalRoleView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_iam_store_v1_principal_role_proto_init() }
|
||||
func file_controller_storage_iam_store_v1_principal_role_proto_init() {
|
||||
if File_controller_storage_iam_store_v1_principal_role_proto != nil {
|
||||
return
|
||||
}
|
||||
file_controller_storage_iam_store_v1_scope_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UserRole); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GroupRole); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PrincipalRoleView); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_iam_store_v1_principal_role_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_controller_storage_iam_store_v1_principal_role_proto_goTypes,
|
||||
DependencyIndexes: file_controller_storage_iam_store_v1_principal_role_proto_depIdxs,
|
||||
MessageInfos: file_controller_storage_iam_store_v1_principal_role_proto_msgTypes,
|
||||
}.Build()
|
||||
File_controller_storage_iam_store_v1_principal_role_proto = out.File
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_rawDesc = nil
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_goTypes = nil
|
||||
file_controller_storage_iam_store_v1_principal_role_proto_depIdxs = nil
|
||||
}
|
||||
@@ -53,6 +53,10 @@ type Role struct {
|
||||
// disabled is by default false and allows a Role to be marked disabled.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Disabled bool `protobuf:"varint,9,opt,name=disabled,proto3" json:"disabled,omitempty" gorm:"default:null"`
|
||||
// version allows optimistic locking of the role when modifying the role
|
||||
// itself and when modifying dependent items like principal roles.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Version uint32 `protobuf:"varint,10,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
|
||||
}
|
||||
|
||||
func (x *Role) Reset() {
|
||||
@@ -136,6 +140,13 @@ func (x *Role) GetDisabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Role) GetVersion() uint32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_controller_storage_iam_store_v1_role_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_iam_store_v1_role_proto_rawDesc = []byte{
|
||||
@@ -149,7 +160,7 @@ var file_controller_storage_iam_store_v1_role_proto_rawDesc = []byte{
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2b,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61,
|
||||
0x67, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f,
|
||||
0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaa, 0x02, 0x0a, 0x04,
|
||||
0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc4, 0x02, 0x0a, 0x04,
|
||||
0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49,
|
||||
0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
||||
@@ -168,11 +179,13 @@ var file_controller_storage_iam_store_v1_role_proto_rawDesc = []byte{
|
||||
0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
|
||||
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70,
|
||||
0x2f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 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,
|
||||
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x77, 0x61, 0x74, 0x63, 0x68,
|
||||
0x74, 0x6f, 0x77, 0x65, 0x72, 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 (
|
||||
|
||||
@@ -142,6 +142,30 @@ func TestGroup(t *testing.T, conn *gorm.DB, scopeId string, opt ...Option) *Grou
|
||||
return grp
|
||||
}
|
||||
|
||||
func TestUserRole(t *testing.T, conn *gorm.DB, scopeId, roleId, userId string, opt ...Option) *UserRole {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
rw := db.New(conn)
|
||||
r, err := NewUserRole(scopeId, roleId, userId, opt...)
|
||||
require.NoError(err)
|
||||
|
||||
err = rw.Create(context.Background(), r)
|
||||
require.NoError(err)
|
||||
return r.(*UserRole)
|
||||
}
|
||||
|
||||
func TestGroupRole(t *testing.T, conn *gorm.DB, scopeId, roleId, grpId string, opt ...Option) *GroupRole {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
rw := db.New(conn)
|
||||
r, err := NewGroupRole(scopeId, roleId, grpId, opt...)
|
||||
require.NoError(err)
|
||||
|
||||
err = rw.Create(context.Background(), r)
|
||||
require.NoError(err)
|
||||
return r.(*GroupRole)
|
||||
}
|
||||
|
||||
// testAuthAccount is a temporary test function. TODO - replace with an auth
|
||||
// subsystem testAuthAccount function. If userId is zero value, then an auth
|
||||
// account will be created with a null IamUserId
|
||||
|
||||
@@ -85,3 +85,45 @@ func Test_TestRole(t *testing.T) {
|
||||
require.NotNil(projRole)
|
||||
assert.NotEmpty(projRole.PublicId)
|
||||
}
|
||||
|
||||
func Test_TestUserRole(t *testing.T) {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
orgRole := TestRole(t, conn, org.PublicId)
|
||||
projRole := TestRole(t, conn, proj.PublicId)
|
||||
user := TestUser(t, conn, org.PublicId)
|
||||
|
||||
userRole := TestUserRole(t, conn, org.PublicId, orgRole.PublicId, user.PublicId)
|
||||
require.NotNil(userRole)
|
||||
require.Equal(orgRole.PublicId, userRole.RoleId)
|
||||
require.Equal(user.PublicId, userRole.PrincipalId)
|
||||
|
||||
userRole = TestUserRole(t, conn, proj.PublicId, projRole.PublicId, user.PublicId)
|
||||
require.NotNil(userRole)
|
||||
require.Equal(projRole.PublicId, userRole.RoleId)
|
||||
require.Equal(user.PublicId, userRole.PrincipalId)
|
||||
}
|
||||
|
||||
func Test_TestGroupRole(t *testing.T) {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
org, proj := TestScopes(t, conn)
|
||||
orgRole := TestRole(t, conn, org.PublicId)
|
||||
orgGroup := TestGroup(t, conn, org.PublicId)
|
||||
|
||||
projRole := TestRole(t, conn, proj.PublicId)
|
||||
projGroup := TestGroup(t, conn, proj.PublicId)
|
||||
|
||||
groupRole := TestGroupRole(t, conn, org.PublicId, orgRole.PublicId, orgGroup.PublicId)
|
||||
require.NotNil(groupRole)
|
||||
require.Equal(orgRole.PublicId, groupRole.RoleId)
|
||||
require.Equal(orgGroup.PublicId, groupRole.PrincipalId)
|
||||
|
||||
groupRole = TestGroupRole(t, conn, proj.PublicId, projRole.PublicId, projGroup.PublicId)
|
||||
require.NotNil(groupRole)
|
||||
require.Equal(projRole.PublicId, groupRole.RoleId)
|
||||
require.Equal(projGroup.PublicId, groupRole.PrincipalId)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package controller.storage.iam.store.v1;
|
||||
option go_package = "github.com/hashicorp/watchtower/internal/iam/store;store";
|
||||
|
||||
import "controller/storage/timestamp/v1/timestamp.proto";
|
||||
import "controller/storage/iam/store/v1/scope.proto";
|
||||
|
||||
message UserRole {
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 1;
|
||||
|
||||
// role_id is the role of this principal.
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string role_id = 2;
|
||||
|
||||
// principal_id is the public_id of the user (which is the principal)
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string principal_id = 3;
|
||||
|
||||
// scope_id of the principal role
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string scope_id = 4;
|
||||
}
|
||||
|
||||
message GroupRole {
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 1;
|
||||
|
||||
// role_id is the role of this principal.
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string role_id = 2;
|
||||
|
||||
// principal_id is the public_id of the group (which is the principal)
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string principal_id = 3;
|
||||
|
||||
// scope_id of the principal role
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string scope_id = 4;
|
||||
}
|
||||
|
||||
message PrincipalRoleView {
|
||||
// create_time from the RDBMS
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 1;
|
||||
|
||||
// role_id is the role of this principal.
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string role_id = 2;
|
||||
|
||||
// Principal type (User or Group)
|
||||
string type = 3;
|
||||
|
||||
// @inject_tag: gorm:"primary_key"
|
||||
string principal_id = 4;
|
||||
|
||||
// scope_id of the principal role
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string scope_id = 5;
|
||||
}
|
||||
@@ -35,4 +35,9 @@ message Role {
|
||||
// disabled is by default false and allows a Role to be marked disabled.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
bool disabled = 9;
|
||||
|
||||
// version allows optimistic locking of the role when modifying the role
|
||||
// itself and when modifying dependent items like principal roles.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 version = 10;
|
||||
}
|
||||
Reference in new issue
Block a user