Introduce global scope (#168)
Introduce the global scope, which holds some resources that don't necessarily belong at either the org or project level.
This commit is contained in:
1 parent
6c1b45f7f5
commit
732c07126d
15 files changed
+349
-75
No files matched your search
@@ -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),
|
||||
@@ -506,12 +539,18 @@ create or replace function
|
||||
as $$
|
||||
declare parent_type int;
|
||||
begin
|
||||
if new.type = 'organization' then
|
||||
insert into iam_scope_organization (scope_id, name)
|
||||
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()
|
||||
@@ -576,6 +631,10 @@ create or replace function
|
||||
as $$
|
||||
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),
|
||||
@@ -67,12 +91,18 @@ create or replace function
|
||||
as $$
|
||||
declare parent_type int;
|
||||
begin
|
||||
if new.type = 'organization' then
|
||||
insert into iam_scope_organization (scope_id, name)
|
||||
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()
|
||||
@@ -137,6 +183,10 @@ create or replace function
|
||||
as $$
|
||||
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, "", ""))
|
||||
|
||||
+39
-19
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user