Introduce global scope (#168)

Introduce the global scope, which holds some resources that don't necessarily belong at either the org or project level.
pull/179/head
Jeff Mitchell 6 years ago committed by GitHub
parent 6c1b45f7f5
commit 732c07126d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,7 @@ begin;
drop domain wt_timestamp;
drop domain wt_public_id;
drop domain wt_scope_id;
drop domain wt_version;
drop function default_create_time;
@ -35,6 +36,13 @@ check(
comment on domain wt_public_id is
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
create domain wt_scope_id as text
check(
length(trim(value)) > 10 or value = 'global'
);
comment on domain wt_scope_id is
'"global" or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
create domain wt_timestamp as
timestamp with time zone
default current_timestamp;
@ -415,6 +423,7 @@ drop table iam_group cascade;
drop table iam_user cascade;
drop table iam_scope_project cascade;
drop table iam_scope_organization cascade;
drop table iam_scope_global cascade;
drop table iam_scope cascade;
drop table iam_scope_type_enm cascade;
drop table iam_role cascade;
@ -440,12 +449,13 @@ COMMIT;
begin;
create table iam_scope_type_enm (
string text not null primary key check(string in ('unknown', 'organization', 'project'))
string text not null primary key check(string in ('unknown', 'global', 'organization', 'project'))
);
insert into iam_scope_type_enm (string)
values
('unknown'),
('global'),
('organization'),
('project');
@ -468,32 +478,55 @@ 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,
public_id wt_scope_id primary key,
create_time wt_timestamp,
update_time wt_timestamp,
name text,
type text not null references iam_scope_type_enm(string) check(
(
type = 'global'
and parent_id is null
)
or (
type = 'organization'
and parent_id = null
and parent_id = 'global'
)
or (
type = 'project'
and parent_id is not null
and parent_id != 'global'
)
),
description text,
parent_id text references iam_scope(public_id) on delete cascade on update cascade
);
create table iam_scope_global (
scope_id wt_scope_id primary key
references iam_scope(public_id)
on delete cascade
on update cascade
check(
scope_id = 'global'
),
name text unique
);
create table iam_scope_organization (
scope_id wt_public_id not null unique references iam_scope(public_id) on delete cascade on update cascade,
name text unique,
primary key(scope_id)
);
scope_id wt_scope_id primary key
references iam_scope(public_id)
on delete cascade
on update cascade,
parent_id wt_scope_id not null
references iam_scope_global(scope_id)
on delete cascade
on update cascade,
name text,
unique (parent_id, name)
);
create table iam_scope_project (
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
parent_id wt_public_id not null references iam_scope_organization(scope_id) on delete cascade on update cascade,
name text,
unique(parent_id, name),
@ -505,13 +538,19 @@ create or replace function
returns trigger
as $$
declare parent_type int;
begin
if new.type = 'organization' then
insert into iam_scope_organization (scope_id, name)
begin
if new.type = 'global' then
insert into iam_scope_global (scope_id, name)
values
(new.public_id, new.name);
return new;
end if;
if new.type = 'organization' then
insert into iam_scope_organization (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return new;
end if;
if new.type = 'project' then
insert into iam_scope_project (scope_id, parent_id, name)
values
@ -522,13 +561,29 @@ begin
end;
$$ language plpgsql;
create trigger
iam_scope_insert
after
insert on iam_scope
for each row execute procedure iam_sub_scopes_func();
create or replace function
disallow_global_scope_deletion()
returns trigger
as $$
begin
if old.type = 'global' then
raise exception 'deletion of global scope not allowed';
end if;
return old;
end;
$$ language plpgsql;
create trigger
iam_scope_disallow_global_deletion
before
delete on iam_scope
for each row execute procedure disallow_global_scope_deletion();
create or replace function
iam_immutable_scope_type_func()
@ -574,8 +629,12 @@ create or replace function
iam_sub_names()
returns trigger
as $$
begin
begin
if new.name != old.name then
if new.type = 'global' then
update iam_scope_global set name = new.name where scope_id = old.public_id;
return new;
end if;
if new.type = 'organization' then
update iam_scope_organization set name = new.name where scope_id = old.public_id;
return new;
@ -596,13 +655,17 @@ before
update on iam_scope
for each row execute procedure iam_sub_names();
insert into iam_scope (public_id, name, type, description)
values ('global', 'global', 'global', 'Global Scope');
create table iam_user (
public_id wt_public_id not null primary key,
create_time wt_timestamp,
update_time wt_timestamp,
name text,
description text,
scope_id wt_public_id not null references iam_scope_organization(scope_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
unique(name, scope_id),
disabled boolean not null default false,
@ -612,6 +675,25 @@ create table iam_user (
unique(scope_id, public_id)
);
create or replace function
user_scope_id_valid()
returns trigger
as $$
begin
perform from iam_scope where public_id = new.scope_id and type in ('global', 'organization');
if not found then
raise exception 'invalid scope type for user creation';
end if;
return new;
end;
$$ language plpgsql;
create trigger
ensure_user_scope_id_valid
before
insert or update on iam_user
for each row execute procedure user_scope_id_valid();
create trigger
update_time_column
before update on iam_user
@ -640,7 +722,7 @@ create table iam_role (
update_time wt_timestamp,
name text,
description text,
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
unique(name, scope_id),
disabled boolean not null default false,
-- version allows optimistic locking of the role when modifying the role
@ -682,7 +764,7 @@ create table iam_group (
update_time wt_timestamp,
name text,
description text,
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
unique(name, scope_id),
disabled boolean not null default false,
-- add unique index so a composite fk can be declared.
@ -719,7 +801,7 @@ update on iam_group
-- 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,
scope_id wt_scope_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),
@ -736,7 +818,7 @@ create table iam_user_role (
-- update trigger using iam_immutable_role().
create table iam_group_role (
create_time wt_timestamp,
scope_id wt_public_id not null,
scope_id wt_scope_id not null,
role_id wt_public_id not null,
principal_id wt_public_id not null,
primary key (role_id, principal_id),
@ -880,7 +962,7 @@ begin;
-- base table for auth methods
create table auth_method (
public_id wt_public_id primary key,
scope_id wt_public_id not null
scope_id wt_scope_id not null
references iam_scope(public_id)
on delete cascade
on update cascade,
@ -896,7 +978,7 @@ begin;
create table auth_account (
public_id wt_public_id primary key,
auth_method_id wt_public_id not null,
scope_id wt_public_id not null,
scope_id wt_scope_id not null,
iam_user_id wt_public_id,
foreign key (scope_id, auth_method_id)
references auth_method (scope_id, public_id)
@ -962,7 +1044,7 @@ begin;
create table static_host_catalog (
public_id wt_public_id primary key,
scope_id wt_public_id not null
scope_id wt_scope_id not null
references iam_scope (public_id)
on delete cascade
on update cascade,

@ -2,6 +2,7 @@ begin;
drop domain wt_timestamp;
drop domain wt_public_id;
drop domain wt_scope_id;
drop domain wt_version;
drop function default_create_time;

@ -7,6 +7,13 @@ check(
comment on domain wt_public_id is
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
create domain wt_scope_id as text
check(
length(trim(value)) > 10 or value = 'global'
);
comment on domain wt_scope_id is
'"global" or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
create domain wt_timestamp as
timestamp with time zone
default current_timestamp;

@ -4,6 +4,7 @@ drop table iam_group cascade;
drop table iam_user cascade;
drop table iam_scope_project cascade;
drop table iam_scope_organization cascade;
drop table iam_scope_global cascade;
drop table iam_scope cascade;
drop table iam_scope_type_enm cascade;
drop table iam_role cascade;

@ -1,12 +1,13 @@
begin;
create table iam_scope_type_enm (
string text not null primary key check(string in ('unknown', 'organization', 'project'))
string text not null primary key check(string in ('unknown', 'global', 'organization', 'project'))
);
insert into iam_scope_type_enm (string)
values
('unknown'),
('global'),
('organization'),
('project');
@ -29,32 +30,55 @@ 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,
public_id wt_scope_id primary key,
create_time wt_timestamp,
update_time wt_timestamp,
name text,
type text not null references iam_scope_type_enm(string) check(
(
type = 'global'
and parent_id is null
)
or (
type = 'organization'
and parent_id = null
and parent_id = 'global'
)
or (
type = 'project'
and parent_id is not null
and parent_id != 'global'
)
),
description text,
parent_id text references iam_scope(public_id) on delete cascade on update cascade
);
create table iam_scope_global (
scope_id wt_scope_id primary key
references iam_scope(public_id)
on delete cascade
on update cascade
check(
scope_id = 'global'
),
name text unique
);
create table iam_scope_organization (
scope_id wt_public_id not null unique references iam_scope(public_id) on delete cascade on update cascade,
name text unique,
primary key(scope_id)
);
scope_id wt_scope_id primary key
references iam_scope(public_id)
on delete cascade
on update cascade,
parent_id wt_scope_id not null
references iam_scope_global(scope_id)
on delete cascade
on update cascade,
name text,
unique (parent_id, name)
);
create table iam_scope_project (
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
parent_id wt_public_id not null references iam_scope_organization(scope_id) on delete cascade on update cascade,
name text,
unique(parent_id, name),
@ -66,13 +90,19 @@ create or replace function
returns trigger
as $$
declare parent_type int;
begin
if new.type = 'organization' then
insert into iam_scope_organization (scope_id, name)
begin
if new.type = 'global' then
insert into iam_scope_global (scope_id, name)
values
(new.public_id, new.name);
return new;
end if;
if new.type = 'organization' then
insert into iam_scope_organization (scope_id, parent_id, name)
values
(new.public_id, new.parent_id, new.name);
return new;
end if;
if new.type = 'project' then
insert into iam_scope_project (scope_id, parent_id, name)
values
@ -83,13 +113,29 @@ begin
end;
$$ language plpgsql;
create trigger
iam_scope_insert
after
insert on iam_scope
for each row execute procedure iam_sub_scopes_func();
create or replace function
disallow_global_scope_deletion()
returns trigger
as $$
begin
if old.type = 'global' then
raise exception 'deletion of global scope not allowed';
end if;
return old;
end;
$$ language plpgsql;
create trigger
iam_scope_disallow_global_deletion
before
delete on iam_scope
for each row execute procedure disallow_global_scope_deletion();
create or replace function
iam_immutable_scope_type_func()
@ -135,8 +181,12 @@ create or replace function
iam_sub_names()
returns trigger
as $$
begin
begin
if new.name != old.name then
if new.type = 'global' then
update iam_scope_global set name = new.name where scope_id = old.public_id;
return new;
end if;
if new.type = 'organization' then
update iam_scope_organization set name = new.name where scope_id = old.public_id;
return new;
@ -157,13 +207,17 @@ before
update on iam_scope
for each row execute procedure iam_sub_names();
insert into iam_scope (public_id, name, type, description)
values ('global', 'global', 'global', 'Global Scope');
create table iam_user (
public_id wt_public_id not null primary key,
create_time wt_timestamp,
update_time wt_timestamp,
name text,
description text,
scope_id wt_public_id not null references iam_scope_organization(scope_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
unique(name, scope_id),
disabled boolean not null default false,
@ -173,6 +227,25 @@ create table iam_user (
unique(scope_id, public_id)
);
create or replace function
user_scope_id_valid()
returns trigger
as $$
begin
perform from iam_scope where public_id = new.scope_id and type in ('global', 'organization');
if not found then
raise exception 'invalid scope type for user creation';
end if;
return new;
end;
$$ language plpgsql;
create trigger
ensure_user_scope_id_valid
before
insert or update on iam_user
for each row execute procedure user_scope_id_valid();
create trigger
update_time_column
before update on iam_user
@ -201,7 +274,7 @@ create table iam_role (
update_time wt_timestamp,
name text,
description text,
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
unique(name, scope_id),
disabled boolean not null default false,
-- version allows optimistic locking of the role when modifying the role
@ -243,7 +316,7 @@ create table iam_group (
update_time wt_timestamp,
name text,
description text,
scope_id wt_public_id not null references iam_scope(public_id) on delete cascade on update cascade,
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
unique(name, scope_id),
disabled boolean not null default false,
-- add unique index so a composite fk can be declared.
@ -280,7 +353,7 @@ update on iam_group
-- 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,
scope_id wt_scope_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),
@ -297,7 +370,7 @@ create table iam_user_role (
-- update trigger using iam_immutable_role().
create table iam_group_role (
create_time wt_timestamp,
scope_id wt_public_id not null,
scope_id wt_scope_id not null,
role_id wt_public_id not null,
principal_id wt_public_id not null,
primary key (role_id, principal_id),

@ -10,7 +10,7 @@ begin;
-- base table for auth methods
create table auth_method (
public_id wt_public_id primary key,
scope_id wt_public_id not null
scope_id wt_scope_id not null
references iam_scope(public_id)
on delete cascade
on update cascade,
@ -26,7 +26,7 @@ begin;
create table auth_account (
public_id wt_public_id primary key,
auth_method_id wt_public_id not null,
scope_id wt_public_id not null,
scope_id wt_scope_id not null,
iam_user_id wt_public_id,
foreign key (scope_id, auth_method_id)
references auth_method (scope_id, public_id)

@ -2,7 +2,7 @@ begin;
create table static_host_catalog (
public_id wt_public_id primary key,
scope_id wt_public_id not null
scope_id wt_scope_id not null
references iam_scope (public_id)
on delete cascade
on update cascade,

@ -22,6 +22,12 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, opt ...Option) (
if s.PublicId != "" {
return nil, fmt.Errorf("create scope: public id not empty: %w", db.ErrInvalidParameter)
}
switch s.Type {
case scope.Unknown.String():
return nil, fmt.Errorf("create scope: unknown type: %w", db.ErrInvalidParameter)
case scope.Global.String():
return nil, fmt.Errorf("create scope: invalid type: %w", db.ErrInvalidParameter)
}
opts := getOpts(opt...)
var publicId string
t := scope.StringToScopeType(s.Type)
@ -110,6 +116,9 @@ func (r *Repository) DeleteScope(ctx context.Context, withPublicId string, opt .
if withPublicId == "" {
return db.NoRowsAffected, fmt.Errorf("delete scope: missing public id %w", db.ErrInvalidParameter)
}
if withPublicId == scope.Global.String() {
return db.NoRowsAffected, fmt.Errorf("delete scope: invalid to delete global scope: %w", db.ErrInvalidParameter)
}
scope := allocScope()
scope.PublicId = withPublicId
rowsDeleted, err := r.delete(ctx, &scope)
@ -138,7 +147,7 @@ func (r *Repository) ListProjects(ctx context.Context, withOrganizationId string
// ListOrganizations and supports the WithLimit option.
func (r *Repository) ListOrganizations(ctx context.Context, opt ...Option) ([]*Scope, error) {
var organizations []*Scope
err := r.list(ctx, &organizations, "type = ?", []interface{}{scope.Organization.String()}, opt...)
err := r.list(ctx, &organizations, "parent_id = ? and type = ?", []interface{}{"global", scope.Organization.String()}, opt...)
if err != nil {
return nil, fmt.Errorf("list organizations: %w", err)
}

@ -17,7 +17,7 @@ import (
"google.golang.org/protobuf/proto"
)
func Test_Repository_CreateScope(t *testing.T) {
func Test_Repository_Scope_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("valid-scope", func(t *testing.T) {
@ -116,7 +116,7 @@ func Test_Repository_CreateScope(t *testing.T) {
})
}
func Test_Repository_UpdateScope(t *testing.T) {
func Test_Repository_Scope_Update(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("valid-scope", func(t *testing.T) {
@ -180,7 +180,7 @@ func Test_Repository_UpdateScope(t *testing.T) {
})
}
func Test_Repository_LookupScope(t *testing.T) {
func Test_Repository_Scope_Lookup(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("found-and-not-found", func(t *testing.T) {
@ -206,7 +206,7 @@ func Test_Repository_LookupScope(t *testing.T) {
})
}
func Test_Repository_DeleteScope(t *testing.T) {
func Test_Repository_Scope_Delete(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
@ -462,7 +462,7 @@ func TestRepository_UpdateScope(t *testing.T) {
})
}
func TestRepository_ListProjects(t *testing.T) {
func Test_Repository_ListProjects(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
@ -525,7 +525,7 @@ func TestRepository_ListProjects(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
require.NoError(conn.Where("public_id != ?", org.PublicId).Delete(allocScope()).Error)
require.NoError(conn.Where("public_id != ? and public_id != 'global'", org.PublicId).Delete(allocScope()).Error)
testProjects := []*Scope{}
for i := 0; i < tt.createCnt; i++ {
testProjects = append(testProjects, testProject(t, conn, org.PublicId))
@ -542,7 +542,7 @@ func TestRepository_ListProjects(t *testing.T) {
}
}
func TestRepository_ListOrganizations(t *testing.T) {
func Test_Repository_ListOrganizations(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
@ -589,7 +589,7 @@ func TestRepository_ListOrganizations(t *testing.T) {
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(allocScope()).Error)
require.NoError(conn.Where("type = 'organization'").Delete(allocScope()).Error)
testOrgs := []*Scope{}
for i := 0; i < tt.createCnt; i++ {
testOrgs = append(testOrgs, testOrg(t, conn, "", ""))

@ -14,7 +14,7 @@ import (
)
// Scope is used to create a hierarchy of "containers" that encompass the scope of
// an IAM resource. Scopes are Organizations and Projects.
// an IAM resource. Scopes are Global, Organizations and Projects.
type Scope struct {
*store.Scope
@ -29,6 +29,9 @@ var _ db.VetForWriter = (*Scope)(nil)
var _ Clonable = (*Scope)(nil)
func NewOrganization(opt ...Option) (*Scope, error) {
global := allocScope()
global.PublicId = "global"
opt = append(opt, withScope(&global))
return newScope(scope.Organization, opt...)
}
@ -48,15 +51,20 @@ func NewProject(organizationPublicId string, opt ...Option) (*Scope, error) {
// scope's description. WithScope specifies the Scope's parent
func newScope(scopeType scope.Type, opt ...Option) (*Scope, error) {
opts := getOpts(opt...)
if scopeType == scope.Unknown {
return nil, fmt.Errorf("new scope: unknown scope type %w", db.ErrInvalidParameter)
}
if opts.withScope != nil && opts.withScope.PublicId == "" {
return nil, fmt.Errorf("new scope: with scope's parent id is missing %w", db.ErrInvalidParameter)
}
if scopeType == scope.Project && opts.withScope == nil {
return nil, fmt.Errorf("new scope: project scope is missing its parent %w", db.ErrInvalidParameter)
switch scopeType {
case scope.Unknown:
return nil, fmt.Errorf("new scope: unknown scope type: %w", db.ErrInvalidParameter)
case scope.Global:
return nil, fmt.Errorf("new scope: invalid scope type: %w", db.ErrInvalidParameter)
default:
if opts.withScope == nil {
return nil, fmt.Errorf("new scope: child scope is missing its parent: %w", db.ErrInvalidParameter)
}
if opts.withScope.PublicId == "" {
return nil, fmt.Errorf("new scope: with scope's parent id is missing: %w", db.ErrInvalidParameter)
}
}
s := &Scope{
Scope: &store.Scope{
Type: scopeType.String(),
@ -106,10 +114,21 @@ func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType,
}
}
if opType == db.CreateOp {
if s.ParentId == "" && s.Type == scope.Project.String() {
return errors.New("project has no organization")
}
if s.Type == scope.Project.String() {
switch {
case s.Type == scope.Global.String():
return errors.New("global scope cannot be created")
case s.ParentId == "":
switch s.Type {
case scope.Organization.String():
return errors.New("organization must have global parent")
case scope.Project.String():
return errors.New("project has no organization")
}
case s.Type == scope.Organization.String():
if s.ParentId != "global" {
return errors.New(`organization's parent must be "global"`)
}
case s.Type == scope.Project.String():
parentScope := allocScope()
parentScope.PublicId = s.ParentId
if err := r.LookupByPublicId(ctx, &parentScope, opt...); err != nil {
@ -126,6 +145,8 @@ func (s *Scope) VetForWrite(ctx context.Context, r db.Reader, opType db.OpType,
// ResourceType returns the type of scope
func (s *Scope) ResourceType() resource.Type {
switch s.Type {
case scope.Global.String():
return resource.Global
case scope.Organization.String():
return resource.Organization
case scope.Project.String():
@ -153,19 +174,19 @@ func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
return nil, fmt.Errorf("unable to get scope by public id: %w", err)
}
}
// HANDLE_ORG
// HANDLE_GLOBAL
switch s.Type {
case scope.Organization.String():
case scope.Global.String():
return nil, nil
case scope.Project.String():
default:
var p Scope
switch s.ParentId {
case "":
// no parent id, so use the public_id to find the parent scope. This
// won't work for if the scope hasn't been written to the db yet,
// like during create but in that case the parent id should be set
// for all scopes which are not organizations, and the organization
// case was handled at HANDLE_ORG
// for all scopes which are not global, and the global case was
// handled at HANDLE_GLOBAL
where := "public_id in (select parent_id from iam_scope where public_id = ?)"
if err := r.LookupWhere(ctx, &p, where, s.PublicId); err != nil {
return nil, fmt.Errorf("unable to lookup parent public id from public id: %w", err)
@ -177,7 +198,6 @@ func (s *Scope) GetScope(ctx context.Context, r db.Reader) (*Scope, error) {
}
return &p, nil
}
return nil, fmt.Errorf("unable to get scope with type %s", s.Type)
}
// TableName returns the tablename to override the default gorm table name

@ -2,9 +2,11 @@ package iam
import (
"context"
"strings"
"testing"
"github.com/hashicorp/watchtower/internal/db"
"github.com/hashicorp/watchtower/internal/iam/store"
"github.com/hashicorp/watchtower/internal/types/action"
"github.com/hashicorp/watchtower/internal/types/resource"
"github.com/hashicorp/watchtower/internal/types/scope"
@ -13,7 +15,7 @@ import (
"google.golang.org/protobuf/proto"
)
func Test_NewScope(t *testing.T) {
func TestScope_New(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("valid-org-with-project", func(t *testing.T) {
@ -40,17 +42,17 @@ func Test_NewScope(t *testing.T) {
s, err := newScope(scope.Unknown)
require.Error(err)
require.Nil(s)
assert.Equal(err.Error(), "new scope: unknown scope type invalid parameter")
assert.Equal(err.Error(), "new scope: unknown scope type: invalid parameter")
})
t.Run("proj-scope-with-no-org", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewProject("")
require.Error(err)
require.Nil(s)
assert.Equal(err.Error(), "error creating new project: new scope: with scope's parent id is missing invalid parameter")
assert.Equal(err.Error(), "error creating new project: new scope: with scope's parent id is missing: invalid parameter")
})
}
func Test_ScopeCreate(t *testing.T) {
func TestScope_Create(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("valid", func(t *testing.T) {
@ -92,7 +94,7 @@ func Test_ScopeCreate(t *testing.T) {
})
}
func Test_ScopeUpdate(t *testing.T) {
func TestScope_Update(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("valid", func(t *testing.T) {
@ -116,16 +118,23 @@ func Test_ScopeUpdate(t *testing.T) {
assert.Equal(0, updatedRows)
})
}
func Test_ScopeGetScope(t *testing.T) {
func TestScope_GetScope(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
t.Run("valid-scope", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w := db.New(conn)
org, proj := TestScopes(t, conn)
global := Scope{
Scope: &store.Scope{Type: scope.Global.String(), PublicId: "global"},
}
globalScope, err := global.GetScope(context.Background(), w)
require.NoError(err)
assert.Nil(globalScope)
foundScope, err := org.GetScope(context.Background(), w)
require.NoError(err)
assert.Nil(foundScope)
assert.Equal(foundScope.PublicId, "global")
projectOrg, err := proj.GetScope(context.Background(), w)
require.NoError(err)
@ -159,6 +168,7 @@ func TestScope_ResourceType(t *testing.T) {
o, err := NewOrganization()
require.NoError(t, err)
assert.Equal(t, o.ResourceType(), resource.Organization)
assert.Equal(t, o.GetParentId(), resource.Global.String())
}
func TestScope_Clone(t *testing.T) {
@ -178,3 +188,55 @@ func TestScope_Clone(t *testing.T) {
assert.True(!proto.Equal(cp.(*Scope).Scope, s2.Scope))
})
}
// TestScope_GlobalErrors tests various expected error conditions related to the
// global scope at a layer below the repository, e.g. within the scope logic or the
// DB itself
func TestScope_GlobalErrors(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
w := db.New(conn)
t.Run("newScope errors", func(t *testing.T) {
// Not allowed
_, err := newScope(scope.Global)
require.Error(t, err)
// Should fail as there's no scope
_, err = newScope(scope.Organization)
require.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "missing its parent"))
})
t.Run("creation disallowed at vet time", func(t *testing.T) {
// Not allowed to create
s := allocScope()
s.Type = scope.Global.String()
s.PublicId = "global"
err := s.VetForWrite(context.Background(), nil, db.CreateOp)
require.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "global scope cannot be created"))
})
t.Run("check org parent at vet time", func(t *testing.T) {
// Org must have global parent
s := allocScope()
s.Type = scope.Organization.String()
s.PublicId = "o_1234"
s.ParentId = "global"
err := s.VetForWrite(context.Background(), nil, db.CreateOp)
require.NoError(t, err)
s.ParentId = "o_2345"
err = s.VetForWrite(context.Background(), nil, db.CreateOp)
require.Error(t, err)
})
t.Run("not deletable in db", func(t *testing.T) {
// Should not be deletable
s := allocScope()
s.PublicId = "global"
// Add this to validate that we did in fact delete
err := w.LookupById(context.Background(), &s)
require.NoError(t, err)
require.Equal(t, s.Type, scope.Global.String())
rows, err := w.Delete(context.Background(), &s)
require.Error(t, err)
assert.Equal(t, 0, rows)
})
}

@ -23,6 +23,7 @@ const (
HostSet Type = 16
Host Type = 17
Target Type = 18
Global Type = 19
)
func (r Type) String() string {
@ -46,6 +47,7 @@ func (r Type) String() string {
"host-set",
"host",
"target",
"global",
}[r]
}
@ -87,6 +89,8 @@ func StringToResourceType(s string) Type {
return Host
case Target.String():
return Target
case Global.String():
return Global
default:
return Unknown
}

@ -88,6 +88,10 @@ func Test_Resource(t *testing.T) {
typeString: "target",
want: Target,
},
{
typeString: "global",
want: Global,
},
}
for _, tt := range tests {
t.Run(tt.typeString, func(t *testing.T) {

@ -5,13 +5,15 @@ type Type uint32
const (
Unknown Type = 0
Organization Type = 1
Project Type = 2
Global Type = 1
Organization Type = 2
Project Type = 3
)
func (s Type) String() string {
return [...]string{
"unknown",
"global",
"organization",
"project",
}[s]
@ -20,6 +22,7 @@ func (s Type) String() string {
func (s Type) Prefix() string {
return [...]string{
"unknown",
"global",
"o",
"p",
}[s]
@ -27,6 +30,8 @@ func (s Type) Prefix() string {
func StringToScopeType(s string) Type {
switch s {
case Global.String():
return Global
case Organization.String():
return Organization
case Project.String():

@ -13,6 +13,12 @@ func Test_StringToScopeType(t *testing.T) {
want Type
wantPrefix string
}{
{
name: "global",
s: "global",
want: Global,
wantPrefix: "global",
},
{
name: "org",
s: "organization",

Loading…
Cancel
Save