On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

Детальный контроль доступа и контексты приложения

Т.Кайт

В этой статье рассмотрены два новых механизма Oracle8i: Детальный Контроль Доступа (Fine Grained Access Control) и Контексты Защищенных Приложений (Secure Application Contexts). При совместном использовании они обеспечивают новые качественные возможности для обеспечения информационной безопасности базы данных.

Как и в других статях этого цикла, в этом опусе будут рассмотрены:

В различных изданиях Детальный Контроль Доступа может быть назван по-разному. Ниже перечислены его синонимы:

  • Детальный Контроль Доступа (Fine Grained Access Control - техническое название)
  • Виртуальная Частная База Данных (Virtual Private Database - рыночное название)
  • Безопасность на Уровне Строк (Row Level Security - техническое название, идущее от того, что эту возможность реализуют PL/SQL-пакеты)

В двух словах, Детальный Контроль Доступа в Oracle8i - это возможность во время работы динамически присоединить предикат (предложения where) как к одному, так и ко всем запросам к таблице или представлению. Таким образом, появилась возможность процедурной модификации запроса в процессе выполнения. Можно вычислить, кто выполняет запрос, откуда запрос выполняется, когда началось выполнение запроса, и сформировать предикат, соответствующий этим критериям. При использовании Контекстов Приложения можно незаметно через окружение (например, через роль, назначенную пользователю приложения) добавить дополнительную информацию, и получить доступ к ней через процедуру или предикат.

Примером Детального Контроля Доступа может служить политика безопасности, которая определяет, какие строки могут быть доступны различным группам пользователей. Политика безопасности формирует предикат, вид которого зависит от соединенного с базой пользователя и группы, к которой он относится. Детальный Контроль Доступа позволяет при вводе запроса "select * from emp" различными пользователями преобразовать его к следующему виду:

Пользователь

Запрос динамически
переписывается в

Замечания

Служащий

select * 
from ( select * 
 from emp 
where ename = USER )

Служащие могут видеть только свои записи

Менеджер

select * 
  from ( select * 
   from emp 
  where mgr = (select 
  empno from emp 
 where ename = USER)
  or ename = USER)

Менеджеры могут видеть свои записи и записи тех, кто работает под их руководством.

Контролер

select * 
  from (select * 
  from emp 
 where deptno = 
SYS_CONTEXT
   ('OurApp','Deptno'))

Контролер может видеть любого в заданном отделе. Этот пример знакомит с синтаксисом получения переменных контекста приложения -встроенной функцией SYS_CONTEXT().

Почему используется эта возможность

Есть много причин, чтобы использовать этот механизм. Наиболее распространенные из них:

  • Легко поддерживается. Детальный Контроль Доступа позволяет иметь только 1 таблицу и 1 хранимую управляющую процедуру, которые заменят использование множества представлений. Создание множества представлений обычно приводит к увеличению числа объектов базы данных, так как для каждой группы пользователей требуется создание отдельного представления. Например, в описанном выше примере со служащими, менеджерами и контролерами в обычной системе необходимо создать 3 представления базы данных. Если потребуется еще одна группа пользователей, то придется добавить еще один набор представлений, которым надо будет управлять и поддерживать. Если политика безопасности изменится (то есть потребуется, чтобы менеджеры видели не только своих непосредственных подчиненных, но и на 2 уровня ниже), необходимо будет пересоздать представления базы данных, после чего все объекты, ссылающихся на эти представления, станут недействительными.
  • Осуществляется на сервере. Учитывая сложность управления и поддержки большого количества представлений, разработчики раз за разом стремятся закладывать логику приложения в самое приложение. Приложения просматривают, кто присоединен к базе данных, что он запрашивает, и выполняют соответствующий запрос. Это защищает данные, но только тогда, когда доступ к ним осуществляется через данное приложение. Это снижает возможность использования средств выполнения запросов и генерации отчетов, а также других средств обработки данных. Повышается также вероятность получения искаженных данных, так как для того, чтобы сделать искажение, достаточно подключиться к базе данных через любое другое средство, отличное от рассматриваемого приложения, и запросить данные. Благодаря же включению в базу данных логики безопасности, то есть механизма, который определяет, какие данные может видеть пользователь, - вы можете быть уверены, что данные будут защищены независимо от используемого средства доступа к ним, и обращение можно осуществлять с помощью любого средства, из которого возможен доступ к данным.
  • Запрет на соединение с базой данных от имени обобщенных пользователей. Благодаря Детальному Контролю Доступа каждый пользователь должен соединяться с базой данных под своим именем. В этом случае обеспечивается полная подотчетность - можно отслеживать действия на уровне пользователя. Раньше многие приложения при работе с различными представлениями данных для различных пользователей должны были применять обобщенных пользователей базы данных, соответственно выбираемым данным. Например, в вышеописанном случае служащий/менеджер/контролер в приложении должно быть создано три учетных записи. Каждый служащий должен использовать учетную запись 'Служащий'. Каждый менеджер должен использовать учетную запись 'Менеджер'. Каждый контролер должен использовать учетную запись 'Контролер'. Это делает невозможным учитывать действия на уровне истинных пользователей.
  • Упрощение разработки приложения. Детальный Контроль Доступа забирает логику безопасности из логики приложения. Для поддержки безопасности данных разработчик приложения может сконцентрироваться на самом приложении, а не на логике низкоуровневого доступа к данным. Так как Детальный Контроль Доступа полностью осуществляется на сервере, то приложения непосредственно наследуют эту логику. Раньше разработчики приложения должны были встраивать логику в приложение, делая приложение все более сложным, сначала для разработки и особенно сложным для его последующей поддержки. Если из приложения возможен доступ к данным, причем к одним и тем же данным и из нескольких точек приложения, то простейшее изменение политики безопасности может затронуть много дюжин модулей приложения. Благодаря применению Детального Контроля Доступа, изменения в политике безопасности не влияют на модули приложения.
  • Применение развитых средств разработки приложения. Во многих средах политика безопасности по началу еще должным образом не определена и через некоторое время может измениться. Если происходит слияние компаний или другие структурные перемены или вводятся правила секретности, то политику безопасности необходимо изменить. Благодаря тому, что управление доступом осуществляется на уровне, близком к данным, можно создать условия для развития приложения с минимальным влиянием, и на него, и на средства разработки. Это является одной из причин для того, чтобы перейти к автоматическому использованию как новой логики, так и всех приложений и инструментов, позволяющих осуществлять доступ к базе данных со встроенной новой логикой.

Способы использования этой возможности

В Oracle8i существует два типа детального контроля доступа:

  • "Контекст" приложения. Это пространство имен с набором пар соответствующих параметров переменная/значение. Например, в контексте, называемом 'OurApp', можно получить доступ к переменным 'DeptNo', 'Mgr' и так далее. Контексты приложения всегда связываются с некоторым PL/SQL-пакетом. Единственный способ присваивания значений контекста - это вызов пакета. Например, для получения переменной 'DeptNo' и установки ее значения в контексте 'OurApp' необходимо вызвать специальный пакет, связанный с контекстом 'OurApp'. Этот пакет гарантирует корректную установку значений контекста 'OurApp' (вы сами так написали, поэтому корректная установка контекста гарантируется). В этом случае предотвращается установка значений контекста приложения злоумышленниками, которые в противном случае могли бы получить доступ к той информации, доступа к которой у них быть не должно. Любой пользователь может читать значения контекста приложения, но установить их может только пакет.
  • Политика безопасности. Политика безопасности представляет собой просто функцию, построенную так, чтобы во время выполнения запроса она могла возвращать предикат для динамической фильтрации данных. Эта функция обычно использует значения контекста приложения для формирования и возврата корректного предиката (т.е. она просматривает, 'кто' присоединен к базе данных, 'что' он собирается сделать, и проверяет, есть ли у него привилегии для выполнения этих операций). Следует обратить внимание, что по отношению к пользователю SYS (или INTERNAL) никогда не используется политика безопасности, эти пользователи могут видеть все данные.

