В этой статье рассмотрены два новых механизма Oracle8i: Детальный Контроль Доступа (Fine Grained Access Control) и Контексты Защищенных Приложений (Secure Application Contexts)
Детальный контроль доступа и контексты приложения
Т.Кайт
В этой статье рассмотрены два новых механизма Oracle8i: Детальный Контроль Доступа (Fine Grained Access Control) и Контексты Защищенных Приложений (Secure Application Contexts). При совместном использовании они обеспечивают новые качественные возможности для обеспечения информационной безопасности базы данных.
Как и в других статях этого цикла, в этом опусе будут рассмотрены:
Для чего нужны новые возможности
Способы использования
Пример 1. Реализация политики безопасности
Важное предупреждение
Пример 2. Использование контекста приложения
Обработка ошибок и отладка
За и против
Демонстрационные скрипты
В различных изданиях Детальный Контроль Доступа может быть назван по-разному. Ниже перечислены его синонимы:
Детальный Контроль Доступа (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. |
|
Скрипты
Для того, чтобы получить все скрипты, используемые в этой статье, загрузите (zip-файл ). Пожалуйста, непременно прочитайте файл README.TXT, входящий в состав архива. Zip-файл можно открыть под Windows с помощью WinZip версии 6.0 и выше.
|