/** * @file veil2_old_privs.c * \code * Author: Marc Munro * Copyright (c) 2020 Marc Munro * License: GPL V3 * * \endcode * @brief * Old Veil privilege testing functions. These use SPI calls to * directly interogate the veil2_session_privileges table. These have * been replaced by much faster tests against memory-resident sdata * structures. This file exists to preserve the old versions for * posterity for future developers who may need to do something similar. * */ /** * veil2.i_have_global_priv(priv) returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, with global scope. * * @param int privilege_id of privilege to test for * * @return boolean true if the session has the given privilege */ Datum veil2_i_have_global_priv(PG_FUNCTION_ARGS) { static void *saved_plan = NULL; bool result; bool found; bool pushed; Oid argtypes[] = {INT4OID}; Datum args[] = {Int32GetDatum(priv)}; int priv = PG_GETARG_INT32(0); if (session_ready) { veil2_spi_connect(&pushed, "SPI connect failed in veil2_i_have_global_priv()"); found = veil2_bool_from_query( "select coalesce(" " (select privs ? $1" " from veil2_session_privileges v" " where v.scope_type_id = 1), false)", 1, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in veil2_i_have_global_priv()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } /** * veil2.i_have_personal_priv(priv) returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in their personal scope (ie for data * pertaining to themselves). * * @param int privilege_id of privilege to test for * @param int accessor_id of the record being checked. * * @return boolean true if the session has the given privilege in the * personal scope of the given accessor_id */ Datum veil2_i_have_personal_priv(PG_FUNCTION_ARGS) { static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int accessor_id = PG_GETARG_INT32(1); Oid argtypes[] = {INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(accessor_id)}; if (session_ready) { veil2_spi_connect(&pushed, "SPI connect failed in veil2_i_have_personal_priv()"); found = veil2_bool_from_query( "select coalesce(" " (select privs ? $1" " from veil2_session_privileges v" " where v.scope_type_id = 2" " and v.scope_id = $2), false)", 2, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in veil2_i_have_personal_priv()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } /** * veil2.i_have_priv_in_scope(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in a specific scope * (scope_type_id, scope_id). * * @param int privilege_id of privilege to test for * @param int scope_type_id of the record being checked. * @param int scope_id for the record being checked. * * @return boolean true if the session has the given privilege for the * given scope_type_id and scope_id */ Datum veil2_i_have_priv_in_scope(PG_FUNCTION_ARGS) { static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; veil2_spi_connect(&pushed, "SPI connect failed in veil2_i_have_priv_in_scope()"); if (session_ready) { found = veil2_bool_from_query( "select coalesce(" " (select privs ? $1" " from veil2_session_privileges v" " where v.scope_type_id = $2" " and v.scope_id = $3), false)", 3, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_scope()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } /** * veil2.i_have_priv_in_scope_or_global(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in a specific scope * (scope_type_id, scope_id), or in global scope. * * @param int privilege_id of privilege to test for * @param int scope_type_id of the record being checked. * @param int scope_id for the record being checked. * * @return boolean true if the session has the given privilege for the * given scope_type_id and scope_id */ Datum veil2_i_have_priv_in_scope_or_global(PG_FUNCTION_ARGS) { static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; veil2_spi_connect(&pushed, "SPI connect failed in veil2_i_have_priv_in_scope_or_global()"); if (session_ready) { found = veil2_bool_from_query( "select coalesce(" " (select union_of(privs) ? $1" " from veil2_session_privileges v" " where ( v.scope_type_id = $2" " and v.scope_id = $3)" " or ( v.scope_type_id = 1" " and v.scope_id = 0)), false)", 3, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_scope_or_global()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } /** * veil2.i_have_priv_in_superior_scope(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in a superior scope to that supplied: * scope_type_id, scope_id. * * @param int privilege_id of privilege to test for * @param int scope_type_id of the record being checked. * @param int scope_id for the record being checked. * * @return boolean true if the session has the given privilege in a * scope superior to that given by scope_type_id and scope_id */ Datum veil2_i_have_priv_in_superior_scope(PG_FUNCTION_ARGS) { static void *saved_plan = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; veil2_spi_connect(&pushed, "SPI connect failed in " "veil2_i_have_priv_in_superior_scope()"); if (session_ready) { found = veil2_bool_from_query( "select true" " from veil2.all_superior_scopes asp" " inner join veil2_session_privileges sp" " on sp.scope_type_id = asp.superior_scope_type_id" " and sp.scope_id = asp.superior_scope_id" " where asp.scope_type_id = $2" " and asp.scope_id = $3" " and sp.privs ? $1", 3, argtypes, args, &saved_plan, &result); veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_superior_scope()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } /** * veil2.i_have_priv_in_scope_or_superior(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in the supplied scope or a superior one: * scope_type_id, scope_id. * * @param int privilege_id of privilege to test for * @param int scope_type_id of the record being checked. * @param int scope_id for the record being checked. * * @return boolean true if the session has the given privilege in the * scope given by scope_type_id and scope_id or a supeior one. */ Datum veil2_i_have_priv_in_scope_or_superior(PG_FUNCTION_ARGS) { static void *saved_plan1 = NULL; static void *saved_plan2 = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; veil2_spi_connect(&pushed, "SPI connect failed in " "veil2_i_have_priv_in_scope_or_superior()"); if (session_ready) { found = veil2_bool_from_query( "select coalesce(" " (select privs ? $1" " from veil2_session_privileges v" " where v.scope_type_id = $2" " and v.scope_id = $3), false)", 3, argtypes, args, &saved_plan1, &result); if (!(found && result)) { found = veil2_bool_from_query( "select true" " from veil2.all_superior_scopes asp" " inner join veil2_session_privileges sp" " on sp.scope_type_id = asp.superior_scope_type_id" " and sp.scope_id = asp.superior_scope_id" " where asp.scope_type_id = $2" " and asp.scope_id = $3" " and sp.privs ? $1", 3, argtypes, args, &saved_plan2, &result); } veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_scope_or_superior()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } /** * veil2.i_have_priv_in_scope_or_superior_or_global(priv, scope_type_id, scope_id) * returns bool * * Predicate to determine whether the current session user has a given * privilege, priv, in global_scope, or the supplied * scope, or a superior one: * scope_type_id, scope_id. * * @param int privilege_id of privilege to test for * @param int scope_type_id of the record being checked. * @param int scope_id for the record being checked. * * @return boolean true if the session has the given privilege in the * scope given by scope_type_id and scope_id or a supeior one or * global scope. */ Datum veil2_i_have_priv_in_scope_or_superior_or_global(PG_FUNCTION_ARGS) { static void *saved_plan1 = NULL; static void *saved_plan2 = NULL; bool result; bool found; bool pushed; int priv = PG_GETARG_INT32(0); int scope_type_id = PG_GETARG_INT32(1); int scope_id = PG_GETARG_INT32(2); Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(priv), Int32GetDatum(scope_type_id), Int32GetDatum(scope_id)}; veil2_spi_connect(&pushed, "SPI connect failed in " "veil2_i_have_priv_in_scope_or_superior()"); if (session_ready) { found = veil2_bool_from_query( "select coalesce(" " (select union_of(privs) ? $1" " from veil2_session_privileges v" " where ( v.scope_type_id = $2" " and v.scope_id = $3)" " or ( v.scope_type_id = 1" " and v.scope_id = 0)), false)", 3, argtypes, args, &saved_plan1, &result); if (!(found && result)) { found = veil2_bool_from_query( "select true" " from veil2.all_superior_scopes asp" " inner join veil2_session_privileges sp" " on sp.scope_type_id = asp.superior_scope_type_id" " and sp.scope_id = asp.superior_scope_id" " where asp.scope_type_id = $2" " and asp.scope_id = $3" " and sp.privs ? $1", 3, argtypes, args, &saved_plan2, &result); } veil2_spi_finish(pushed, "SPI finish failed in " "veil2_i_have_priv_in_scope_or_superior()"); result_counts[found && result]++; return found && result; } if (error_if_no_session()) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Attempt to check privileges before call to " "veil2_reset_session."))); } else { result_counts[false]++; } return false; } #ifdef TRIED_WITH_NO_PERFORMANCE_GAIN PG_FUNCTION_INFO_V1(veil2_create_accessor_session); typedef struct { int accessor_id; int authent_accessor_id; int session_id; int login_context_type_id; int login_context_id; int session_context_type_id; int session_context_id; int mapping_context_type_id; int mapping_context_id; } SessionContext; static bool fetch_session_context(HeapTuple tuple, TupleDesc tupdesc, void *args) { SessionContext *session_context = (SessionContext *) args; bool is_null; int mapping_context_type; int mapping_context; session_context->session_id = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 1, &is_null)); mapping_context_type = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 2, &is_null)); if (mapping_context_type == 1) { /* Mapping is in global_context */ session_context->mapping_context_type_id = 1; session_context->mapping_context_id = 0; } else { mapping_context = DatumGetInt32(SPI_getbinval(tuple, tupdesc, 3, &is_null)); if (is_null) { session_context->mapping_context_type_id = session_context->session_context_type_id; session_context->mapping_context_id = session_context->session_context_id; } else { session_context->mapping_context_type_id = mapping_context_type; session_context->mapping_context_id = mapping_context; } } return false; // Only want one row: this stopes further processing. } // TODO: // later add a static to indicate whether session is currently reset // and do nothing in reset_session if so static void get_session_context(SessionContext *session_context) { static void *saved_plan = NULL; int processed; Oid argtypes[] = {INT4OID, INT4OID}; Datum args[] = {Int32GetDatum(session_context->session_context_type_id), Int32GetDatum(session_context->session_context_type_id)}; processed = veil2_query( "select nextval('veil2.session_id_seq')::integer," " sp.parameter_value::integer," " asp.superior_scope_id" " from veil2.system_parameters sp" " left outer join veil2.all_superior_scopes asp" " on asp.scope_type_id = $1" " and asp.scope_id = $2" " and asp.superior_scope_type_id = sp.parameter_value::integer" " and asp.is_type_promotion" " where sp.parameter_name = 'mapping context target scope type'", 2, argtypes, args, true, &saved_plan, fetch_session_context, (void *) session_context); if (!(processed == 1)) { ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("Failed to fetch session context."))); } } static void save_session_context(SessionContext *session_context) { static void *saved_plan = NULL; Oid argtypes[] = {INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID}; Datum args[] = { Int32GetDatum(session_context->accessor_id), Int32GetDatum(session_context->authent_accessor_id), Int32GetDatum(session_context->session_id), Int32GetDatum(session_context->login_context_type_id), Int32GetDatum(session_context->login_context_id), Int32GetDatum(session_context->session_context_type_id), Int32GetDatum(session_context->session_context_id), Int32GetDatum(session_context->mapping_context_type_id), Int32GetDatum(session_context->mapping_context_id)}; (void) veil2_query( "insert" " into veil2_session_context" " (accessor_id, authent_accessor_id, session_id," " login_context_type_id, login_context_id," " session_context_type_id, session_context_id," " mapping_context_type_id, mapping_context_id)" " values ($1, $2, $3," " $4, $5," " $6, $7," " $8, $9)", 9, argtypes, args, false, &saved_plan, NULL, NULL); } typedef struct { int accessor_id; char *authent_type; char *supplemental_fn; char *session_token; char *session_supplemental; } AuthentDetails; static bool fetch_authent_details(HeapTuple tuple, TupleDesc tupdesc, void *args) { AuthentDetails *authent_details = (AuthentDetails *) args; Datum val; bool is_null; if ((val = SPI_getbinval(tuple, tupdesc, 1, &is_null))) { authent_details->supplemental_fn = TextDatumGetCString(val); } else { authent_details->supplemental_fn = NULL; authent_details->session_supplemental = NULL; } authent_details->session_token = TextDatumGetCString(SPI_getbinval(tuple, tupdesc, 2, &is_null)); return false; // Only want one row: this stopes further processing. } static bool fetch_supplemental_tokens(HeapTuple tuple, TupleDesc tupdesc, void *args) { AuthentDetails *authent_details = (AuthentDetails *) args; Datum val; bool is_null; if ((val = SPI_getbinval(tuple, tupdesc, 1, &is_null))) { /* If session_token is null, we leave the original value in * place, otherwise we overwrite it below. */ authent_details->session_token = TextDatumGetCString(val); } if ((val = SPI_getbinval(tuple, tupdesc, 2, &is_null))) { authent_details->session_supplemental = TextDatumGetCString(val); } else { authent_details->session_supplemental = NULL; } return false; // Only want one row: this stopes further processing. } #define EXEC_SUPPLEMENTAL_FN_START "select * from %s($1, $2)" static void get_authent_details(AuthentDetails *authent_details) { static void *saved_plan = NULL; static void *saved_plan2 = NULL; int processed; Oid argtypes[] = {TEXTOID}; Datum args[] = {CStringGetTextDatum(authent_details->authent_type)}; char *qrystr; /* Query designed to always return a sesion token regardless of * whether the authentication type matches a record. */ processed = veil2_query( "select a2.supplemental_fn," " encode(digest(random()::text || now()::text, 'sha256')," " 'base64') as session_token" " from (select $1 as auth_type) a1" " left outer join veil2.authentication_types a2" " on a2.shortname = a1.auth_type", 1, argtypes, args, true, &saved_plan, fetch_authent_details, (void *) authent_details); if (!(processed == 1)) { ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("Failed to fetch authentication details."))); } if (authent_details->supplemental_fn) { Oid argtypes[] = {INT4OID, TEXTOID}; Datum args[] = { Int32GetDatum(authent_details->accessor_id), CStringGetTextDatum(authent_details->session_token)}; qrystr = (char *) palloc(sizeof(char *) * (strlen(EXEC_SUPPLEMENTAL_FN_START) + strlen(authent_details->supplemental_fn))); (void) sprintf(qrystr, EXEC_SUPPLEMENTAL_FN_START, authent_details->supplemental_fn); processed = veil2_query( qrystr, 1, argtypes, args, true, &saved_plan2, fetch_supplemental_tokens, (void *) authent_details); } } static bool valid_accessor_context(SessionContext *session_context) { static void *saved_plan = NULL; bool found; bool is_valid; Oid argtypes[] = {INT4OID, INT4OID, INT4OID}; Datum args[] = { Int32GetDatum(session_context->accessor_id), Int32GetDatum(session_context->login_context_type_id), Int32GetDatum(session_context->login_context_id)}; found = veil2_bool_from_query( "select true" " from veil2.accessors a" " inner join veil2.accessor_contexts ac" " on ac.accessor_id = a.accessor_id" " where a.accessor_id = $1" " and ac.context_type_id = $2" " and ac.context_id = $3", 3, argtypes, args, &saved_plan, &is_valid); return found; } static void record_session(SessionContext *session_context, AuthentDetails *authent_details) { static void *saved_plan = NULL; char *nulls; Oid argtypes[] = {INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, TEXTOID, TEXTOID, TEXTOID}; Datum args[] = { Int32GetDatum(session_context->accessor_id), Int32GetDatum(session_context->authent_accessor_id), Int32GetDatum(session_context->session_id), Int32GetDatum(session_context->login_context_type_id), Int32GetDatum(session_context->login_context_id), Int32GetDatum(session_context->session_context_type_id), Int32GetDatum(session_context->session_context_id), Int32GetDatum(session_context->mapping_context_type_id), Int32GetDatum(session_context->mapping_context_id), CStringGetTextDatum(authent_details->authent_type), (Datum) NULL, CStringGetTextDatum(authent_details->session_token)}; if (authent_details->session_supplemental) { args[10] = CStringGetTextDatum(authent_details->session_supplemental); nulls = NULL; /* No nulls. */ } else { /* session_supplemental is null */ nulls = " n "; } (void) veil2_query_wn( "insert" " into veil2.sessions" " (accessor_id, authent_accessor_id, session_id," " login_context_type_id, login_context_id," " session_context_type_id, session_context_id," " mapping_context_type_id, mapping_context_id," " authent_type, has_authenticated," " session_supplemental, expires," " token) " "select $1, $2, $3," " $4, $5," " $6, $7," " $8, $9," " $10, false," " $11, now() + sp.parameter_value::interval," " $12" " from veil2.system_parameters sp" " where sp.parameter_name = 'shared session timeout'", 12, argtypes, args, nulls, false, &saved_plan, NULL, NULL); } /** * veil2.create_accessor_session(params) returns record * * TODO: explain this * * @param accessor_id in integer * @param authent_type in text * @param context_type_id in integer * @param context_id in integer * @param session_context_type_id in integer * @param session_context_id in integer * @param authent_accessor_id in integer default null * @param session_id out integer * @param session_token out text * @param session_supplemental out text * * @return record */ Datum veil2_create_accessor_session(PG_FUNCTION_ARGS) { SessionContext session_context; AuthentDetails authent_details; TupleDesc tuple_desc; HeapTuple tuple; Datum results[3]; bool nulls[3] = {false, false, false}; bool pushed; session_context.accessor_id = PG_GETARG_INT32(0); session_context.login_context_type_id = PG_GETARG_INT32(2); session_context.login_context_id = PG_GETARG_INT32(3); session_context.session_context_type_id = PG_GETARG_INT32(4); session_context.session_context_id = PG_GETARG_INT32(5); if (PG_ARGISNULL(6)) { session_context.authent_accessor_id = session_context.accessor_id; } else { session_context.authent_accessor_id = PG_GETARG_INT32(6); } authent_details.accessor_id = session_context.accessor_id; authent_details.authent_type = TextDatumGetCString(PG_GETARG_TEXT_P(1)); veil2_spi_connect(&pushed, "failed to get_session_context (1)"); do_reset_session(); get_session_context(&session_context); save_session_context(&session_context); get_authent_details(&authent_details); if (valid_accessor_context(&session_context)) { record_session(&session_context, &authent_details); } veil2_spi_finish(pushed, "failed to get_session_context (2)"); /* Create results record. */ if (get_call_result_type(fcinfo, NULL, &tuple_desc) != TYPEFUNC_COMPOSITE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } /* Return the results set. */ results[0] = Int32GetDatum(session_context.session_id); results[1] = CStringGetTextDatum(authent_details.session_token); if (authent_details.session_supplemental) { results[2] = CStringGetTextDatum(authent_details.session_supplemental); } else { nulls[2] = true; results[2] = (Datum) NULL; } tuple_desc = BlessTupleDesc(tuple_desc); tuple = heap_form_tuple(tuple_desc, results, nulls); return HeapTupleGetDatum(tuple); } #endif