Для того, чтобы воспользоваться этой возможностью, разработчик, кроме стандартных ролей connect и resource, должен иметь следующие привилегии:

  • EXECUTE_CATALOG_ROLE. Позволяет разработчику выполнять функции и процедуры пакета dbms_rls. Другой вариант - можно, присоединившись как SYS, передать привилегию только на пакет: grant execute on dbms_rls to <учетная_запись>.
  • CREATE ANY CONTEXT: Позволяет разработчику создавать контексты приложения.

Контексты приложения создаются простой SQL-командой

SQL> create context OurApp using Our_Context_Pkg;

OurApp - это имя контекста, а Our_Context_Pkg - это PL/SQL-пакет, через который устанавливаются значения контекста. Возможность использования Контекстов Приложения для Детального Контроля Доступа имеет большое значение по двум причинам:

  • Обеспечивается гарантированный способ установки переменных пространства имен. Устанавливать значения этого контекста может только PL/SQL-пакет, связанный с контекстом. В этом случае гарантируется целостность значений контекста. Так как контексты предназначены для ограничения или разрешения доступа к данным, целостность значений контекста должна быть обеспечена.
  • В SQL-запросе ссылки на значения Контекста Приложения трактуются как связанные переменные. Например, установка переменной 'DeptNo' контекста 'OurApp' и использование политики "deptno = SYS_CONTEXT('OurApp','DeptNo')" для возврата условия where не повлияют на частоту использования разделяемого sql-предложения, так как ссылка SYS_CONTEXT подобна "deptno = :b1". Каждый может пользоваться значениями 'Deptno', но все будут повторно использовать один и тот же разобранный оптимизированный план запроса.

Пример 1. Реализация политики безопасности

Если требуется, чтобы политика безопасности позволяла пользователю, не являющемуся RLS_ADMIN, видеть только такие строки, 'владельцем' которых он является, то необходимо выполнить команду:

SQL> create function my_security_function
  2     ( p_schema in varchar2,p_object in varchar2 )
  3      return varchar2
  4  as
  5  begin
  6      if ( user = 'RLS_ADMIN' ) then
  7  return '';
  8      else
  9  return 'owner = USER';
 10      end if;
 11  end;
 12  /
Функция создана.

Предикат "where owner = USER" будет динамически добавляться ко всем запросам по таблице, с которой связана эта функция, что значительно уменьшает количество строк, доступных пользователю. Предикат NULL (пусто) возвращается только в том случае, когда на данный момент к базе данных присоединен пользователь RLS_ADMIN. Пустой возвращаемый предикат выглядит как "1=1" или "TRUE".

Для того, чтобы связать эту функцию с таблицей, необходимо использовать PL/SQL-процедуру "dbms_rls.add_policy". Например, имеется следующая таблица:

SQL> create table my_table
  2  (  datavarchar2(30),
  3     OWNER       varchar2(30) default USER
  4  )
  5  /
Table created.

SQL> grant all on my_table to public
  2  /
Grant succeeded.

SQL> insert into my_table (data) 
  2       values ('Некоторые данные')
  3  /
1 row created.

SQL> insert into my_table ( data, owner )
  2  values ( 'Some Data Owned by SCOTT', 'SCOTT' )
  3  /
1 row created.

SQL> commit
  2  /
Commit complete.

SQL> select * from my_table
  2  /

DATA                             OWNER
-------------------------------- ---------
Некоторые даные                  RLS
Некоторые данные владелеца SCOTT SCOTT 


Политику "My_Security_Policy" следует подключать 
следующим образом:

SQL> begin
  2  dbms_rls.add_policy
  3  ( object_schema   => 'RLS',
  4    object_name     => 'MY_TABLE',
  5    policy_name     => 'MY_POLICY',
  6    function_schema => 'RLS',
  7    policy_function => 'My_Security_Function',
  8    statement_types => 'select,insert,update,delete',
  9    update_check    => TRUE );
 10  end;
 11  /

PL/SQL procedure successfully completed.

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

SQL> connect rls/rls
Connected.
SQL> select * from my_table
  2  /

DATA                    OWNER
----------------------- ------------
Некоторые данные	RLS

Итак, полученный результат показывает, что строки отфильтрованы надлежащим образом - текущий пользователь RLS может видеть только свои строки - он является их владельцем. Строки, владельцем которых является SCOTT, стали невидимы. Присоединимся теперь как учетная запись RLS_ADMIN:

SQL> connect rls_admin/rls_admin
Connected.
SQL> select * from rls.my_table
  2  /

DATA                              OWNER
--------------------------------- ----------------
Некоторые данные                  RLS
Некоторые данные владелеца SCOTT  SCOTT

Результат показывает, что учетная запись RLS_ADMIN может видеть все данные, какие пожелает. Присоединимся опять учетной записью RLS и посмотрим, что произойдет при попытке создания данных, которые нельзя 'увидеть' (пользователь не являемся их владельцем):

SQL> connect rls/rls
Connected.
SQL> insert into my_table ( data ) 
  2         values ( 'Некоторые новые данные' )
  3  /

1 row created.

SQL> insert into my_table ( data, owner )
  2  values ('Некоторые новые данные 
  3    владелеца SCOTT', 'SCOTT' )
  4  /
insert into my_table ( data, owner )
    *
ERROR at line 1:
ORA-28115: нарушение политики с опцией проверки

Ошибка ORA-28115 возникает, так как при добавлении политики было указано:

:
9       update_check    => TRUE );
:

по аналогии с созданием представления с включенной возможностью "CHECK OPTION". Такая политика позволяет создавать только те данные, которые можно выбрать. По умолчанию можно создавать данные, которые выбрать нельзя.

Важное предупреждение

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

  3  as
  4  begin
  5      if ( user = 'RLS_ADMIN' ) then
  6  return '';
  7      else
  8  return 'owner = USER';
  9      end if;
 10  end;

Эта предикатная функция либо не возвращает никакого предиката, либо возвращает "owner = USER". Во время заданной сессии она постоянно будет возвращать один и тот же предикат. Ситуация, когда получен предикат "owner = USER", а затем в этой же сессии - пустой предикат "", возникнуть не может. Для того, чтобы понять, почему это крайне необходимо для корректного проектирования приложений с Детальным Контролем Доступа, следует понять, когда предикат связывается с запросом и как различные среды обрабатывают эту ситуацию.

Предположим, что написана следующая предикатная функция:

SQL> create or replace function rls_examp
  2  ( p_schema in varchar2, p_object in varchar2 )
  3  return varchar2
  4  as
  5  begin
  6  if ( sys_context( 'myctx', 'x' ) is not null )
  7  then
  8  return 'x > 0';
  9  else
 10  return '1=0';
 11  end if;
 12  end;
/

Это показывает, что если атрибут "x" контекста установлен, то предикат должен иметь значение "x > 0". Если атрибут "x" контекста не установлен, то предикат должен быть "1=0". При создании таблицы T, добавьте в нее данные, политику и контекст так, как показано ниже:

SQL> create table t ( x int );
Table created.

SQL> insert into t values ( 1234 );
1 row created.

SQL> begin
  2     dbms_rls.add_policy
  3     ( object_schema   => user, object_name => 'T',
  4       policy_name => 'T_POLICY', 
  5       function_schema => user,
  6       policy_function => 'rls_examp', 
  7       statement_types => 'select' );
  8  end;
  9  /
PL/SQL procedure successfully completed.

SQL> create or replace procedure 
  2   set_ctx( p_val in varchar2 )
  3  as
  4  begin
  5  dbms_session.set_context( 'myctx', 'x', p_val );
  6  end;
  7  /
Procedure created.

SQL> create or replace context myctx using set_ctx;
Context created. 

Такая политика означает, что если контекст установлен, можно будет увидеть 1 строку. Если контекст не установлен, ни одной строки не будет видно. Действительно, если провести тест в SQLPLUS, непосредственно выполняя SQL, то получится следующий результат:

SQL> exec set_ctx( null );
PL/SQL procedure successfully completed.

SQL> select * from t;
no rows selected

