13 июл. 2012 г.

Protect PL/SQL API by Data Vault

Опция Data Vault все время преподносится как средство для защиты БД от администратора. Однако, на самом деле, с ее помощью можно реализовывать дополнительные проверки, сделать которые стандартным функционалом невозможно.

Рассмотрим для примера такую ситуацию: PL/SQL-код приложения состоит из нескольких слоев: слой доступа к данным, слой бизнес-логики и слой обеспечивающий презентационный уровень. Одним словом, есть набор PL/SQL-пакетов и типов реализующих доступ к таблицам, есть PL/SQL-объекты которые содержат в себе бизнес-логику и есть код реализующий визуальный интерфейс приложения (например: пользовательский код в формах APEX).

При этом код, отвечающий за интерфейс пользователя, имеет право вызывать только процедуры бизнес-логики, и не может напрямую обращаться к слою доступа к данным, иначе это может привести нарушению логической целостности данных.

В настоящий момент, стандартными средствами СУБД, невозможно реализовать декларативную проверку на возможность вызова PL/SQL-процедур только в определенном месте.

Для решения этой задачи нам на помощь приходит опция Data Vault. Мы можем создать правило на команду EXECUTE и далее в функции проверки этого правила, проанализировав стек вызовов PL/SQL, разрешить или запретить вызов соответствующей процедуры.

Давайте посмотрим, как все это работает.
В моем приложении работу с репозитарием объектов инкапсулирует объектный тип TObjectRepository. К этому объектному типу непосредственно могут обращаться только следующих три объектных типа:
  • TPersistent - абстрактный тип, его наследники обладают свойством сохранять и читать свое состояние из БД;
  • TDictionary - инкапсулирует работу с справочниками, в связи с особой ролью системных справочников, может напрямую обращаться к API поддержки репозитория;
  • TGroup - инкапсулирует функционал группировки объектов в группы, тоже напрямую может работать с фабрикой объектов.

Для начала создадим функцию осуществляющую проверку возможности вызова:

create or replace function pcl4.check_valid_call return pls_integer is
begin
  if pcl_service.getWhoCallMe in ('PCL4.TPERSISTENT','PCL4.TDICTIONARY','PCL4.TGROUP') then
    return 1;
  end if;

  return 0;
end;
/
show errors;

grant execute on check_valid_call to dvsys;
где функция getWhoCallMe анализируя стек вызовов (с помощью системной функции DBMS_UTILITY.FORMAT_CALL_STACK), возвращает имя PL/SQL-объекта из которого была вызвана текущая проверяемая процедура.
Также не забываем дать права на выполнение функции проверки пользователю DVSYS, поскольку выполнение проверки будет происходит из под него.
Конечно, было бы более правильнее, если бы условие проверки в функции check_valid_call не было бы жестко зашито в код, а читалось из отдельной таблицы. Но сути примера это не меняет.

Далее, подключившись под пользователем владельцем Data Vault (его имя мы указали при установке опции DataVault) определяем правило:
SQL> connect dvpcl/*****
Connected.
SQL>
SQL> declare
  2    v_xRule_set_name  varchar2(90 char)  := 'Проверка вызова PL/SQL-объекта в разрешённом месте';
  3    v_xRule_name      varchar2(90 char)  := 'CHECK_API_LAYER';
  4  begin
  5    --Создаем набор правил
  6    dvsys.dbms_macadm.create_rule_set(rule_set_name   => v_xRule_set_name
  7                                     ,description     => null
  8                                     ,enabled         => dvsys.dbms_macutl.g_yes
  9                                     ,eval_options    => dvsys.dbms_macutl.g_ruleset_eval_all
 10                                     ,audit_options   => dvsys.dbms_macutl.g_ruleset_audit_off
 11                                     ,fail_options    => dvsys.dbms_macutl.g_ruleset_fail_show
 12                                     ,fail_message    => 'Вызов в данном месте НЕ разрешен'
 13                                     ,fail_code       => -20002
 14                                     ,handler_options => dvsys.dbms_macutl.g_ruleset_handler_off
 15                                     ,handler         => null);
 16    -- Создаем правило - указываем код проверки:
 17
 18    dvsys.dbms_macadm.create_rule(rule_name => v_xRule_name
 19                                 ,rule_expr => 'PCL4.CHECK_VALID_CALL = 1');
 20
 21    -- Добавляем вновь созданное правило к нашему набору
 22    dvsys.dbms_macadm.add_rule_to_rule_set(rule_set_name => v_xRule_set_name
 23                                          ,rule_name     => v_xRule_name);
 24
 25    -- Указываем критерии проверки: выполнение (EXECUTE) объектного типа (TOBJECTREPOSITORY) содержащегося в определенной схеме (PCL4)
 26    dvsys.dbms_macadm.create_command_rule(command         => 'EXECUTE'
 27                                         ,rule_set_name   => v_xRule_set_name
 28                                         ,object_owner    => 'PCL4'
 29                                         ,object_name     => 'TOBJECTREPOSITORY'
 30                                         ,enabled         => dvsys.dbms_macutl.g_yes);
 31    commit;
 32  end;
 33  /

PL/SQL procedure successfully completed.

SQL>

Обратите внимание, что проверка будет производится только к вызовам объектного типа TObjectRepository, место вызова остальных PL/SQL-объектов никак не проверяется!
Таким образом, мы можем явно определить PL/SQL-объекты, вызов которых возможен только в определенном месте.

Теперь попробуем обратиться к объекту из произвольной процедуры:
SQL> conn pcl4/*****
Connected.
SQL> create or replace procedure test_dv1 is
  2    v_xObject TPersistent;
  3  begin
  4    v_xObject := TObjectRepository.getObjectById(12345) ;
  5  end;
  6  /

Procedure created.

SQL> show errors;
No errors.
SQL>
SQL> exec test_dv1;
BEGIN test_dv1; END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "PCL4.TOBJECTREPOSITORY", line 248
ORA-06512: at "PCL4.TEST_DV1", line 4
ORA-06512: at line 1

SQL>
Здесь важно отметить, что проверка производится именно в момент вызова, а не компиляции вызывающего PL/SQL-блока.

В общем случае, проверки не ограничиваются только командой выполнения (EXECUTE), вы можете реализовать хитроумные проверки практически для любой команды СУБД.