SET search_path to roleman, pg_catalog, pg_temp;

CREATE FUNCTION array_lower(in_array text[])
returns text[]
language sql immutable as
$$
select string_to_array(lower(array_to_string(in_array, ':~:')), ':~:');
$$;

comment on function array_lower(text[]) is
$$Takes in an array of text elements and returns
an array of text elements in lower case.  Intended for use
with whitelisting SQL keywords, so elements must not contain
the string ':~:'$$;

CREATE OR REPLACE FUNCTION mod_role(in_rolename text, in_attrs text[], mode text)
RETURNS bool LANGUAGE plpgsql as
$$
DECLARE with_clause text = '';
BEGIN
   IF not (array_lower(in_attrs) 
           <@ 
          array['superuser'::text, 'replication', 'login', 'inherit', 'nologin', 'noinherit',
                'nosuperuser', 'nocreatedb', 'noreplication', 'createdb', 'createrole',
                'nocreaterole'])
   THEN RAISE EXCEPTION 'Bad option for role %, options were %', in_rolename, 
          array_to_string(in_attrs, ', ');
   END IF;
   IF mode NOT IN ('create', 'alter') THEN
      raise exception 'invalid mode % in mod_role', mode;
   END IF;
   IF array_upper(in_attrs, 1) > 0 THEN
       with_clause = ' WITH ' || array_to_string(in_attrs, ' ');
   END IF;
   EXECUTE upper(mode) || ' ROLE ' || quote_ident(in_rolename) || with_clause;
   return true;
END;
$$ SET SEARCH_PATH FROM CURRENT;

CREATE OR REPLACE FUNCTION create_role(IN in_rolename text, IN in_attrs text[] default '{}')
RETURNS bool LANGUAGE sql AS
$$
select roleman.mod_role(in_rolename, in_attrs, 'create');
$$ set search_path from current;

CREATE OR REPLACE FUNCTION alter_base(in_rolename text, in_attrs text[])
returns bool language sql as
$$
select roleman.mod_role(in_rolename, in_attrs, 'alter');
$$ set search_path from current;

comment on function create_role(text, text[]) is
$$Creates a role with name of rolename.  Second argument is used to provide
options for WITH clause.$$;

CREATE OR REPLACE FUNCTION role_set_password
(in_rolename text, in_password text, in_valid_until timestamp default 'infinity')
RETURNS void language plpgsql as
$$
BEGIN
   EXECUTE 'alter role ' || quote_ident(in_rolename) 
            || ' with password ' || quote_literal(in_password)
            || ' valid until ' || quote_literal(in_valid_until);
END;
$$ SET SEARCH_PATH FROM CURRENT;;

CREATE OR REPLACE FUNCTION role_blank_perms(in_rolename text)
RETURNS BOOL 
language plpgsql as $$
DECLARE permrec record;
        acltype text;
BEGIN
   PERFORM FROM pg_roles where rolname = in_rolename;
   IF NOT FOUND THEN RETURN FALSE; END IF;
   -- ok now we look for all db objects with explicit permissions and revoke all from them.
   -- These may be overinclusive but better that than under-inclusive.

   -- revoke all from current database
   EXECUTE 'REVOKE ALL ON DATABASE ' || quote_ident(current_database()) || ' FROM ' || quote_ident(in_rolename);

   FOR permrec IN SELECT oid, * FROM pg_namespace
   LOOP
      EXECUTE 'REVOKE ALL ON ALL TABLES IN SCHEMA ' || quote_ident(permrec.nspname) || ' FROM ' || quote_ident(in_rolename);
      EXECUTE 'REVOKE ALL ON ALL SEQUENCES IN SCHEMA ' || quote_ident(permrec.nspname) || ' FROM ' || quote_ident(in_rolename);
      EXECUTE 'REVOKE ALL ON ALL FUNCTIONS IN SCHEMA ' || quote_ident(permrec.nspname) || ' FROM ' || quote_ident(in_rolename);
      EXECUTE 'REVOKE ALL ON SCHEMA ' || quote_ident(permrec.nspname) || ' FROM ' || quote_ident(in_rolename);
   END LOOP;

   -- finally roles we have been granted
   FOR permrec IN 
       SELECT mr.*
         FROM pg_auth_members m
         JOIN pg_roles mr ON m.roleid = mr.oid
         join pg_roles pr ON m.member = pr.oid
              and pr.rolname = in_rolename
   LOOP
       EXECUTE 'REVOKE ' || quote_ident(permrec.rolename) || ' FROM ' || quote_ident(in_rolename);
   END LOOP;
   RETURN TRUE; 