SQL> exec set_ctx( 1 ) ;
PL/SQL procedure successfully completed.

SQL> select * from t;

 X
----------
      1234

Таким образом, выбрались те данные, которые ожидались. Динамический предикат работает так, как ожидалось. В действительности же, если использовать PL/SQL (Pro*C или приложения, написанные на OCI, а также многие другие исполняемые среды) обнаруживается, что вышеописанный результат неверен. Создадим, например, небольшую PL/SQL-процедуру:

SQL> create or replace procedure dump_t
  2  ( some_input in number default NULL )
  3  as
  4  begin
  5  dbms_output.put_line
  6  ( '*** Результат работы SELECT * FROM T' );
  7  
  8  for x in (select * from t ) loop
  9  dbms_output.put_line( x.x );
 10  end loop;
 11  
 12  
 13  if ( some_input is not null )
 14  then
 15  dbms_output.put_line
 16  ('*** Результат работы другого SELECT * FROM T');
 17  
 18  for x in (select * from t ) loop
 19  dbms_output.put_line( x.x );
 20  end loop;
 21  end if;
 22  end;
 23  /

Procedure created.

В первый раз простой "select * from T" в этой процедуре выполняется, когда входной параметр не задан, и во второй раз, когда задано его некоторое значение. Давайте выполним эту процедуру и посмотрим результат:

SQL> -- Включим вывод на экран 
SQL> -- результат dbms_output.put_line
SQL> set serveroutput on
 
SQL> -- отменим установку контекста 
 SQL> -- присвоим X значение NULL
SQL> exec set_ctx( NULL )
PL/SQL procedure successfully completed.

SQL> -- выполним процедуру. Заметьте, что
SQL> -- some_input по умолчанию может быть NULL. 
SQL> -- Выполнится только 1-ый select * from t.
SQL> -- Как и ожидалось, выбрано НОЛЬ строк, так как 
SQL> -- использовался предикат 1=0
SQL> exec dump_t
*** Результат работы SELECT * FROM T
PL/SQL procedure successfully completed.

SQL> -- Теперь установим значение контекста 
               --  в ненулевое значение.
SQL> exec set_ctx( 1 )
PL/SQL procedure successfully completed.

SQL> -- Так как таблица t содержит 1 строку со 
SQL> -- значением 1234, а предикат должен быть "x > 0", 
SQL> -- когда этот атрибут установлен, то для получения 
SQL> -- данных можно выполнить запрос к таблице T.
SQL> -- Чтобы убедиться в том, что результат может 

SQL> -- оказаться неверным, выполним процедуру dump_t с 
SQL> --некоторым НЕНУЛЕВЫМ входным параметром.
SQL> -- В этом случае выполнятся оба select * from T

SQL> -- Следует обратить внимание на то, что при первом 
SQL> -- выполнении "select * from T" никакие данные 
SQL> -- не возвращаются!
SQL> -- А при втором - возвращаются!

SQL> -- Почему? Смотрите далее

SQL> exec dump_t( 0 )
*** Результат работы SELECT * FROM T
*** Результат работы другого SELECT * FROM T
1234
PL/SQL procedure successfully completed.

Итак, при запуске процедуры с атрибутом контекста "x", установленным в значение null, получен ожидаемый результат (так как в этой сессии процедура была запущена первый раз). Затем контекстный атрибут "x" был установлен в ненулевое значение, и результат получился "противоречивый". Первый select * from t в процедуре снова не возвратил ни одной строки - он, скорее всего, все еще использует предикат "1=0". Второй запрос (тот, что в первый раз не выполнялся) возвратил, казалось бы, корректный результат - он, как и ожидалось, использует предикат "x > 0",.

Почему первый запрос в этой процедуре не использовал предикат, который предполагался? Это произошло из-за оптимизации, называемой "кэширование курсора". На самом деле PL/SQL и многие другие исполняемые среды не закрывают курсор по команде 'закрыть'. Вышеописанный пример может быть легко воспроизведен, например, в Pro*C, если опцию предкомпилятора "release_cursor" оставить в значении по умолчанию NO. Если тот же самый код перекомпилировать с опцией release_cursor=YES, то программа Pro*C будет вести себя более похоже на запросы в SQLPLUS. Предикат, используемый DBMS_RLS, связывается с запросом во время фазы PARSE. Первый запрос "select * from T" разбирается во время первого выполнения хранимой процедуры - когда предикат действительно был равен "1=0". Инструмент PL/SQL кэширует этот разобранный курсор. Во второй раз при выполнении хранимой процедуры PLSQL просто повторно использует разобранный курсор из первого "select * from T", при этом разобранный запрос имеет предикат "1=0" - предикатная функция в этот момент вообще не вызывалась. Так как процедуре передаются также некоторые входные данные, PLSQL выполнил второй запрос. Этот запрос, однако, уже не является открытым и разобранным, поэтому он разбирается во время его выполнения - когда контекстный атрибут НЕ ПУСТОЙ. Второй "select * from t" использует связанный предикат "x>0". Отсюда и противоречивость. Так как в общем случае контроль за кэшированием этих курсоров не осуществляется, то предикатную функцию безопасности, возвращающую более 1 предиката за сессию, следует во что бы то ни стало избегать. В противном случае в будущем придется с большим трудом отыскивать ошибки приложения. В следующем примере я продемонстрирую, как построить предикатную функцию безопасности, которая не сможет возвратить более одного предиката за сессию. В этом случае гарантируется, что:

  • Результаты одного запроса не противоречат результатам другого при работе с Детальным Контролем Доступа.
  • Изменить предикат в середине сессии не удастся, иначе можно получить неопределенные непредсказуемые результаты.
  • Предприняты меры для того, чтобы убедить политику безопасности в том, что имеющийся предикат - единственный для пользователя, и не пытаться возвратить предикат, специализированный под пользователя, работающего в текущем окружении.

Я настоятельно советую создавать все нетривиальные предикатные функции так же, как показано в следующем примере. Это позволит создавать хорошо работающие, предикабельные приложения, поддерживающие Детальный Контроль Доступа.

Следует добавить, что в некоторых случаях изменение предиката в середине сессии желательно. Для достижения наилучших результатов клиентские приложения, имеющие доступ к объектам, которые поддерживаются политикой, позволяющей изменять предикаты в середине сессии должны быть написаны в особой форме. Например, во избежание кэширования курсора в PLSQL необходимо написать приложение, полностью использующее динамический sql. Если используется этот динамический предикатный метод, то необходимо иметь в виду, что результаты будут зависеть от того, как написано клиентское приложение. Поэтому не следует применять политику безопасности с использованием этой возможности. Мы не будем рассматривать использование всех возможностей DBMS_RLS, а лучше сконцентрируемся на ее конкретном использовании - для защиты данных.

Пример 2. Использование контекстов приложения

Необходимо, например, реализовать Политику Безопасности в подсистеме 'Кадры' (Human Resources Security Policy ). В этом примере будут использоваться таблицы EMP и DEPT демонстрационного пользователя SCOTT/TIGER и добавится еще одна таблица, которая позволит назначить человека на должность контролера. Далее перечислены требования:

  • Менеджер отдела может:
    • ЧИТАТЬ свою запись, записи всех сотрудников, которые ему подчиняются и всех сотрудников, которые в свою очередь подчиняются этим подчиненным (по иерархии)
    • ОБНОВЛЯТЬ записи всех служащих, которые подчиняются ему непосредственно.
  • Служащий может:
    • ЧИТАТЬ свою запись.
  • Контролер может:
    • ЧИТАТЬ все записи по отделу, в котором он работает (В этом приложении контролер работает в одном из отделов)
    • ОБНОВЛЯТЬ любую запись заданного отдела
    • ВСТАВЛЯТЬ в заданный отдел.
    • УДАЛЯТЬ из заданного отдела.

Как было сказано ранее, приложение будет использовать существующие таблицы EMP и DEPT пользователя SCOTT и добавочную таблицу HR_REPS для связи контролера с отделом. Схема будет выглядеть следующим образом:

SQL> -- создадим демонстрационную схему.  
SQL> -- Она основывается на таблицах EMP и DEPT, 
SQL> -- владелец которых scott.  Добавим в схему 
SQL> -- описатель RI (идентификатор контролера) и 
SQL> -- переименуем значения поля ENAME в таблице EMP 
SQL> -- так, чтобы они соответствовали именам 
SQL> -- пользователей тестируемой базы данных 
SQL> -- (например: пользователю RLS_KING соответствует 
SQL> -- имя пользователя RLS_KING в таблице emp)

SQL> create table dept as select * from scott.dept;
Table created.

SQL> alter table dept add constraint dept_pk primary
  2       key(deptno);
Table altered.

SQL> create table emp_base_table as 
  2       select * from scott.emp;
Table created.

SQL> update emp_base_table 
  2      set ename = 'RLS_' || ename;
14 rows updated.

SQL> alter table emp_base_table 
  2      add constraint emp_pk primary key(empno);
Table altered.

SQL> alter table emp_base_table 
  2      add constraint emp_fk_to_dept
  3  foreign key (deptno) references dept(deptno);
Table altered.

SQL> -- создадим индексы, которые будут использоваться 
SQL> -- функцией контекста приложения для повышения 
SQL> -- производительности.
SQL> -- Необходимо быстро определить, является ли 
SQL> -- некоторый пользователь mgr (менеджером) отдела. 
SQL> -- Кроме того, необходимо быстро конвертировать имя 
SQL> -- пользователя в empno

SQL> create index emp_mgr_deptno_idx 
  2      on emp_base_table(mgr);
Index created.

SQL> create unique index emp_ename_idx 
  2     on emp_base_table(ename);
Index created.

SQL> -- Кроме того, создадим представление EMP 
SQL> -- на основе запроса 
SQL> -- select * from emp_base_table. 
SQL> -- К этому ПРЕДСТАВЛЕНИЮ
SQL> -- будет применена политика, и через него 
SQL> -- приложения будут 
SQL> -- запрашивать/вставлять/обновлять
SQL> -- и так далее.

SQL> create view emp 
  2     as select * from emp_base_table;
View created.


SQL> -- создадим таблицу для управления HR_REPS.  
SQL> -- Для этого будет использоваться 
SQL> -- INDEX ORGANIZED TABLE, так как всегда будет 
SQL> -- выполняться запрос только такого типа,
SQL> -- как
SQL> -- "select * from hr_reps 
SQL> --          where username = X and deptno = Y".
SQL> -- В использовании таблицы нет необходимости, 
SQL> -- достаточно использовать только индекс.

SQL> create table hr_reps
  2  (   username    varchar2(30),
  3      deptnonumber,
  4      primary key(username,deptno)
  5  )
  6  organization index;
Table created.

SQL> -- Свяжем HR Reps с отделами.  
SQL> -- KING может видеть все отделы.

SQL> insert into hr_reps values ( 'RLS_JONES', 10 );
SQL> insert into hr_reps values ( 'RLS_BLAKE', 20 );
SQL> insert into hr_reps values ( 'RLS_CLARK', 30 );
SQL> insert into hr_reps values ( 'RLS_KING', 10 );
SQL> insert into hr_reps values ( 'RLS_KING', 20 );
SQL> insert into hr_reps values ( 'RLS_KING', 30 );
SQL> insert into hr_reps values ( 'RLS', 10 );
SQL> commit;
Commit complete.

Теперь, когда для приложения созданы таблицы EMP, DEPT и HR_REPS, напишем процедуру, которая позволит установить значения контекста приложения. Контекст приложения будет состоять из двух элементов данных - EMPNO пользователя, присоединенного в данный момент, и его роль (одна из EMP, MGR или HR_REP). Процедура формирования динамического предиката будет использовать роль, хранящуюся в контексте приложения для того, чтобы определить, как должно выглядеть условие where для конкретного пользователя.

Для осуществления этой задачи используются таблицы EMP_BASE_TABLE и HR_REP. Тогда возникает вопрос "зачем использовать таблицу EMP_BASE_TABLE и представление EMP, если можно просто выполнить select * from emp_base_table?" По двум причинам:

1. Данные таблицы служащих используются для реализации политики безопасности.

2. Эта таблица считывается при создании контекста приложения.

Для того, чтобы прочитать данные о служащем, необходимо установить значения контекста приложения, для того, чтобы установить значения контекста приложения, необходимо прочитать данные о служащем. Это проблема курицы и яйца. Одним из вариантов ее решения является создание представления, которым будут пользоваться все приложения (представление EMP) и реализация безопасности этого представления. Для осуществления этой задачи политика безопасности будет пользоваться исходной таблицей EMP_BASE_TABLE. Из таблицы EMP_BASE_TABLE можно узнать, кто является менеджером некоторого отдела и кто подчиняется некоторому пользователю. Приложение и конечные пользователи никогда не будут обращаться к таблице EMP_BASE_TABLE - она будет использоваться только политикой безопасности. После этого создается процедура установки значений контекста:

SQL> -- это процедура, "несущая ответственность" за 
SQL> -- создание контекста приложения. Она реализует
SQL> -- политику путем установки привилегий,
SQL> -- выданных пользователю в контексте приложения.

SQL> create or replace procedure 
  2     set_role( p_roleName in varchar2 default null )
  3  as
  4      l_empno       number;
  5      l_cnt      number;
  6      l_roleName varchar2(40) default 
                                     upper(p_roleName);
  7  begin
  8      if ( sys_context( 'Hr_App_Ctx', 'RoleName' ) 
                                     is NOT NULL )
  9      then
 10  -- прерывание запроса. Изменение роли требует
 11  -- изменения предиката, связанного с запросом. Из-за 
 12  -- кеширования курсоров клиентском приложении нельзя 
 13  -- гарантировать, что не существует других уже 
 14  -- разобранных запросов с предикатами из текущей 
15  -- роли. Если, например, роль уже установлена в 
 16  -- значение MGR и разобрано несколько запросов, а 
 17  -- теперь попытаться изменить роль на EMP, то 
 18  -- разобранные запросы все еще будут использовать 
 19  -- предикаты MGR, а не EMP.
