add principal roles (users and groups) to iam (#163)

This commit is contained in:
Jim authored and GitHub committed 2020-07-08 17:02:01 -04:00
1 parent beea32b8c8
commit 6c1b45f7f5
23 files changed
+3248 -53

No files matched your search

+3
View File
@@ -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
+192 -9
View File
@@ -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;
+181 -4
View File
@@ -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;
+10
View File
@@ -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
}
}
+14
View File
@@ -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)
})
}
+10 -6
View File
@@ -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
View File
@@ -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
}
}
+230
View File
@@ -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
}
}
+747
View File
@@ -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)
}
+7 -1
View File
@@ -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
+31 -5
View File
@@ -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")
+453
View File
@@ -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)
}
+2 -1
View File
@@ -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)
+27 -2
View File
@@ -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)
+422
View File
@@ -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
}
+19 -6
View File
@@ -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 (
+24
View File
@@ -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
+42
View File
@@ -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;
}