mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2086 lines
60 KiB
2086 lines
60 KiB
// Code generated by "make migrations"; DO NOT EDIT.
|
|
package migrations
|
|
|
|
var postgresMigrations = map[string]*fakeFile{
|
|
"migrations": {
|
|
name: "migrations",
|
|
},
|
|
"migrations/01_domain_types.down.sql": {
|
|
name: "01_domain_types.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop domain wt_timestamp;
|
|
drop domain wt_public_id;
|
|
drop domain wt_private_id;
|
|
drop domain wt_scope_id;
|
|
drop domain wt_user_id;
|
|
drop domain wt_version;
|
|
|
|
drop function default_create_time;
|
|
drop function update_time_column;
|
|
drop function update_version_column;
|
|
drop function immutable_columns;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/01_domain_types.up.sql": {
|
|
name: "01_domain_types.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create domain wt_public_id as text
|
|
check(
|
|
length(trim(value)) > 10
|
|
);
|
|
comment on domain wt_public_id is
|
|
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_private_id as text
|
|
check(
|
|
length(trim(value)) > 10
|
|
);
|
|
comment on domain wt_private_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_user_id as text
|
|
check(
|
|
length(trim(value)) > 10 or value = 'u_anon' or value = 'u_auth'
|
|
);
|
|
comment on domain wt_scope_id is
|
|
'"u_anon", "u_auth", or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_role_id as text
|
|
check(
|
|
length(trim(value)) > 10 or value = 'r_default'
|
|
);
|
|
comment on domain wt_scope_id is
|
|
'"r_default", or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_timestamp as
|
|
timestamp with time zone
|
|
default current_timestamp;
|
|
comment on domain wt_timestamp is
|
|
'Standard timestamp for all create_time and update_time columns';
|
|
|
|
create or replace function
|
|
update_time_column()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if row(new.*) is distinct from row(old.*) then
|
|
new.update_time = now();
|
|
return new;
|
|
else
|
|
return old;
|
|
end if;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
update_time_column()
|
|
is
|
|
'function used in before update triggers to properly set update_time columns';
|
|
|
|
create or replace function
|
|
default_create_time()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.create_time is distinct from now() then
|
|
raise warning 'create_time cannot be set to %', new.create_time;
|
|
new.create_time = now();
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
default_create_time()
|
|
is
|
|
'function used in before insert triggers to set create_time column to now';
|
|
|
|
|
|
create domain wt_version as bigint
|
|
default 1
|
|
check(
|
|
value > 0
|
|
);
|
|
comment on domain wt_version is
|
|
'standard column for row version';
|
|
|
|
-- update_version_column() will increment the version column whenever row data
|
|
-- is updated and should only be used in an update after trigger. This function
|
|
-- will overwrite any explicit updates to the version column.
|
|
create or replace function
|
|
update_version_column()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if pg_trigger_depth() = 1 then
|
|
if row(new.*) is distinct from row(old.*) then
|
|
execute format('update %I set version = $1 where public_id = $2', tg_relid::regclass) using old.version+1, new.public_id;
|
|
new.version = old.version + 1;
|
|
return new;
|
|
end if;
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
update_version_column()
|
|
is
|
|
'function used in after update triggers to properly set version columns';
|
|
|
|
-- immutable_columns() will make the column names immutable which are passed as
|
|
-- parameters when the trigger is created. It raises error code 23601 which is a
|
|
-- class 23 integrity constraint violation: immutable column
|
|
create or replace function
|
|
immutable_columns()
|
|
returns trigger
|
|
as $$
|
|
declare
|
|
col_name text;
|
|
new_value text;
|
|
old_value text;
|
|
begin
|
|
foreach col_name in array tg_argv loop
|
|
execute format('SELECT $1.%I', col_name) into new_value using new;
|
|
execute format('SELECT $1.%I', col_name) into old_value using old;
|
|
if new_value is distinct from old_value then
|
|
raise exception 'immutable column: %.%', tg_table_name, col_name using
|
|
errcode = '23601',
|
|
schema = tg_table_schema,
|
|
table = tg_table_name,
|
|
column = col_name;
|
|
end if;
|
|
end loop;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
immutable_columns()
|
|
is
|
|
'function used in before update triggers to make columns immutable';
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/02_oplog.down.sql": {
|
|
name: "02_oplog.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table oplog_metadata cascade;
|
|
drop table oplog_ticket cascade;
|
|
drop table oplog_entry cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/02_oplog.up.sql": {
|
|
name: "02_oplog.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- TODO (jimlambrt 7/2020) remove update_time
|
|
create table if not exists oplog_entry (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
version text not null,
|
|
aggregate_name text not null,
|
|
"data" bytea not null
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on oplog_entry
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on oplog_entry
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- oplog_entry is immutable.
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on oplog_entry
|
|
for each row execute procedure immutable_columns('id','update_time','create_time','version','aggregate_name', 'data');
|
|
|
|
create table if not exists oplog_ticket (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
"name" text not null unique,
|
|
"version" bigint not null
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on oplog_ticket
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on oplog_ticket
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- oplog_ticket: only allow updates to: version and update_time
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on oplog_ticket
|
|
for each row execute procedure immutable_columns('id','create_time','name');
|
|
|
|
-- TODO (jimlambrt 7/2020) remove update_time
|
|
create table if not exists oplog_metadata (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
entry_id bigint not null references oplog_entry(id) on delete cascade on update cascade,
|
|
"key" text not null,
|
|
value text null
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on oplog_metadata
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on oplog_metadata
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- oplog_metadata is immutable
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on oplog_metadata
|
|
for each row execute procedure immutable_columns('id','create_time','update_time','entry_id','key','value');
|
|
|
|
create index if not exists idx_oplog_metatadata_key on oplog_metadata(key);
|
|
|
|
create index if not exists idx_oplog_metatadata_value on oplog_metadata(value);
|
|
|
|
insert into oplog_ticket (name, version)
|
|
values
|
|
('auth_token', 1),
|
|
('default', 1),
|
|
('iam_scope', 1),
|
|
('iam_user', 1),
|
|
('iam_group', 1),
|
|
('iam_group_member', 1),
|
|
('iam_role', 1),
|
|
('iam_role_grant', 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),
|
|
('iam_principal_role', 1);
|
|
|
|
|
|
commit;
|
|
|
|
|
|
`),
|
|
},
|
|
"migrations/03_db.down.sql": {
|
|
name: "03_db.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table db_test_rental cascade;
|
|
drop table db_test_car cascade;
|
|
drop table db_test_user cascade;
|
|
drop table db_test_scooter cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/03_db.up.sql": {
|
|
name: "03_db.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- create test tables used in the unit tests for the internal/db package
|
|
-- these tables (db_test_user, db_test_car, db_test_rental, db_test_scooter) are
|
|
-- not part of the watchtower domain model... they are simply used for testing
|
|
-- the internal/db package
|
|
create table if not exists db_test_user (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
public_id text not null unique,
|
|
name text unique,
|
|
phone_number text,
|
|
email text,
|
|
version wt_version
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_user
|
|
for each row execute procedure update_time_column();
|
|
|
|
-- define the immutable fields for db_test_user
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_user
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_user
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on db_test_user
|
|
for each row execute procedure update_version_column();
|
|
|
|
create table if not exists db_test_car (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
public_id text not null unique,
|
|
name text unique,
|
|
model text,
|
|
mpg smallint
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_car
|
|
for each row execute procedure update_time_column();
|
|
|
|
-- define the immutable fields for db_test_car
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_car
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_car
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table if not exists db_test_rental (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
public_id text not null unique,
|
|
name text unique,
|
|
user_id bigint not null references db_test_user(id),
|
|
car_id bigint not null references db_test_car(id)
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_rental
|
|
for each row execute procedure update_time_column();
|
|
|
|
-- define the immutable fields for db_test_rental
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_rental
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_rental
|
|
for each row execute procedure default_create_time();
|
|
|
|
|
|
create table if not exists db_test_scooter (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
private_id text not null unique,
|
|
name text unique,
|
|
model text,
|
|
mpg smallint
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_scooter
|
|
for each row execute procedure update_time_column();
|
|
|
|
|
|
-- define the immutable fields for db_test_scooter
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_scooter
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_scooter
|
|
for each row execute procedure default_create_time();
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/06_iam.down.sql": {
|
|
name: "06_iam.down.sql",
|
|
bytes: []byte(`
|
|
BEGIN;
|
|
|
|
drop table iam_group cascade;
|
|
drop table iam_user cascade;
|
|
drop table iam_scope_project cascade;
|
|
drop table iam_scope_org cascade;
|
|
drop table iam_scope_global 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 table iam_group_member_user cascade;
|
|
drop view iam_group_member cascade;
|
|
drop table iam_role_grant 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_principal cascade;
|
|
drop function iam_user_role_scope_check cascade;
|
|
drop function iam_group_role_scope_check cascade;
|
|
drop function iam_group_member_scope_check cascade;
|
|
drop function iam_immutable_group_member cascade;
|
|
drop function get_scoped_member_id cascade;
|
|
drop function grant_scope_id_valid cascade;
|
|
drop function disallow_global_scope_deletion cascade;
|
|
drop function user_scope_id_valid cascade;
|
|
drop function iam_immutable_role_grant cascade;
|
|
drop function disallow_iam_anon_auth_deletion cascade;
|
|
|
|
COMMIT;
|
|
|
|
`),
|
|
},
|
|
"migrations/06_iam.up.sql": {
|
|
name: "06_iam.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create table iam_scope_type_enm (
|
|
string text not null primary key check(string in ('unknown', 'global', 'org', 'project'))
|
|
);
|
|
|
|
insert into iam_scope_type_enm (string)
|
|
values
|
|
('unknown'),
|
|
('global'),
|
|
('org'),
|
|
('project');
|
|
|
|
-- define the immutable fields of iam_scope_type_enm
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_type_enm
|
|
for each row execute procedure immutable_columns('string');
|
|
|
|
create table iam_scope (
|
|
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 = 'org'
|
|
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_org (
|
|
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_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_org(scope_id) on delete cascade on update cascade,
|
|
name text,
|
|
unique(parent_id, name),
|
|
primary key(scope_id, parent_id)
|
|
);
|
|
|
|
create or replace function
|
|
iam_sub_scopes_func()
|
|
returns trigger
|
|
as $$
|
|
declare parent_type int;
|
|
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 = 'org' then
|
|
insert into iam_scope_org (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
|
|
(new.public_id, new.parent_id, new.name);
|
|
return new;
|
|
end if;
|
|
raise exception 'unknown scope type';
|
|
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 trigger
|
|
update_time_column
|
|
before update on iam_scope
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_scope
|
|
for each row execute procedure default_create_time();
|
|
|
|
|
|
-- define the immutable fields for iam_scope
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'type', 'parent_id');
|
|
|
|
-- define the immutable fields of iam_scope_global
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_global
|
|
for each row execute procedure immutable_columns('scope_id');
|
|
|
|
-- define the immutable fields of iam_scope_org
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_org
|
|
for each row execute procedure immutable_columns('scope_id');
|
|
|
|
-- define the immutable fields of iam_scope_project
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_project
|
|
for each row execute procedure immutable_columns('scope_id');
|
|
|
|
|
|
-- iam_sub_names will allow us to enforce the different name constraints for
|
|
-- orgs and projects via a before update trigger on the iam_scope
|
|
-- table.
|
|
create or replace function
|
|
iam_sub_names()
|
|
returns trigger
|
|
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 = 'org' then
|
|
update iam_scope_org set name = new.name where scope_id = old.public_id;
|
|
return new;
|
|
end if;
|
|
if new.type = 'project' then
|
|
update iam_scope_project set name = new.name where scope_id = old.public_id;
|
|
return new;
|
|
end if;
|
|
raise exception 'unknown scope type';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
iam_sub_names
|
|
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_user_id primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
description text,
|
|
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,
|
|
|
|
-- The order of columns is important for performance. See:
|
|
-- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
-- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field
|
|
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', 'org');
|
|
if not found then
|
|
raise exception 'invalid scope type for user creation';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
grant_scope_id_valid()
|
|
returns trigger
|
|
as $$
|
|
declare parent_scope_id text;
|
|
declare role_scope_type text;
|
|
begin
|
|
-- There is a not-null constraint so ensure that if the value passed in is
|
|
-- empty we simply set to the scope ID
|
|
if new.grant_scope_id = '' or new.grant_scope_id is null then
|
|
new.grant_scope_id = new.scope_id;
|
|
end if;
|
|
-- If the scopes match, it's allowed
|
|
if new.grant_scope_id = new.scope_id then
|
|
return new;
|
|
end if;
|
|
-- Fetch the type of scope
|
|
select isc.type from iam_scope isc where isc.public_id = new.scope_id into role_scope_type;
|
|
-- Always allowed
|
|
if role_scope_type = 'global' then
|
|
return new;
|
|
end if;
|
|
-- Never allowed; the case where it's set to the same scope ID as the project
|
|
-- itself is covered above
|
|
if role_scope_type = 'project' then
|
|
raise exception 'invalid to set grant_scope_id to non-same scope_id when role scope type is project';
|
|
end if;
|
|
if role_scope_type = 'org' then
|
|
-- Look up the parent scope ID for the scope ID given
|
|
select isc.parent_id from iam_scope isc where isc.public_id = new.grant_scope_id into parent_scope_id;
|
|
-- Allow iff the grant scope ID's parent matches the role's scope ID; that
|
|
-- is, match if the role belongs to a direct child scope of this
|
|
-- org
|
|
if parent_scope_id = new.scope_id then
|
|
return new;
|
|
end if;
|
|
raise exception 'grant_scope_id is not a child project of the role scope';
|
|
end if;
|
|
raise exception 'unknown scope type';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
disallow_iam_anon_auth_deletion()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if old.public_id = 'u_anon' then
|
|
raise exception 'deletion of anonymous user not allowed';
|
|
end if;
|
|
if old.public_id = 'u_auth' then
|
|
raise exception 'deletion of authenticated user not allowed';
|
|
end if;
|
|
return old;
|
|
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
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_user
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
iam_user_disallow_anon_auth_deletion
|
|
before
|
|
delete on iam_user
|
|
for each row execute procedure disallow_iam_anon_auth_deletion();
|
|
|
|
-- TODO: Do we want to disallow changing the name or description?
|
|
insert into iam_user (public_id, name, description, scope_id)
|
|
values ('u_anon', 'anonymous', 'The anonymous user matches any request, whether authenticated or not', 'global');
|
|
|
|
insert into iam_user (public_id, name, description, scope_id)
|
|
values ('u_auth', 'authenticated', 'The authenticated user matches any user that has a valid token', 'global');
|
|
|
|
-- define the immutable fields for iam_user
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_user
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'scope_id');
|
|
|
|
create table iam_role (
|
|
public_id wt_role_id primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
description text,
|
|
scope_id wt_scope_id not null references iam_scope(public_id) on delete cascade on update cascade,
|
|
grant_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
|
|
-- 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)
|
|
);
|
|
|
|
-- Grants are immutable, which is enforced via the trigger below
|
|
create table iam_role_grant (
|
|
create_time wt_timestamp,
|
|
role_id wt_role_id -- pk
|
|
references iam_role(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
canonical_grant text -- pk
|
|
check(
|
|
length(trim(canonical_grant)) > 0
|
|
),
|
|
raw_grant text not null
|
|
check(
|
|
length(trim(raw_grant)) > 0
|
|
),
|
|
primary key(role_id, canonical_grant)
|
|
);
|
|
|
|
-- iam_immutable_role_grant() ensures that grants assigned to roles are immutable.
|
|
create or replace function
|
|
iam_immutable_role_grant()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'role grants are immutable';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger immutable_role_grant
|
|
before
|
|
update on iam_role_grant
|
|
for each row execute procedure iam_immutable_role_grant();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_role_grant
|
|
for each row execute procedure default_create_time();
|
|
|
|
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
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_role
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
ensure_grant_scope_id_valid
|
|
before
|
|
insert or update on iam_role
|
|
for each row execute procedure grant_scope_id_valid();
|
|
|
|
-- disallow_default_role_deletion prevents the default role (r_default) from
|
|
-- being deleted
|
|
create or replace function
|
|
disallow_default_role_deletion()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if old.public_id = 'r_default' then
|
|
raise exception 'deletion of default role not allowed';
|
|
end if;
|
|
return old;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
disallow_default_role_deletion
|
|
before
|
|
delete on iam_role
|
|
for each row execute procedure disallow_default_role_deletion();
|
|
|
|
-- define the immutable fields for iam_role (started trigger name with "a_" so
|
|
-- it will run first)
|
|
create trigger
|
|
a_immutable_columns
|
|
before
|
|
update on iam_role
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'scope_id');
|
|
|
|
insert into iam_role (public_id, name, description, scope_id)
|
|
values('r_default', 'default', 'default role', 'global');
|
|
|
|
insert into iam_role_grant (role_id, canonical_grant, raw_grant)
|
|
values('r_default', 'type=org;actions=list', 'type=org;actions=list');
|
|
|
|
create table iam_group (
|
|
public_id wt_public_id not null primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
description text,
|
|
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 group when modifying the group
|
|
-- itself and when modifying dependent items like group members.
|
|
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_group
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on iam_group
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_group
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- define the immutable fields for iam_group
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_group
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'scope_id');
|
|
|
|
-- iam_user_role contains roles that have been assigned to users. Users can be
|
|
-- from any scope. The rows in this table must be immutable after insert, which
|
|
-- will be ensured with a before update trigger using
|
|
-- iam_immutable_role_principal().
|
|
create table iam_user_role (
|
|
create_time wt_timestamp,
|
|
role_id wt_role_id
|
|
references iam_role(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
principal_id wt_user_id
|
|
references iam_user(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
primary key (role_id, principal_id)
|
|
);
|
|
|
|
-- iam_group_role contains roles that have been assigned to groups.
|
|
-- Groups can be from any scope. The rows in this table must be immutable after
|
|
-- insert, which will be ensured with a before update trigger using
|
|
-- iam_immutable_role_principal().
|
|
create table iam_group_role (
|
|
create_time wt_timestamp,
|
|
role_id wt_role_id
|
|
references iam_role(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
principal_id wt_public_id
|
|
references iam_group(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
primary key (role_id, principal_id)
|
|
);
|
|
|
|
-- get_scoped_principal_id is used by the iam_principle_role view as a convient
|
|
-- way to create <scope_id>:<principal_id> to reference principals from
|
|
-- other scopes than the role's scope.
|
|
create or replace function get_scoped_principal_id(role_scope text, principal_scope text, principal_id text) returns text
|
|
as $$
|
|
begin
|
|
if role_scope = principal_scope then
|
|
return principal_id;
|
|
end if;
|
|
return principal_scope || ':' || principal_id;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
insert into iam_user_role (role_id, principal_id)
|
|
values
|
|
('r_default', 'u_anon'),
|
|
('r_default', 'u_auth');
|
|
|
|
|
|
-- iam_principle_role provides a consolidated view all principal roles assigned
|
|
-- (user and group roles).
|
|
create view iam_principal_role as
|
|
select
|
|
ur.create_time,
|
|
ur.principal_id,
|
|
ur.role_id,
|
|
u.scope_id as principal_scope_id,
|
|
r.scope_id as role_scope_id,
|
|
get_scoped_principal_id(r.scope_id, u.scope_id, ur.principal_id) as scoped_principal_id,
|
|
'user' as type
|
|
from
|
|
iam_user_role ur,
|
|
iam_role r,
|
|
iam_user u
|
|
where
|
|
ur.role_id = r.public_id and
|
|
u.public_id = ur.principal_id
|
|
union
|
|
select
|
|
gr.create_time,
|
|
gr.principal_id,
|
|
gr.role_id,
|
|
g.scope_id as principal_scope_id,
|
|
r.scope_id as role_scope_id,
|
|
get_scoped_principal_id(r.scope_id, g.scope_id, gr.principal_id) as scoped_principal_id,
|
|
'group' as type
|
|
from
|
|
iam_group_role gr,
|
|
iam_role r,
|
|
iam_group g
|
|
where
|
|
gr.role_id = r.public_id and
|
|
g.public_id = gr.principal_id;
|
|
|
|
-- iam_immutable_role_principal() ensures that roles assigned to principals are immutable.
|
|
create or replace function
|
|
iam_immutable_role_principal()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'roles are immutable';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger immutable_role_principal
|
|
before
|
|
update on iam_user_role
|
|
for each row execute procedure iam_immutable_role_principal();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_user_role
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger immutable_role_principal
|
|
before
|
|
update on iam_group_role
|
|
for each row execute procedure iam_immutable_role_principal();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_group_role
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- iam_group_member_user is an association table that represents groups with
|
|
-- associated users.
|
|
create table iam_group_member_user (
|
|
create_time wt_timestamp,
|
|
group_id wt_public_id references iam_group(public_id) on delete cascade on update cascade,
|
|
member_id wt_public_id references iam_user(public_id) on delete cascade on update cascade,
|
|
primary key (group_id, member_id)
|
|
);
|
|
|
|
-- iam_immutable_group_member() ensures that group members are immutable.
|
|
create or replace function
|
|
iam_immutable_group_member()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'group members are immutable';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_group_member_user
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger iam_immutable_group_member
|
|
before
|
|
update on iam_group_member_user
|
|
for each row execute procedure iam_immutable_group_member();
|
|
|
|
-- get_scoped_principal_id is used by the iam_group_member view as a convient
|
|
-- way to create <scope_id>:<member_id> to reference members from
|
|
-- other scopes than the group's scope.
|
|
create or replace function get_scoped_member_id(group_scope text, member_scope text, member_id text) returns text
|
|
as $$
|
|
begin
|
|
if group_scope = member_scope then
|
|
return member_id;
|
|
end if;
|
|
return member_scope || ':' || member_id;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- iam_group_member provides a consolidated view of group members.
|
|
create view iam_group_member as
|
|
select
|
|
gm.create_time,
|
|
gm.group_id,
|
|
gm.member_id,
|
|
u.scope_id as member_scope_id,
|
|
g.scope_id as group_scope_id,
|
|
get_scoped_member_id(g.scope_id, u.scope_id, gm.member_id) as scoped_member_id,
|
|
'user' as type
|
|
from
|
|
iam_group_member_user gm,
|
|
iam_user u,
|
|
iam_group g
|
|
where
|
|
gm.member_id = u.public_id and
|
|
gm.group_id = g.public_id;
|
|
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/07_auth.down.sql": {
|
|
name: "07_auth.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop function insert_auth_account_subtype;
|
|
drop function insert_auth_method_subtype;
|
|
|
|
drop table auth_account cascade;
|
|
drop table auth_method cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/07_auth.up.sql": {
|
|
name: "07_auth.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌────────────────┐ ┌────────────────┐
|
|
│ iam_scope │ │ auth_method │
|
|
├────────────────┤ ├────────────────┤
|
|
│ public_id (pk) │ ╱│ public_id (pk) │
|
|
│ │┼┼───────────○─│ scope_id (fk) │
|
|
│ │ ╲│ │
|
|
└────────────────┘ └────────────────┘
|
|
┼ ┼
|
|
┼ ┼
|
|
│ │
|
|
│ │ ▲fk1
|
|
│ │
|
|
○ ○
|
|
╱│╲ ╱│╲
|
|
┌────────────────┐ ┌──────────────────────────┐
|
|
│ iam_user │ │ auth_account │
|
|
├────────────────┤ ├──────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk) │
|
|
│ scope_id (fk) │ ◀fk2 │ scope_id (fk1,fk2) │
|
|
│ │┼○──────○┼│ auth_method_id (fk1) │
|
|
│ │ │ iam_user_id (fk2) │
|
|
└────────────────┘ └──────────────────────────┘
|
|
|
|
An iam_scope can have 0 to many iam_users.
|
|
An iam_scope can have 0 to many auth_methods.
|
|
|
|
An iam_user belongs to 1 iam_scope.
|
|
An auth_method belongs to 1 iam_scope.
|
|
|
|
An iam_user can have 0 or 1 auth_account.
|
|
An auth_account belongs to 0 or 1 iam_user.
|
|
|
|
An auth_method can have 0 to many auth_accounts.
|
|
An auth_account belongs to 1 auth_account.
|
|
|
|
An auth_account can only be associated with an iam_user in the same scope of
|
|
the auth_account's auth_method. Including scope_id in fk1 and fk2 ensures this
|
|
restriction is not violated.
|
|
|
|
Design influenced by:
|
|
https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
|
|
*/
|
|
|
|
-- base table for auth methods
|
|
create table auth_method (
|
|
public_id wt_public_id primary key,
|
|
scope_id wt_scope_id not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
|
|
-- The order of columns is important for performance. See:
|
|
-- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
-- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
|
|
-- base table for auth accounts
|
|
create table auth_account (
|
|
public_id wt_public_id primary key,
|
|
auth_method_id wt_public_id not null,
|
|
scope_id wt_scope_id not null,
|
|
iam_user_id wt_public_id,
|
|
-- including scope_id in fk1 and fk2 ensures the scope_id of the owning
|
|
-- auth_method and the scope_id of the owning iam_user are the same
|
|
foreign key (scope_id, auth_method_id) -- fk1
|
|
references auth_method (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (scope_id, iam_user_id) -- fk2
|
|
references iam_user (scope_id, public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
unique(scope_id, auth_method_id, public_id)
|
|
);
|
|
|
|
create or replace function
|
|
insert_auth_method_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into auth_method
|
|
(public_id, scope_id)
|
|
values
|
|
(new.public_id, new.scope_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
insert_auth_account_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
|
|
select auth_method.scope_id
|
|
into new.scope_id
|
|
from auth_method
|
|
where auth_method.public_id = new.auth_method_id;
|
|
|
|
insert into auth_account
|
|
(public_id, auth_method_id, scope_id)
|
|
values
|
|
(new.public_id, new.auth_method_id, new.scope_id);
|
|
|
|
return new;
|
|
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/10_static_host.down.sql": {
|
|
name: "10_static_host.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table static_host_set_member cascade;
|
|
drop table static_host_set cascade;
|
|
drop table static_host cascade;
|
|
drop table static_host_catalog cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/10_static_host.up.sql": {
|
|
name: "10_static_host.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create table static_host_catalog (
|
|
public_id wt_public_id primary key,
|
|
scope_id wt_scope_id not null
|
|
references iam_scope (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
unique(scope_id, name)
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on static_host_catalog
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on static_host_catalog
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on static_host_catalog
|
|
for each row execute procedure immutable_columns('public_id', 'scope_id','create_time');
|
|
|
|
create table static_host (
|
|
public_id wt_public_id primary key,
|
|
static_host_catalog_id wt_public_id not null
|
|
references static_host_catalog (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
description text,
|
|
address text not null
|
|
check(
|
|
length(trim(address)) > 7
|
|
and
|
|
length(trim(address)) < 256
|
|
),
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
unique(static_host_catalog_id, name)
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on static_host
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on static_host
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on static_host
|
|
for each row execute procedure immutable_columns('public_id', 'static_host_catalog_id','create_time');
|
|
|
|
create table static_host_set (
|
|
public_id wt_public_id primary key,
|
|
static_host_catalog_id wt_public_id not null
|
|
references static_host_catalog (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
unique(static_host_catalog_id, name)
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on static_host_set
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on static_host_set
|
|
for each row execute procedure default_create_time();
|
|
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on static_host_set
|
|
for each row execute procedure immutable_columns('public_id', 'static_host_catalog_id','create_time');
|
|
|
|
create table static_host_set_member (
|
|
static_host_set_id wt_public_id
|
|
references static_host_set (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
static_host_id wt_public_id
|
|
references static_host (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
primary key(static_host_set_id, static_host_id)
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on static_host_set_member
|
|
for each row execute procedure immutable_columns('static_host_set_id', 'static_host_id');
|
|
|
|
insert into oplog_ticket (name, version)
|
|
values
|
|
('static_host_catalog', 1),
|
|
('static_host', 1),
|
|
('static_host_set', 1),
|
|
('static_host_set_member', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/11_auth_token.down.sql": {
|
|
name: "11_auth_token.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop view auth_token_account cascade;
|
|
drop table auth_token cascade;
|
|
|
|
drop function update_last_access_time cascade;
|
|
drop function immutable_auth_token_columns cascade;
|
|
drop function expire_time_not_older_than_token cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/11_auth_token.up.sql": {
|
|
name: "11_auth_token.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- an auth token belongs to 1 and only 1 auth account
|
|
-- an auth account can have 0 to many auth tokens
|
|
create table auth_token (
|
|
public_id wt_public_id primary key,
|
|
token bytea not null unique,
|
|
auth_account_id wt_public_id not null
|
|
references auth_account(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
-- This column is not updated every time this auth token is accessed.
|
|
-- It is updated after X minutes from the last time it was updated on
|
|
-- a per row basis.
|
|
approximate_last_access_time wt_timestamp
|
|
check(
|
|
approximate_last_access_time <= expiration_time
|
|
),
|
|
expiration_time wt_timestamp
|
|
check(
|
|
create_time <= expiration_time
|
|
)
|
|
);
|
|
|
|
create view auth_token_account as
|
|
select at.public_id,
|
|
at.token,
|
|
at.auth_account_id,
|
|
at.create_time,
|
|
at.update_time,
|
|
at.approximate_last_access_time,
|
|
at.expiration_time,
|
|
aa.scope_id,
|
|
aa.iam_user_id,
|
|
aa.auth_method_id
|
|
from auth_token as at
|
|
inner join auth_account as aa
|
|
on at.auth_account_id = aa.public_id;
|
|
|
|
create or replace function
|
|
update_last_access_time()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.approximate_last_access_time is distinct from old.approximate_last_access_time then
|
|
new.approximate_last_access_time = now();
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
update_last_access_time()
|
|
is
|
|
'function used in before update triggers to properly set last_access_time columns';
|
|
|
|
create or replace function
|
|
immutable_auth_token_columns()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.auth_account_id is distinct from old.auth_account_id then
|
|
raise exception 'auth_account_id is read-only';
|
|
end if;
|
|
if new.token is distinct from old.token then
|
|
raise exception 'token is read-only';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
immutable_auth_token_columns()
|
|
is
|
|
'function used in before update triggers to make specific columns immutable';
|
|
|
|
-- This allows the expiration to be calculated on the server side and still hold the constraint that
|
|
-- the expiration time cant be before the creation time of the auth token.
|
|
create or replace function
|
|
expire_time_not_older_than_token()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.expiration_time < new.create_time then
|
|
new.expiration_time = new.create_time;
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
expire_time_not_older_than_token()
|
|
is
|
|
'function used in before insert triggers to ensure expiration time is not older than create time';
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before insert on auth_token
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
expire_time_not_older_than_token
|
|
before insert on auth_token
|
|
for each row execute procedure expire_time_not_older_than_token();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on auth_token
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
update_last_access_time
|
|
before update on auth_token
|
|
for each row execute procedure update_last_access_time();
|
|
|
|
|
|
create trigger
|
|
immutable_auth_token_columns
|
|
before update on auth_token
|
|
for each row execute procedure immutable_auth_token_columns();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_token
|
|
for each row execute procedure immutable_columns('public_id', 'auth_account_id', 'create_time');
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/12_auth_password.down.sql": {
|
|
name: "12_auth_password.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table auth_password_credential;
|
|
drop table auth_password_conf cascade;
|
|
drop table if exists auth_password_account;
|
|
drop table if exists auth_password_method;
|
|
|
|
drop function insert_auth_password_credential_subtype;
|
|
drop function insert_auth_password_conf_subtype;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/12_auth_password.up.sql": {
|
|
name: "12_auth_password.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌────────────────┐ ┌──────────────────────┐ ┌────────────────────────────┐
|
|
│ auth_method │ │ auth_password_method │ │ auth_password_conf │
|
|
├────────────────┤ ├──────────────────────┤ ├────────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk,fk) │ ╱│ private_id (pk,fk) │
|
|
│ scope_id (fk) │┼┼─────────────○┼│ scope_id (fk) │┼┼─────────○─│ password_method_id (fk) │
|
|
│ │ │ ... │ ╲│ │
|
|
└────────────────┘ └──────────────────────┘ └────────────────────────────┘
|
|
┼ ┼ ┼
|
|
┼ ┼ ┼
|
|
│ │ │
|
|
│ ▲fk1 │ ▲fk1 │ ▲fk1
|
|
│ │ │
|
|
○ ○ ○
|
|
╱│╲ ╱│╲ ╱│╲
|
|
┌──────────────────────────┐ ┌──────────────────────────┐ ┌───────────────────────────────┐
|
|
│ auth_account │ │ auth_password_account │ │ auth_password_credential │
|
|
├──────────────────────────┤ ├──────────────────────────┤ ├───────────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk,fk2) │ │ private_id (pk) │
|
|
│ scope_id (fk1,fk2) │ ◀fk2 │ scope_id (fk1,fk2) │ ◀fk2 │ password_method_id (fk1,fk2) │
|
|
│ auth_method_id (fk1) │┼┼──────○┼│ auth_method_id (fk1,fk2) │┼┼──────○┼│ password_conf_id (fk1) │
|
|
│ iam_user_id (fk2) │ │ ... │ │ password_account_id (fk2) │
|
|
└──────────────────────────┘ └──────────────────────────┘ └───────────────────────────────┘
|
|
|
|
An auth_method is a base type. An auth_password_method is an auth_method
|
|
subtype. For every row in auth_password_method there is one row in auth_method
|
|
with the same public_id and scope_id.
|
|
|
|
Similarly, an auth_account is a base type. An auth_password_account is an
|
|
auth_account subtype. For every row in auth_password_account there is one row
|
|
in auth_account with the same public_id, scope_id, and auth_method_id.
|
|
|
|
Both auth_password_conf and auth_password_credential are base types. Each
|
|
password key derivation function will require a auth_password_conf and
|
|
auth_password_credential table.
|
|
|
|
An auth_method can have 0 or 1 auth_password_method.
|
|
An auth_account can have 0 or 1 auth_password_account.
|
|
|
|
An auth_password_method belongs to 1 auth_method.
|
|
An auth_password_method can have 0 to many auth_password_accounts.
|
|
An auth_password_method can have 0 to many auth_password_confs.
|
|
|
|
An auth_password_account belongs to 1 auth_account.
|
|
An auth_password_account belongs to 1 auth_password_method.
|
|
An auth_password_account can have 0 or 1 auth_password_credential.
|
|
|
|
An auth_password_conf belongs to 1 auth_password_method.
|
|
An auth_password_conf can have 0 to many auth_password_credentials.
|
|
|
|
An auth_password_credential belongs to 1 auth_password_account.
|
|
An auth_password_credential belongs to 1 auth_password_conf.
|
|
|
|
*/
|
|
|
|
create table auth_password_method (
|
|
public_id wt_public_id primary key,
|
|
scope_id wt_scope_id not null,
|
|
password_conf_id wt_private_id not null, -- FK to auth_password_conf added below
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
min_user_name_length int not null default 5,
|
|
min_password_length int not null default 8,
|
|
foreign key (scope_id, public_id)
|
|
references auth_method (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(scope_id, name),
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
create trigger
|
|
insert_auth_method_subtype
|
|
before insert on auth_password_method
|
|
for each row execute procedure insert_auth_method_subtype();
|
|
|
|
create table auth_password_account (
|
|
public_id wt_public_id primary key,
|
|
auth_method_id wt_public_id not null,
|
|
-- NOTE(mgaffney): The scope_id type is not wt_scope_id because the domain
|
|
-- check is executed before the insert trigger which retrieves the scope_id
|
|
-- causing an insert to fail.
|
|
scope_id text not null,
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
user_name text not null
|
|
check(
|
|
lower(trim(user_name)) = user_name
|
|
and
|
|
length(user_name) > 0
|
|
),
|
|
foreign key (scope_id, auth_method_id)
|
|
references auth_password_method (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (scope_id, auth_method_id, public_id)
|
|
references auth_account (scope_id, auth_method_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(auth_method_id, name),
|
|
unique(auth_method_id, user_name),
|
|
unique(auth_method_id, public_id)
|
|
);
|
|
|
|
create trigger
|
|
insert_auth_account_subtype
|
|
before insert on auth_password_account
|
|
for each row execute procedure insert_auth_account_subtype();
|
|
|
|
create table auth_password_conf (
|
|
private_id wt_private_id primary key,
|
|
password_method_id wt_public_id not null
|
|
references auth_password_method (public_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred,
|
|
unique(password_method_id, private_id)
|
|
);
|
|
|
|
alter table auth_password_method
|
|
add constraint current_conf_fkey
|
|
foreign key (public_id, password_conf_id)
|
|
references auth_password_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred;
|
|
|
|
-- insert_auth_password_conf_subtype() is a trigger function for subtypes of
|
|
-- auth_password_conf
|
|
create or replace function
|
|
insert_auth_password_conf_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into auth_password_conf
|
|
(private_id, password_method_id)
|
|
values
|
|
(new.private_id, new.password_method_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create table auth_password_credential (
|
|
private_id wt_private_id primary key,
|
|
password_account_id wt_public_id not null unique,
|
|
password_conf_id wt_private_id not null,
|
|
password_method_id wt_public_id not null,
|
|
foreign key (password_method_id, password_conf_id)
|
|
references auth_password_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (password_method_id, password_account_id)
|
|
references auth_password_account (auth_method_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(password_method_id, password_conf_id, password_account_id)
|
|
);
|
|
|
|
-- insert_auth_password_credential_subtype() is a trigger function for
|
|
-- subtypes of auth_password_credential
|
|
create or replace function
|
|
insert_auth_password_credential_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
|
|
select auth_password_account.auth_method_id
|
|
into new.password_method_id
|
|
from auth_password_account
|
|
where auth_password_account.public_id = new.password_account_id;
|
|
|
|
insert into auth_password_credential
|
|
(private_id, password_account_id, password_conf_id, password_method_id)
|
|
values
|
|
(new.private_id, new.password_account_id, new.password_conf_id, new.password_method_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- update_auth_password_credential_subtype() is an after update trigger
|
|
-- function for subtypes of auth_password_credential
|
|
create or replace function
|
|
update_auth_password_credential_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
/*
|
|
The configuration id of a credential is updated when a credential is
|
|
rehashed during authentication.
|
|
*/
|
|
if new.password_conf_id is distinct from old.password_conf_id then
|
|
update auth_password_credential
|
|
set password_conf_id = new.password_conf_id
|
|
where private_id = new.private_id;
|
|
end if;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
--
|
|
-- triggers for time columns
|
|
--
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on auth_password_method
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_method
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_method
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on auth_password_account
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_account
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_account
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- The tickets for oplog are the subtypes not the base types because no updates
|
|
-- are done to any values in the base types.
|
|
insert into oplog_ticket
|
|
(name, version)
|
|
values
|
|
('auth_password_method', 1),
|
|
('auth_password_account', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/13_auth_password_argon.down.sql": {
|
|
name: "13_auth_password_argon.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table auth_password_argon2_cred;
|
|
drop table auth_password_argon2_conf;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/13_auth_password_argon.up.sql": {
|
|
name: "13_auth_password_argon.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create table auth_password_argon2_conf (
|
|
private_id wt_private_id primary key
|
|
references auth_password_conf (private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
password_method_id wt_public_id not null,
|
|
create_time wt_timestamp,
|
|
iterations int not null default 3
|
|
check(iterations > 0),
|
|
memory int not null default 65536
|
|
check(memory > 0),
|
|
threads int not null default 1
|
|
check(threads > 0),
|
|
-- salt_length unit is bytes
|
|
salt_length int not null default 32
|
|
-- minimum of 16 bytes (128 bits)
|
|
check(salt_length >= 16),
|
|
-- key_length unit is bytes
|
|
key_length int not null default 32
|
|
-- minimum of 16 bytes (128 bits)
|
|
check(key_length >= 16),
|
|
unique(password_method_id, iterations, memory, threads, salt_length, key_length),
|
|
unique (password_method_id, private_id),
|
|
foreign key (password_method_id, private_id)
|
|
references auth_password_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred
|
|
);
|
|
|
|
create or replace function
|
|
read_only_auth_password_argon2_conf()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'auth_password_argon2_conf is read-only';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
read_only_auth_password_argon2_conf
|
|
before
|
|
update on auth_password_argon2_conf
|
|
for each row execute procedure read_only_auth_password_argon2_conf();
|
|
|
|
create trigger
|
|
insert_auth_password_conf_subtype
|
|
before insert on auth_password_argon2_conf
|
|
for each row execute procedure insert_auth_password_conf_subtype();
|
|
|
|
create table auth_password_argon2_cred (
|
|
private_id wt_private_id primary key
|
|
references auth_password_credential (private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
password_account_id wt_public_id not null,
|
|
password_conf_id wt_private_id not null,
|
|
-- NOTE(mgaffney): The password_method_id type is not wt_public_id because
|
|
-- the domain check is executed before the insert trigger which retrieves
|
|
-- the password_method_id causing an insert to fail.
|
|
password_method_id text not null,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
salt bytea not null -- cannot be changed unless derived_key is changed too
|
|
check(length(salt) > 0),
|
|
derived_key bytea not null
|
|
check(length(derived_key) > 0),
|
|
foreign key (password_method_id, password_conf_id)
|
|
references auth_password_argon2_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (password_method_id, password_conf_id, password_account_id)
|
|
references auth_password_credential (password_method_id, password_conf_id, password_account_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred
|
|
);
|
|
|
|
create trigger
|
|
insert_auth_password_credential_subtype
|
|
before insert on auth_password_argon2_cred
|
|
for each row execute procedure insert_auth_password_credential_subtype();
|
|
|
|
create trigger
|
|
update_auth_password_credential_subtype
|
|
after update on auth_password_argon2_cred
|
|
for each row execute procedure update_auth_password_credential_subtype();
|
|
|
|
--
|
|
-- triggers for time columns
|
|
--
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_argon2_conf
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_argon2_conf
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on auth_password_argon2_cred
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_argon2_cred
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_argon2_cred
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- The tickets for oplog are the subtypes not the base types because no updates
|
|
-- are done to any values in the base types.
|
|
insert into oplog_ticket
|
|
(name, version)
|
|
values
|
|
('auth_password_argon2_conf', 1),
|
|
('auth_password_argon2_cred', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/14_auth_password_views.down.sql": {
|
|
name: "14_auth_password_views.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop view auth_password_current_conf;
|
|
drop view auth_password_conf_union;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/14_auth_password_views.up.sql": {
|
|
name: "14_auth_password_views.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- auth_password_conf_union is a union of the configuration settings
|
|
-- of all supported key derivation functions.
|
|
-- It will be updated as new key derivation functions are supported.
|
|
create or replace view auth_password_conf_union as
|
|
-- Do not change the order of the columns when adding new configurations.
|
|
-- Union with new tables appending new columns as needed.
|
|
select c.password_method_id, c.private_id as password_conf_id, c.private_id,
|
|
'argon2' as conf_type,
|
|
c.iterations, c.memory, c.threads, c.salt_length, c.key_length
|
|
from auth_password_argon2_conf c;
|
|
|
|
-- auth_password_current_conf provides a view of the current password
|
|
-- configuration for each password auth method.
|
|
-- The view will be updated as new key derivation functions are supported
|
|
-- but the query to create the view should not need to be updated.
|
|
create or replace view auth_password_current_conf as
|
|
-- Rerun this query whenever auth_password_conf_union is updated.
|
|
select pm.min_user_name_length, pm.min_password_length, c.*
|
|
from auth_password_method pm
|
|
inner join auth_password_conf_union c
|
|
on pm.password_conf_id = c.password_conf_id;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
}
|