20 raise_application_error(-2000,'Роль уже установлена');
 21      end if;
 22    -- Так как используется пользователь СЕССИИ, а 
 23  -- не ТЕКУЩИЙ пользователь и, кроме того, empno 
 24  -- текущего пользователя используется достаточно 
 25  -- часто, то он будет храниться в этом Контексте. 
 26  -- Пользователь СЕССИИ - это имя присоединенного 
 27  -- в данный момент пользователя. ТЕКУЩИЙ  же , 
 28  -- пользователь обладает привилегиями на выполнение 
 29  -- запроса, им может быть владелец процедуры, 
 30 -- т.е. не присоединенный пользователь!!!
 31      select empno into l_empno
 32 from emp_base_table
 33 where ename=
        sys_context('userenv', 'session_user');
 34 dbms_session.set_context
         ('Hr_App_Ctx','Empno',l_empno);
 35
 36      if ( l_roleName = 'EMP' )
 37      then
 38  -- любой может воспользоваться ролью EMP
 39 dbms_session.set_context
         ('Hr_App_Ctx','RoleName','EMP');
 40      elsif ( l_roleName = 'MGR' )
 41      then
 42 -- проверим, пользователь - MGR (менеджер), и
 43 -- если нет, то выдадим сообщение об ошибке и прервем 
 44 -- выполнение.  Пользователь может попытаться снова.
 45  select count(*) into l_cnt
 46    from dual
 47   where exists
 48     ( select NULL
 49     from emp_base_table
 50  where mgr = to_number
             (sys_context('Hr_App_Ctx','Empno'))
 51     );
 52  if ( l_cnt = 0 )
 53  then
 54  raise_application_error
             ( -20002, 'Вы не менеджер' );
 55  end if;
 56  dbms_session.set_context
               ( 'Hr_App_Ctx', 'RoleName', 'MGR' );
 57      elsif ( l_roleName = 'HR_REP' )
 58      then
 59  -- проверим, является ли пользователь HR_REP, если 
 60 -- нет, выдадим сообщение об ошибке и прервем
 61 -- выполнение.Пользователь может попытаться снова.
 62  select count(*) into l_cnt
 63    from dual
 64   where exists
 65     ( select NULL
 66     from hr_reps
 67  where username = 
                sys_context( 'userenv', 'session_user' )
 68     );
 69
 70  if ( l_cnt = 0 )
 71  then
 72  raise_application_error( -20002, 'Вы не контролер' );
 73  end if;
 74  dbms_session.set_context
              ('Hr_App_Ctx','RoleName','HR_REP');
 75      else
 76  raise_application_error( -20003, 'Роль 
 77  '||l_roleName||' невозможно распознать' );
 78      end if;
 79  end;
 80  /
Procedure created.

SQL> grant execute on set_role to public
  2  /
Grant succeeded.

Итак, до этого момента сделано: создана процедура, которая принимает имя роли в виде параметра. В начале этой процедуры обеспечивается, чтобы атрибут RoleName еще не был установлен. Так как в политике безопасности будут возвращаться различные предикаты, зависящие от значения RoleName, то нельзя разрешать пользователю изменять его роль, если она уже установлена. Если допустить изменение роли, то может возникнуть проблема, связанная с кэшированием курсора и 'old'-предикатами. Далее посмотрим на EMPNO текущего пользователя. Эта процедура выполняет две операции:

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

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

Далее процедура переходит к проверке, разрешено ли текущему пользователю получить роль, которую он запрашивает. Каждый, кто запрашивает роль EMP, может установить ее. Только тот, кто действительно управляет другими людьми, может установить роль MGR. Только тот, о ком есть данные в таблице HR_REPS, может установить роль HR_REP.

Далее создадим объект контекста приложения базы данных и свяжем его с только что созданной процедурой SET_HR_APP_DEPT:

SQL> -- Создание контекста приложения. Имя контекста -
SQL> -- HR_APP_CTX.  Процедура, с которой он связан 
SQL> -- в данном случае - это SET_ROLE

SQL> create or replace context Hr_App_Ctx using SET_ROLE
  2  /
Context created.

Итак, теперь созданы контекст с именем Hr_App_Ctx и процедура для его установки. Важно иметь в виду, что, так как контекст связан с процедурой Set_Role, то она является единственным средством установки значений контекста. Если, например, попытаться установить в этом контексте RoleName с целью получения доступа к запрещенным данным, то обнаружится, что сделать это нельзя:

SQL> REM Выполнение следующих далее операций будет 
SQL> REM ПРЕРВАНО. Это показывает, что 
SQL> REM процедура dbms_session.set_context 
SQL> REM может установить контекст Hr_App_Ctx только 
SQL> REM через процедуру SET_ROLE 

SQL> exec dbms_session.set_context
            ( 'Hr_App_Ctx', 'RoleName', 'MGR' );
BEGIN dbms_session.set_context
             ( 'Hr_App_Ctx', 'RoleName', 'MGR' ); END;

*
ERROR at line 1:
ORA-01031: привилегий недостаточно
ORA-06512: at "SYS.DBMS_SESSION", line 55
ORA-06512: at line 1

Этот атрибут контекста отвечает за безопасность. Значения контекста можно установить только через процедуру. В этом случае гарантируется, что, если значение и существует, то оно проверено и установлено. Теперь для тестирования логики процедуры попытаемся выполнить хранимую процедуру под различными пользователями и посмотрим, какие роли можно установить и какие значения существуют в контексте.

SQL> grant select on sys.v_$context to rls_smith;
Grant succeeded.

SQL> connect rls_smith/rls_smith
Connected.

SQL> set serveroutput on
SQL> show user
USER is "RLS_SMITH"

SQL> exec rls.set_role( 'Mgr' )
BEGIN rls.set_role( 'Mgr' ); END;

*
ERROR at line 1:
ORA-20002: Вы не менеджер
ORA-06512: at "RLS.SET_ROLE", line 53
ORA-06512: at line 1

До настоящего времени можно было увидеть, что процедура доступна RLS_SMITH, но не позволяет ему установить контекст 'MGR' до тех пор, пока он фактически не станет менеджером. Если теперь посмотреть на этот контекст через динамическое представление v$context, то можно увидеть:

SQL> select * from v$context;

NAMESPACE  ATTRIBUTE  VALUE
---------- ---------- -----
HR_APP_CTX EMPNO      7369

что RLS_SMITH действительно мог получить список номеров его служащих, но не атрибут RoleName. Теперь повторим операцию с RoleName, задав правильное значение для RLS_SMITH:

SQL> exec rls.set_role( 'emp' )
PL/SQL procedure successfully completed.

SQL> select * from v$context;

NAMESPACE  ATTRIBUTE  VALUE
---------- ---------- -----
HR_APP_CTX ROLENAME   EMP
HR_APP_CTX EMPNO      7369

Можно увидеть, что теперь все работает, как ожидалось. RLS_SMITH может получить номер его служащего и атрибут RoleName, установленный в контексте HR_APP_CTX. Для дальнейшего тестирования логики выполним:

SQL> exec rls.set_role( 'emp' )
BEGIN rls.set_role( 'emp' ); END;

*
ERROR at line 1:
ORA-20001: Роль уже установлена
ORA-06512: at "RLS.SET_ROLE", line 18
ORA-06512: at line 1

SQL> select * from v$context;

NAMESPACE  ATTRIBUTE  VALUE
---------- ---------- -----
HR_APP_CTX ROLENAME   EMP
HR_APP_CTX EMPNO      7369

Результат показывает, что логика не позволяет пользователю изменить его роль после того, как она уже установлена, далее будет видно, что в контексте остаются прежние значения. Это сообщение об ошибке не критично, оно значит только то, что нельзя изменить роль во время сессии.

Далее присоединимся различными пользователями, просмотрим результаты работы процедуры и рассмотрим различные способы получения значений контекста сессии:

SQL> connect rls_blake/rls_blake
Connected.

SQL> exec rls.set_role( 'mgr' )
PL/SQL procedure successfully completed.

SQL> set serveroutput on
SQL> declare
  2      l_AppCtx     dbms_session.AppCtxTabTyp;
  3      l_sizenumber;
  4  begin
  5      dbms_session.list_context
             ( l_AppCtx, l_size );
  6      for i in 1 .. l_size loop
  7  dbms_output.put( l_AppCtx(i).namespace || '.' );
  8  dbms_output.put( l_AppCtx(i).attribute || ' = ' );
  9  dbms_output.put_line( l_AppCtx(i).value );
 10      end loop;
 11  end;
/

HR_APP_CTX.ROLENAME = MGR
HR_APP_CTX.EMPNO = 7698

PL/SQL procedure successfully completed.

На этот раз присоединимся как RLS_BLAKE, заведующий отделом 30. Когда RLS_BLAKE вызывает процедуру Set_Role с параметром RoleName = 'MGR', видно, что контекст установлен правильно: он - менеджер, и количество его служащих установлено. Кроме того, этот тест показывает, как просмотреть пары значений атрибутов в контексте сессии с помощью пакета dbms_session.list_context. Функции этого пакета может вызывать любой пользователь (так как происходит обращение к представлению sys.v$context, которое использовалось ранее), поэтому все пользователи могут использовать такой метод для проверки значений контекста сессии.

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

SQL> -- создадим предикатный пакет 
SQL> -- (пакет для генерации условия where) приложения HR. 
SQL> -- Для создания уникального условия 
SQL> -- where каждой операции 
SQL> -- SELECT/INSERT/UPDATE/DELETE 
SQL> -- должна соответствовать отдельная функция.

SQL> create or replace package hr_predicate_pkg
  2  as
  3     function select_function( p_schema in varchar2,
  4       p_object in varchar2 ) return varchar2;
  5
  6     function update_function( p_schema in varchar2,
  7       p_object in varchar2 ) return varchar2;
  8    function insert_delete_function
  9     ( p_schema in varchar2,
 10   p_object in varchar2 ) return varchar2;
 11  end;
 12  /

Для каждой операции Языка Манипулирования Данными (Data Manipulation Language - DML) напишем предикаты, несколько отличающиеся друг от друга. Каждая DML-операция будет подчиняться своим правилам. Это позволит предложению DELETE видеть набор данных (в данном примере меньший), отличающийся от набора данных, видимого при выполнении предложения SELECT. Далее показана реализация тела пакета HR_PREDICATE_PKG:

SQL> create or replace package body hr_predicate_pkg
  2  as
  3
  4  g_app_ctx constant varchar2(30) 
  5     default 'Hr_App_Ctx';
  6  g_sel_pred varchar2(1024) default NULL;
  7  g_upd_pred varchar2(1024) default NULL;
  8  g_ins_del_pred varchar2(1024) default NULL;
  9
 10
 11  function select_function( p_schema in varchar2,
 12    p_object in varchar2 ) return varchar2
 13  is
 14  begin
 15  if ( g_sel_pred is NULL )
 16  then
 17  if ( sys_context( g_app_ctx, 'RoleName' ) = 'EMP' ) 
 18  then
 19  g_sel_pred := 
 20  'empno = sys_context(''Hr_App_Ctx'',''EmpNo'')';
 21   elsif ( sys_context
 22         ( g_app_ctx, 'RoleName' ) = 'MGR' )
 23  then
 24  g_sel_pred :=
 25    ' empno in ( select empno
 26    from emp_base_table
 27   start with empno =
 28  sys_context(''Hr_App_Ctx'',''EmpNo'')
 29 connect by prior empno = mgr)';
 30  elsif (sys_context(g_app_ctx,'RoleName') = 
 31        'HR_REP')
 32  then
 33  g_sel_pred := 'deptno in
 34   ( select deptno
 35       from hr_reps
 36      where username =
 37      sys_context(''userenv'',''session_user'') )';
 38  else
 39  raise_application_error
 40        (-20005,'Роль не установлена');
 41  end if;
 42      end if;
 43
 44      return g_sel_pred;
 45  end;
 46
 47  function update_function( p_schema in varchar2,
 48    p_object in varchar2 ) return varchar2
 49  is
 50  begin
 51      if ( g_upd_pred is NULL )
 52      then
 53  if (sys_context(g_app_ctx,'RoleName') = 'EMP')
 54  then
 55  g_upd_pred := '1=0';
 56
 57  elsif ( sys_context(g_app_ctx,'RoleName') = 'MGR')
 58  then
 59  g_upd_pred :=
 60     ' empno in ( select empno
 61    from emp_base_table
 62   where mgr =
 63       sys_context(''Hr_App_Ctx'',''EmpNo'') )';
 64
 65  elsif (sys_context(g_app_ctx,'RoleName') = 'HR_REP')
 66  then
 67  g_upd_pred := 'deptno in
 68   ( select deptno
 69       from hr_reps
 70      where username =
 71      sys_context(''userenv'',''session_user'') )';
 72  else
 73  raise_application_error
 74       (-20005,'Роль не установлена');
 75  end if;
 76      end if;
 77
 78      return g_upd_pred;
 79  end;
 80  function insert_delete_function
 81   (p_schema in varchar2,
 82       p_object in varchar2 ) return varchar2
 83  is
 84  begin
 85   if (g_ins_del_pred is NULL ) then
 86   if (sys_context(g_app_ctx,'RoleName')    
 87                     in ('EMP','MGR'))
 88  then
 89  g_ins_del_pred := '1=0';
 90  elsif (sys_context(g_app_ctx,'RoleName') = 'HR_REP')
 91  then
 92  g_upd_pred := 'deptno in
 93   ( select deptno
 94       from hr_reps
 95      where username =
 96  sys_context(''userenv'',''session_user'') )';
 97  else
 98  raise_application_error
 99      (-20005,'Роль не установлена');