END;
$$ SET SEARCH_PATH FROM CURRENT;;

create or replace function grant_database(in_rolename text, in_dbname text, in_perms text[])
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   IF NOT (array_lower(in_perms) <@ ARRAY['connect', 'create', 'temp', 'temporary', 'all']) then
      RAISE EXCEPTION 'bad database permissions for %, dbname %, perms %',
            in_rolename, in_dbname, array_to_string(in_perms, ', ');
   END IF;
   EXECUTE 'GRANT ' || array_to_string(in_perms, ', ') || ' on database ' 
          || quote_ident(in_dbname) || ' TO ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;;

create or replace function grant_schema(in_rolename text, in_schema text, in_perms text[])
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   IF NOT (array_lower(in_perms) <@ ARRAY['usage', 'create', 'all']) then
      RAISE EXCEPTION 'bad permissions for %,  schema %, perms %',
            in_rolename, in_schema, array_to_string(in_perms, ', ');
   END IF;
   EXECUTE 'GRANT ' || array_to_string(in_perms, ', ') || ' on schema ' 
          || quote_ident(in_schema) || ' TO ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;

create or replace function grant_schema_all(in_rolename text, in_schema text, in_type text, in_perms text[])
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   IF NOT (array_lower(in_perms) <@ ARRAY['usage', 'create', 'all', 'select', 
                                         'update', 'insert', 'delete', 'execute']) then
      RAISE EXCEPTION 'bad database permissions for %,  schema %, type %, perms %',
            in_rolename, in_schema, in_type, array_to_string(in_perms, ', ');
   END IF;
   IF NOT lower(in_type) = any(ARRAY['functions', 'tables', 'sequences']) THEN
      RAISE EXCEPTION 'bad tyoe %', in_type;
   END IF;
   EXECUTE 'GRANT ' || array_to_string(in_perms, ', ') || ' on all '|| in_type || ' in schema ' 
          || quote_ident(in_schema) || ' TO ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;

create or replace function grant_table(in_rolename text, in_table regclass, in_perms text[])
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   IF NOT (array_lower(in_perms) <@ ARRAY['select', 'insert', 'update', 'delete', 'all']) then
      RAISE EXCEPTION 'bad database permissions for %,  table %, perms %',
            in_rolename, in_table, array_to_string(in_perms, ', ');
   END IF;
   EXECUTE 'GRANT ' || array_to_string(in_perms, ', ') || ' on table ' 
          || in_table || ' TO ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;

create or replace function grant_seq(in_rolename text, in_seq regclass, in_perms text[])
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   IF NOT (array_lower(in_perms) <@ ARRAY['usage', 'all']) then
      RAISE EXCEPTION 'bad database permissions for %,  sequence %, perms %',
            in_rolename, in_seq, array_to_string(in_perms, ', ');
   END IF;
   EXECUTE 'GRANT ' || array_to_string(in_perms, ', ') || ' on sequence ' 
          || in_seq || ' TO ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;

create or replace function grant_function(in_rolename text, in_proc regprocedure, in_perms text[])
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   IF NOT (array_lower(in_perms) <@ ARRAY['execute', 'all']) then
      RAISE EXCEPTION 'bad database permissions for %,  function %, perms %',
            in_rolename, in_proc, array_to_string(in_perms, ', ');
   END IF;
   EXECUTE 'GRANT ' || array_to_string(in_perms, ', ') || ' on function  ' 
          || in_proc || ' TO ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;

create or replace function role_grant_role(in_rolename text, in_new_parent text)
returns void language plpgsql as
$$
begin
   EXECUTE 'grant ' || quote_ident(in_new_parent) || ' to ' || quote_ident(in_rolename);
END;
$$ SET SEARCH_PATH FROM CURRENT;

CREATE OR REPLACE FUNCTION drop_role(in_rolename text)
RETURNS VOID LANGUAGE PLPGSQL AS
$$
BEGIN
   EXECUTE 'DROP ROLE ' || quote_ident(in_rolename);
END;
$$;