100 end if;
101      end if;
102      return g_ins_del_pred;
103  end;
104  end;
/

Package body created.

Ранее, без Детального Контроля Доступа использование одной таблицы с описанными выше тремя предикатами можно было достичь при помощи 3-х представлений - по одному на каждую операцию SELECT, UPDATE и INSERT/DELETE. Детальный Контроль Доступа позволяет сократить количество объектов до одной таблицы с динамическим предикатом.

Вспомните логику, описанную ранее:

 11  function select_function( p_schema in varchar2,
 12    p_object in varchar2 ) return varchar2
 13  is
 14  begin
 15
 16      if ( g_sel_pred is NULL )
 17      then
	логика для присвоения значения g_sel_pred, 
    глобальной переменной, 
    объявленной в теле пакета
 42      end if;
 43
 44      return g_sel_pred;
 45  end;

В этой функции переменная g_sel_pred устанавливается в непустое значение точно один раз за сессию. Если при предыдущем вызове этой предикатной функции предикат уже установлен - он просто возвращается снова. В этом есть два преимущества:

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

Последний этап этого процесса заключается в связывании предикатов с каждой DML-операцией и самой таблицей EMP. Далее показана реализация этой операции:

SQL> -- Добавим политику к представлению EMP. 
SQL> -- Каждая функция пакета HR_PREDICATE_PKG 
SQL> -- связывается с таблицей для выполнения операций 
SQL> -- SELECT/INSERTUPDATE/DELETE. 
SQL> -- При INSERT и UPDATE установим флаг
SQL> -- 'update_check' в значение TRUE. 
SQL> -- Это очень похоже на создание
SQL> -- представления с 'CHECK OPTION'
SQL> -- В этом случае обеспечивается, что данные, 
SQL> --  создаваемые в базе данных, - это те данные, 
SQL> -- которые пользователь может увидеть.

SQL> begin
  2 dbms_rls.add_policy
  3 ( object_schema   => 'RLS',
  4 object_name     => 'EMP',
  5 policy_name     => 'HR_APP_SELECT_POLICY',
  6 function_schema => 'RLS',
  7 policy_function => 
  8   'HR_PREDICATE_PKG.SELECT_FUNCTION',
  9 statement_types => 'select' );
 10 end;
 11  /

PL/SQL procedure successfully completed.

SQL> begin
  2  dbms_rls.add_policy
  3  ( object_schema   => 'RLS',
  4  object_name     => 'EMP',
  5  policy_name     => 'HR_APP_UPDATE_POLICY',
  6  function_schema => 'RLS',
  7  policy_function => 
  8    'HR_PREDICATE_PKG.UPDATE_FUNCTION',
  9  statement_types => 'update' ,
 10  update_check    => TRUE );
 11  end;
 12  /

PL/SQL procedure successfully completed.

SQL> begin
  2 dbms_rls.add_policy
  3 ( object_schema   => 'RLS',
  4  object_name     => 'EMP',
  5  policy_name     =>
  6    'HR_APP_INSERT_DELETE_POLICY',
  7  function_schema => 'RLS',
  8  policy_function => 
  9    'HR_PREDICATE_PKG.INSERT_DELETE_FUNCTION',
 10  statement_types => 'insert, delete' ,
 11  update_check    => TRUE );
 12  end;
 13  /

PL/SQL procedure successfully completed.

Таким образом, с каждой DML-операцией связывается различная предикатная функция. Когда пользователь запрашивает данные таблицы EMP, к запросу будет присоединяться предикат, сформированный пакетной функцией hr_predicate_pkg.select. Когда пользователь обновляет таблицу, будет использоваться пакетная функция update и так далее.

Теперь протестируем приложение. Для этого создадим пакет HR_APP. Этот пакет представляет собой приложение. В него входят функции для выполнения:

  • Запроса данных
  • Обновления данных
  • Удаления данных
  • Вставки новых данных

Присоединимся различными пользователями с различными ролями и проследим за поведением нашего приложения. Это позволит увидеть, как работает Детальный Контроль Доступа.

Ниже показана спецификация приложения:

SQL> create or replace package hr_app
  2  as
  3      procedure listEmps;
  4
  5      procedure updateSal;
  6
  7      procedure deleteAll;
  8
  9      procedure insertNew( p_deptno in number );
 10  end;
 11  /

Package created.

Тело пакета:

SQL> create or replace package body hr_app
  2  as
  3
  4  procedure listEmps
  5  as
  6      l_AppCtx     dbms_session.AppCtxTabTyp;
  7      l_size       number;
  8  begin
  9      dbms_output.put_line
       ( '--- Контекст Сессии -------' );
 10      dbms_session.list_context( l_AppCtx, l_size );
 11      for i in 1 .. l_size loop
 12  dbms_output.put( l_AppCtx(i).namespace || '.' );
 13  dbms_output.put( l_AppCtx(i).attribute || ' = ' );
 14  dbms_output.put_line( l_AppCtx(i).value );
 15      end loop;
 16  dbms_output.put_line
 17 ('--Данные таблицы Emp, которые можно увидеть--');
 18      for x in ( select ename, sal, dname
 19       from emp, dept
 20      where emp.deptno = dept.deptno )
 21      loop
 22  dbms_output.put_line
 23        ( x.ename || ',' || x.sal || ',' || x.dname );
 24      end loop;
 25  end;
 26
 27  procedure updateSal
 28  is
 29  begin
 30      update emp set sal = 9999;
 31      dbms_output.put_line
 32       ( sql%rowcount || ' строк обновлено' );
 33  end;
 34  procedure deleteAll
 35  is
 36  begin
 37      delete from emp where empno 
 38         <> sys_context( 'Hr_app_Ctx', 'EMPNO' );
 39      dbms_output.put_line
             ( sql%rowcount || ' строк удалено' );
 40  end;
 41  procedure insertNew( p_deptno in number )
 42  as
 43  begin
 44      insert into emp ( empno, deptno, sal ) 
                   values ( 123, p_deptno, 1111 );
 45  end;
 46
 47  end hr_app;
 48  /

Package body created.

SQL> grant execute on hr_app to public
  2  /
Grant succeeded.

Итак, "приложение" создано. Процедура listEmps показывает все записи, которые можно увидеть через представление EMP. Процедура updateSal обновляет каждую запись, к которой можно получить доступ. Процедура deleteAll удаляет каждую запись, к которой можно получить доступ, за исключением записи, идентифицирующей пользователя. Процедура insertNew создает нового служащего в заданном отделе. Это приложение просто тестирует все DML-операции над представлением EMP, которые можно было бы выполнить.

Теперь присоединимся различными пользователями и протестируем функциональность приложения.

SQL> -- теперь посмотрим, 
SQL> -- что произойдет при попытке выполнить
SQL> -- что-нибудь без получения роли

SQL> exec rls.hr_app.listEmps
--- Контекст Сессии ---
HR_APP_CTX.EMPNO = 7876
--Данные таблицы Emp, которые можно увидеть--
BEGIN rls.hr_app.listEmps; END;

*
ERROR at line 1:
ORA-28112: ошибка при выполнении функции политики
ORA-06512: at "RLS.HR_APP", line 18
ORA-06512: at line 1

Появилось сообщение об ошибке. Это сообщение возникло, потому что так написана предикатная функция:

function select_function( p_schema in varchar2,
  p_object in varchar2 ) return varchar2
is
begin

    if ( g_sel_pred is NULL )
    then
if ( sys_context( g_app_ctx, 'RoleName' ) 
       = 'EMP' )
then
    :
elsif ( sys_context( g_app_ctx, 'RoleName' )
       = 'MGR' )
then
		:
elsif ( sys_context( g_app_ctx, 'RoleName' )
      = 'HR_REP' )
then
:
else
 raise_application_error
     ( -20005, 'Роль не установлена' );
end if;
    end if;

    return g_sel_pred;
end;

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

Далее установим такую роль, чтобы можно было что-нибудь сделать, и попробуем выполнить эти же операции:

SQL> -- Теперь установим корректную роль 
SQL> -- и выполним что-нибудь

SQL> exec rls.set_role( 'emp' );
PL/SQL procedure successfully completed.

SQL> -- посмотрим контекст и данные, которые 
SQL> -- можно видеть - это только одна запись

SQL> exec rls.hr_app.listEmps
--- Контекст Сессии ----
HR_APP_CTX.ROLENAME = EMP
HR_APP_CTX.EMPNO = 7876
--Данные таблицы Emp, которые можно увидеть--
RLS_ADAMS,1100,RESEARCH

PL/SQL procedure successfully completed.

SQL> -- несмотря на то, что данные "видно" 
SQL> -- их нельзя "изменить".

SQL> exec rls.hr_app.updateSal
0 rows updated

PL/SQL procedure successfully completed.

SQL> -- нельзя удалить никакую информацию

SQL> exec rls.hr_app.deleteAll
0 rows deleted

PL/SQL procedure successfully completed.

SQL> -- нельзя ничего создать
SQL> exec rls.hr_app.insertNew(20)
BEGIN rls.hr_app.insertNew(20); END;

*
ERROR at line 1:
ORA-28115: нарушение политики с опцией проверки
ORA-06512: at "RLS.HR_APP", line 44
ORA-06512: at line 1

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

Далее присоединимся как MGR и посмотрим, что произойдет:

SQL> -- Присоединимся как менеджер
SQL> connect rls_jones/rls_jones
Connected.

SQL> -- Включим возможность вывода на экран из PLSQL
SQL> set serveroutput on

SQL> -- Для начала попробуем стать менеджером
SQL> -- мы являемся менеджером, 
SQL> -- так как на этот раз нам разрешено
SQL> -- стать им

SQL> exec rls.set_role( 'mgr' )
PL/SQL procedure successfully completed.

SQL> -- посмотрим контекст и данные, которые 
SQL> -- можно видеть. На этот раз - более одной строки.

SQL> exec rls.hr_app.listEmps
--- Контекст Сессии -----
HR_APP_CTX.ROLENAME = MGR
HR_APP_CTX.EMPNO = 7566
--Данные таблицы Emp, которые можно увидеть--
RLS_SMITH,800,RESEARCH
RLS_JONES,2975,RESEARCH
RLS_SCOTT,3000,RESEARCH
RLS_ADAMS,1100,RESEARCH
RLS_FORD,3000,RESEARCH

PL/SQL procedure successfully completed.

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

SQL> exec rls.hr_app.updateSal
2 rows updated

PL/SQL procedure successfully completed.

SQL> exec rls.hr_app.listEmps
--- Контекст Сессии ----
HR_APP_CTX.ROLENAME = MGR
HR_APP_CTX.EMPNO = 7566
--Данные таблицы Emp, которые можно увидеть--
RLS_SMITH,800,RESEARCH
RLS_JONES,2975,RESEARCH
RLS_SCOTT,9999,RESEARCH
RLS_ADAMS,1100,RESEARCH
RLS_FORD,9999,RESEARCH

PL/SQL procedure successfully completed.


SQL> -- так как мы не являемся контролером, то, 
SQL> -- согласно заданным правилам,
SQL> --  нельзя никого удалить 

SQL> exec rls.hr_app.deleteAll
0 rows deleted

PL/SQL procedure successfully completed.

SQL> -- так как мы не являемся контролером, то, 
SQL> -- согласно заданным правилам, 
SQL> -- нельзя никого вставить
SQL> exec rls.hr_app.insertNew(20)
BEGIN rls.hr_app.insertNew(20); END;

*
ERROR at line 1:
ORA-28115: нарушение политики с опцией проверки
ORA-06512: at "RLS.HR_APP", line 44
ORA-06512: at line 1
SQL> @rls_adams
SQL> -- Присоединимся как служащий, 
SQL> -- у которого нет способности управлять.

SQL> connect rls_adams/rls_adams
Connected.

SQL> set serveroutput on

SQL> -- Сначала попробуем стать менеджером
SQL> -- Мы не являемся менеджером, поэтому стать им
SQL> -- не разрешается 
SQL> exec rls.set_role( 'mgr' )
BEGIN rls.set_role( 'mgr' ); END;

*
ERROR at line 1:
ORA-20002: Вы не менеджер
ORA-06512: at "RLS.SET_ROLE", line 53
ORA-06512: at line 1

Таким образом, результат показывает, что нельзя получить роль, не предназначенную для текущего пользователя. Чтобы убедиться, что ни к каким данным нет доступа, попробуем теперь запросить что-нибудь, и посмотрим, что произойдет:

Таким образом, теперь нам, как MGR, можно:

  • Просматривать не только свои данные. Видно всех, кто нам подчинен и тех, кто подчинен нашим подчиненным и так далее (по иерархии).
  • Изменять некоторые данные. Точнее, можно изменять только те записи, которые относятся к нашим непосредственным подчиненным - что и требуется.
  • Ни над какими данными все еще нельзя выполнить DELETE или INSERT - что и требуется

И, наконец, присоединимся как контролер и посмотрим на поведение приложения при работе с этой ролью:

SQL> -- Присоединимся как контролер
SQL> connect rls_king/rls_king
Connected.

SQL> -- Подключим возможность вывода на экран из PLSQL
SQL> set serveroutput on

SQL> -- Для начала, попробуем стать контролером
SQL> -- Теперь мы являемся контролером, так как 
SQL> -- стать им разрешено 

SQL> exec rls.set_role( 'hr_rep' )
PL/SQL procedure successfully completed.

SQL> -- посмотрим контекст и данные, которые 
SQL> -- можно видеть.  На этот раз видно все 
SQL> -- строки, так как пользователь - контолер.

SQL> exec rls.hr_app.listEmps
--- Контекст Сессии ----
HR_APP_CTX.ROLENAME = HR_REP
HR_APP_CTX.EMPNO = 7839
--Данные таблицы Emp, которые можно увидеть--
RLS_CLARK,2450,ACCOUNTING
RLS_KING,5000,ACCOUNTING
RLS_MILLER,1300,ACCOUNTING
RLS_SMITH,800,RESEARCH
RLS_JONES,2975,RESEARCH
RLS_SCOTT,9999,RESEARCH
RLS_ADAMS,1100,RESEARCH
RLS_FORD,9999,RESEARCH
RLS_ALLEN,1600,SALES
RLS_WARD,1250,SALES
RLS_MARTIN,1250,SALES
RLS_BLAKE,2850,SALES
RLS_TURNER,1500,SALES
RLS_JAMES,950,SALES

PL/SQL procedure successfully completed.

SQL> -- следующая операция показывает, ч
SQL> -- то можно изменить любую запись
SQL> -- в любом отделе, так как пользователь 
SQL> -- для всех является контролером
SQL> -- далее снова запустим listEmps, чтобы увидеть, 
SQL> -- какие строки изменились (все)

SQL> exec rls.hr_app.updateSal
14 rows updated

PL/SQL procedure successfully completed.

SQL> -- так как пользователь - контролер, 
SQL> -- то он может удалить кого-нибудь согласно
SQL> -- заданным правилам
SQL> -- При удалении ВСЕХ не удаляется 'я',
SQL> -- т.е. текущий пользователь

SQL> exec rls.hr_app.deleteAll
13 rows deleted

PL/SQL procedure successfully completed.

SQL> -- так как пользователь - контролер, то он 
SQL> -- может вставить кого-нибудь согласно
SQL> -- заданным правилам

SQL> exec rls.hr_app.insertNew(20)
PL/SQL procedure successfully completed.

SQL> -- посмотрим на результат изменения, 
SQL> -- удаления и последующей вставки

SQL> exec rls.hr_app.listEmps
--- Контекст Сессии ---
HR_APP_CTX.ROLENAME = HR_REP
HR_APP_CTX.EMPNO = 7839
--Данные таблицы Emp, которые можно увидеть--
RLS_KING,9999,ACCOUNTING
,1111,RESEARCH

PL/SQL procedure successfully completed.

На этом завершается тестирование трех ролей рассматриваемого примера. Все требования удовлетворены - безопасность данных обеспечена, и они стали прозрачными для приложения.

Обработка Ошибок и Отладка

Во время создания вышеописанного приложения я натолкнулся на некоторые ошибки и должен был его отлаживать. Так как Детальный Контроль Доступа работает на сервере, то при обнаружении ошибок и отладке приложения могут возникнуть сложности. Следующий раздел поможет успешной отладке и обнаружению ошибок.

Во время разработки процедур Детального Контроля Доступа могут появиться четыре основных кода ошибок Oracle:

  • ORA-28110: функция политики или пакет <имя_функции> содержит ошибки. Это означает, что связанный с политикой пакет или функция содержат ошибки и не могут быть скомпилированы. Ошибки можно увидеть, если выполнить "show errors function <имя_функции>" или "show errors package body <имя_пакета>".
  • ORA-28112: ошибка при выполнении функции политики. Возникает, если ошибка появляется во время выполнения предикатной функции. Это может произойти, например, когда при выполнении предложения SELECT INTO, находящегося внутри PL/SQL-функции, строки не найдены, и для этой ситуации нет обработчика исключений. Функция распространяет исключение NO_DATA_FOUND обратно в точку вызова (в ядро базы данных), и база данных инициирует ошибку ORA-28112.
  • ORA-28113: предикат политики содержит ошибки. Эта ошибка возникает, когда предикатная функция успешно возвращает условие where, но при его добавлении к SQL-запросу внутри него обнаруживаются ошибки. Например, в том случае, когда возвращается условие where типа "x = 5", а таблица, с которой оно ассоциируется, не имеет столбца "x", буден получен код ошибки ORA-28113.
  • ORA-28106: входное значение аргумента #2 неверно. Эта ошибка возникает при обращении к dbms_session.set_context, если имя атрибута не является правильным идентификатором Oracle. Имена атрибутов контекста приложения должны быть правильными идентификаторами (т.е. их можно использовать для назначения имен столбцов таблиц или PL/SQL-переменных). Необходимо только изменить имя атрибута. Например, в приложении могут использоваться атрибуты 'SEL', 'INS', 'UPD' и 'DEL' вместо 'SELECT', 'INSERT' и так далее, потому что 'SELECT' не является правильным именем идентификатора Oracle.

При написании предикатных функций я часто пользуюсь одной утилитой - это пакет 'debug'. Этот пакет, автором которого является Кристофер Бек (Christopher Beck) из Oracle, позволяет вставить в код предложения команду 'print'. Кроме того, этот пакет позволяет широко использовать предложения типа:

create function foo :
as
   :
begin
     debug.f( 'Вход в процедуру foo' );
     if ( some_condition ) then
 l_predicate := 'x=1';
     end if;
   
     debug.f
  ( 'Переход к возврату предиката "%s"', l_predicate );
     return l_predicate;
end;

Таким образом, работа процедуры debug.f похожа на с-функцию printf, а сама она использует пакет UTL_FILE. На сервере базы данных она создает управляемые программистом файлы трассировки. Файлы трассировки содержат отладочные предложения, которые можно использовать для просмотра выполненных действий при выполнении кода. Так как программный код находится в ядре базы данных, отладка может оказаться сложной. Наличие файлов трассировки может сэкономить много времени. Скрипты, которые можно загрузить (см. далее в этом же разделе) содержат отладочный пакет и комментарии по его установке и использованию.

За и Против

Существует много За эту возможность и совсем немного Против. Фактически, сложно вообще найти хотя одно Против этой возможности. Как бы то ни было, они перечислены ниже:

За

Против

Упрощает разработку Приложения -переносит управление доступом из приложения на уровень данных.

Отладка может оказаться сложной, так как Детальный Контроль Доступа осуществляется в фоновом режиме. Для этой цели более подходят пакеты типа 'debug', о которых идет речь в секции диагностики и отладки.

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

 

Допускает значительные изменения политики безопасности без влияния на клиентские приложения.

 

Упрощает управление объектами базы данных. Уменьшается общее число объектов базы данных, необходимых для поддержки приложения.

 

Хорошо работает. Использование контекстов приложения позволяет воспользоваться преимуществами разделяемого SQL.

 



Литература по Oracle