WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 17 | 18 || 20 | 21 |   ...   | 24 |

«Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...»

-- [ Страница 19 ] --

Теперь у нас есть Web и, как следствие, многоуровневая архитектура, когда, например, клиент (браузер) предоставляет свои реквизиты промежуточному серверу приложений, на котором выполняются компоненты JavaServer Page (JSP), обращающиеся к CORBA-объекту, который, в свою очередь, взаимодействует с базой данных. Реквизиты, передаваемые ПО промежуточного уровня, могут совпадать или не совпадать с именем пользователя и паролем, использовавшимся во времена клиент-серверной архитектуры, — это могут быть реквизиты, разрешающие доступ к службе каталогов (directory service), чтобы ПО промежуточного уровня могло выяснить, кто подключается и какими привилегиями доступа. Это могут быть реквизиты в виде сертификата X.509, включающего идентификационную информацию и привилегии. В любом случае ПО промежуточного уровня необязательно использует эти реквизиты для регистрации пользователя в базе данных. Клиент больше не взаимодействует с базой данных непосредственно;

между ними есть один, два или более промежуточных уровней. Можно, правда, заставить пользователя передавать регистрационное имя и пароль компонентам JSP, которые будут передавать их CORBA-объекту, а тот — серверу базы данных, но это не позволит использовать другие технологии и механизмы аутентификации, в особенности механизмы однократной регистрации. Рассмотрим следующий пример — достаточно типичное современное Web-приложение:

Многоуровневая аутентификация Клиент представляет собой Web-браузер, отображающий HTML-страницы и обращающийся к Web-серверу/серверу приложений по протоколу HTTP (1). Само приложение находится на Web-сервере/сервере приложений, например, в виде Java-сервлета, модуля сервера Apache и т.д. На представленной выше схеме промежуточный сервер использует службу каталогов (directory service), доступную по протоколу LDAP, которой и передаются предоставленные пользователем реквизиты (2). Служба каталогов используется как средство аутентификации сеанса браузера. Если аутентификация прошла успешно, сервер приложений получает соответствующее уведомление (3) и, наконец, подключается к одной из нескольких баз данных (4) для получения данных и обработки транзакций. "Реквизиты", передаваемые из браузера серверу приложений на шаге (1), могут быть разного вида — имя пользователя/пароль, ключ с сервера однократной регистрации, некий цифровой сертификат — все;

что угодно. Имя пользователя базы данных и его пароль, как правило, не передается. Проблема, конечно же, в том, что серверу приложений необходимо имя пользователя базы данных и пароль для аутентификации пользователя в используемой базе данных. Более того, комбинации имя пользователя/пароль в каждом случае будут разными. В рассмотренном выше примере имеется четыре базы данных: • база данных ошибок, в которой надо регистрироваться, скажем, как TKYTE;

• база данных затрат, в которой надо регистрироваться как TKYTE_US;

• база данных планировщика, в которой надо регистрироваться как WEB$TKYTE;

• и так далее... Подумайте: сколько у вас имен пользователей и паролей в разных базах данных? Я могу вспомнить не меньше 15. Более того, хотя имена пользователей в базах данных я никогда не меняю, пароли изменяются достаточно часто. Правда, хорошо было бы аутен Глава тифицироваться один раз — на сервере приложений — и обеспечить доступ сервера приложений ко всем необходимым базам данных от нашего имени (или по нашему заданию), не передавая пароли для каждой базы данных? Именно это и обеспечивает многоуровневая аутентификация. На уровне базы данных для этого достаточно задать опцию подключения. В Oracle 8i оператор ALTER USER был изменен и поддерживает конструкцию GRANT CONNECT THROUGH (подробнее она рассматривается далее, в разделе "Предоставление привилегии"). Рассмотрим доступ к базе данных затрат в представленном ранее приложении:

Служба каталогов содержит информацию сопоставления, связывающую пользователя TomKyte с клиентом базы данных TKYTE_US. После успешного получения этой информации сервер приложений (промежуточный сервер) может подключаться к базе данных со своими реквизитами от имени клиента базы данных, TKYTE_US. Серверу приложений при этом пароль пользователя TKYTE_US знать не надо. Чтобы это можно было сделать, администратор базы данных затрат должен предоставить учетной записи APP_SERVER право подключаться от имени клиента: alter user tykte_us grant connect through app_server Сервер приложений будет работать в базе данных от имени и с привилегиями пользователя TKYTE_US, как если бы пользователь TKYTE_US зарегистрировался в базе данных непосредственно. Таким образом, сервер Oracle 8i расширяет модель защиты настолько, что сервер приложений может безопасно работать от имени клиента, не требуя от него пароля соответствующей учетной записи в базе данных и не запрашивая многочисленные привилегии для доступа к объектам или процедурам, которые ему непосредственно не нужны. Кроме того, система проверки также была расширена и включает действия, выполняемые сервером приложений от имени клиента. Другими словами, мы можем узнать, выполнил ли сервер приложений определенное действие от имени клиента (подробнее об этом см. в разделе "Проверка промежуточных учетных записей").

Многоуровневая аутентификация Теперь перейдем к обсуждению реализации этих возможностей. Как упоминалось во введении, это средство в настоящее время* доступно только для программ на языках С и С++, использующих средства Oracle Call Interface.

Механизм многоуровневой аутентификации В этом разделе одна из стандартных демонстрационных программ OCI, реализующая мини-SQL*Plus, будет изменена для использования промежуточной аутентификации при подключении к базе данных. В результате получится небольшое, интерактивное средство выполнения SQL-операторов, которое позволит выяснить, как работает многоуровневая аутентификация и какие побочные эффекты при этом возникают. В качестве приза вы получите инструментальное средство, которое при наличии соответствующих привилегий позволит зарегистрироваться от имени другого пользователя и выполнять действия, регистрируемые системой проверки как ваши. Это средство можно, например, использовать для предоставления привилегии SELECT на таблицу другого пользователя, не зная его пароля. В демонстрационную программу cdemo2.c (которая находится в каталоге [ORACLE_HOME]\rdbms\demo) придется добавить только другую процедуру регистрации. После этого мы сможем зарегистрироваться как пользователь SCOTT, используя аутентификацию операционной системой, и предоставить роль CONNECT:

C:\> cdemo2 / s c o t t CONNECT Можно также, например, зарегистрироваться, указав имя пользователя и пароль в удаленной по отношению к учетной записи SCOTT базе данных, и предоставить роли RESOURCE и PLUSTRACE: C:\> cdemo2 user/pass@database scott RESOURCE,PLUSTRACE Для того чтобы показать, как регистрироваться с помощью механизма многоуровневой аутентификации, придется создать С-функцию — эту часть программы мы рассмотрим детально. Остальная же часть приложения — обычный OCI-код, ничем не отличающийся от любой программы, использующей библиотеку OCI. В начало кода включен стандартный заголовочный файл oci.h, находящийся в каталоге ORACLE_HOME]\rdbms\demo. Этот файл содержит необходимые прототипы функций и макросов для всех OCI-программ. Затем объявляются локальные переменные для подпрограммы регистрации. Используются обычные дескрипторы подключений OCI, но обратите внимание на два дескриптора OCISession: один — для учетной записи, реквизиты которой будут передаваться (от имени которой выполняется регистрация), а второй — для учетной записи, от имени которой мы будем работать. Назначение остальных локальных переменных понятно из имен — они содержат имя пользователя, пароль, имя базы данных и все роли, которые мы хотим предоставить: #include void checkerr(OCIError * errhp, sword status);

Глава Lda_Def connect8i(int argc, char * argv[]) { OCIEnv *environment_handle;

OCIServer *data_server_handle;

OCIError *error_handle;

OCISvcCtx *application_server_service_handle;

OCISession *first_client_session_handle;

OCISession *application_server_session_handle;

char char char char char char int *username;

*password;

*database;

temp[255];

role_buffer[1024];

*roles[255];

noe;

rls Проверим допустимость переданных функции аргументов командной строки. Если передано не четыре аргумента, значит, переданной информации недостаточно, т.е. просто выдается сообщение о правильном использовании, и работа завершается. В противном случае мы анализируем (с помощью стандартной С-функции strtok) переданные аргументы. Поскольку вызов strtok — деструктивный (он изменяет переданную функции строку), перед анализом аргументов мы копируем их в локальные переменные: if (argc != 4) { printf("usage: %s proxy_user/proxy_pass real_account_name rolel,|\n", argv[0]);

printf(" proxy_user/proxy_pass can just be / \ n " ) ;

printf(" real_account_name is what you want to connect to\n");

exit(l);

} strcpy(temp, argv[1]);

username = strtok(temp, " / " ) ;

password = strtok(NULL, " @ " ) ;

database = strtok(NULL, " " ) ;

strcpy( role_buffer, argv[3] );

for (nroles = 0, roles[nroles] = strtok(role_buffer,",");

roles[nroles] != NULL;

nroles++, roles[nroles] =strtok(NULL,","));

Теперь выполняем общую инициализацию и выделение контекстов. Это стандартные действия для всех ОСI-программ: OCIInitialize(OCI_DEFAULT, NOLL, NULL, NULL, NULL);

OCIEnvInit(&environment_handle, OCI_DEFAULT, 0, NULL);

OCIHandleAlloc((dvoid *) enviroranent_handle, (dvoid **) serror_handle, OCI_HTYPE_ERROR, 0, NULL);

Затем выделяем и инициализируем контексты сервера и службы, используемые "сервером приложений". В данном случае сервером приложений является демонстрацион Многоуровневая аутентификация ная программа cdemo2 — реализация SQL*Plus в миниатюре. Этот код выполняет подключение к серверу, но не начинает сеанс: checkerr(error_handle, OCIHandleAlloc(environment_handle, (dvoid **)sdata_server_handIe, OCI_HTYPE_SERVER, 0, NULL) );

checkerr(error_handle, OCIHandleAlloc((dvoid*) environment_handle, (dvoid **) sapplication_server_service_handle, OCI_HTYPE_SVCCTX, 0, NULL) );

checkerr(error_handle, OCIServerAttach(data_server_handle, error_handle, (text *)database?database:"", strlen(database?database:""), 0) );

checkerr(error_handle, OCIAttrSet((dvoid *) application_server_service_handle, OCI_HTYPE_SVCCTX, (dvoid *) data_server_handle, (ub4) 0, OCI_ATTR_SERVER, error_handle) );

Теперь можно инициализировать, а затем аутентифицировать дескриптор сеанса сервера приложений. В данном случае используется либо внешняя аутентификация, либо имя пользователя/пароль: checkerr(error_handle, OCIHandleAlloc(

Инициализировав дескриптор сеанса, мы должны передать информацию для аутентификации. Можно разрешить либо аутентификацию операционной системой (которая не потребует от сервера приложений передавать имена пользователей и пароли на сервер), либо стандартную аутентификацию по имени пользователя и паролю. Вот код для аутентификации по имени пользователя и паролю: if (username != NULL && password != NULL && *username && *password) < checkerr(error_handle, OCIAttrSet((dvoid *) application_server_session_handle, Глава (ub4) OCI_HTYPE_SESSION, (dvoid *) username, (ub4) strlen((char *)username), (ub4) OCI_ATTR_USERNAME, error_handle) );

checkerr( error_handle, OCIAttrSet((dvoid *) application_server_session_handle, (ub4) OCI_HTYPE_SESSION, (dvoid *) password, (ub4) strlen((char *)password), (Ub4) OCI_ATTR_PASSWORD, error_handle) );

checkerr(error_handle, OCISessionBegin (application_server_service_handle, error_handle, application_server_session_handle, OCI_CRED_RDBMS, (ub4) OCI_DEFAULT) );

} А это — для выполнения аутентификации операционной системой:

else { checkerr(error_handdle, OCISessionBegin(application_server_service_handJ_e, error_handle, application_server_session_handle, OCI_CRED_EXT, OCI_DEFAULT) );

} Теперь все готово для инициализации сеанса от имени клиента (пользователя, который регистрируется на сервере приложений, и от имени которого нам доверяют выполнять действия). Сначала инициализируем сеанс: checkerr (error_handle, OCIHandleAlloc((dvoid *) environment_handle, (dvoid **)&first_client_session_handle, (ub4) OCI_HTYPE_SESSION, (size_t) 0, (dvoid **) 0) );

Потом устанавливаем имя пользователя, от имени которого этот сеанс будет работать: checkerr (error_handle, OCIAttrSet((dvoid *) first_client_session_handle, (ub4) OCI_HTYPE_SESSION, (dvoid *) argv[2], Многоуровневая аутентификация (ub4) strlen(argv[2]), OCI_ATTR_USERNAME, error_handle) );

Затем добавляем список ролей, которые необходимо включить для данного сеанса, — если пропустить этот вызов, будут включены все стандартные роли соответствующего пользователя: checkerr( error_handle, OCIAttrSet((dvoid *) first_client_session_handle, (ub4) OCI_HTYPE_SESSION, (dvoid *) roles, (ub4) nroles, OCI_ATTR_INITIAL_CLIENT_ROLES, error_handle) );

Теперь все готово для начала реального сеанса. Сначала мы свяжем клиентский сеанс (от имени которого должны выполняться действия в базе данных) с сеансом сервера приложений (промежуточной учетной записью): checkerr(error_handle, OCIAttrSet((dvoid *) first_client_session_handle, (ub4) OCI_HTYPE_SESSION, (dvoid *) application_server_session_handle, (ub4) 0, CCI_ATTR_PROXY_CREDENTIALS, error_handle) );

checkerr(error_handle, OCIAttrSet((dvoid *)application_server_service_handle, (ub4) OCI_HTYPE_SVCCTX, (dvoid *)first_client_session_handle, (ub4)0, (ub4)OCI_ATTR_SESSION, error_handle) );

A затем начнем сеанс:

checkerr(error_handle, OCISessionBegin(application_server_service_handle, error_handle, first_client_session_handle, OCI_CRED_PROXY, OCI_DEFAULT) );

Теперь, поскольку это OCI-программа версии 7 (cdemo2.c — программа версии 7), необходимо преобразовать регистрационные данные Oracle 8i в форму, которую можно будет использовать. Здесь будет преобразована информация о подключении версии 8 в OCI LDA (Login Data Area — область данных регистрации) версии 7 и возвращен результат:

Глава checkerr(error_handle, OCISvcCtxToLda(application_server_service_handle, error_handle, &lda ) );

return lda;

} Последняя часть кода — функция checkerr, которую мы многократно использовали. Эта функция проверяет, что коды возврата OCI-функций свидетельствуют об успешном выполнении — в противном случае она выдает сообщение об ошибке и завершает работу программы: void checkerr(OCIError * errhp, sword status) { text errbuf[512];

sb4 errcode = 0;

switch (status) { case OCI_SUCCESS: break;

case OCI_SUCCESS_WITH_INFO: (void) printf("Ошибка - OCI_SUCCESS_WITH_INFO\n");

break;

case OCI_NEED_DATA: (void) p r i n t f ( " О ш и б к а - OCI_NEED_DATA\n");

break;

c a s e OCI_NO_DATA:. (void) p r i n t f ( " О ш и б к а - OCI_NODATA\n");

break;

case OCI_ERROR: (void) OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR);

(void) p r i n t f ( " О ш и б к а - %. * s \ n ", 512, e r r b u f ) ;

exit(l);

break;

case OCI_INVALID_HANDLE: (void) printf("Ошибка - OCI_INVALID_HANDLE\n");

break;

case OCI_STILL_EXECUTING: (void) p r i n t f ( " О ш и б к а - OCI_STILL_EXECUTE\n");

break;

case OCI_CONTINUE: (void) printf("Ошибка - OCI_CONTINUE\n");

break;

default: break;

} } Теперь осталось изменить файл cdemo2.c и включить в него необходимый код. Существующий код этой демонстрационной программы имеет вид:

Многоуровневая аутентификация static sword numwidth = 8;

main() { sword col, errno, n, ncols;

text *cp;

/* Подключаемся к ORACLE. */ if (connect_user()) exit(-l);

Изменение очень несложное — добавить код, выделенный полужирным: static sword numwidth = 8;

Lda_Def connect8i(int argc, char * argv[]);

main(int argc, char * argv[]) { sword col, errno, n, ncols;

text *cp;

/* Подключаемся к ORACLE. */ /* if (connect_user()) exit(-l);

Ida = connect8i( argc/ argv );

Затем надо добавить весь представленный ранее код (функции connect8i и checkerr) в конец файла исходного кода. Сохраняем файл и компилируем. В ОС UNIX для компиляции надо выполнить следующую команду: $ make -f $ORACLE_HOME/rdbms/demo/demo_rdbms.mk cdemo2 В среде Windows NT я использовал следующий файл управления проектом makefile: CPU=i386 WORK_DIR =.\ # # каталог, в который будут помещаться все !include <\msdev\include\win32.mak> OBJDIR = $(WORK_DIR)\ EXEDIR = $(WORK_DIR)\ ->.ехе-модули ORACLE_HOME = \oracle TARGET */ = $(EXEDIR)cdemo2.exe = cdemo2.obj SAMPLEOBJS LOCAL_DEFINE = -DWIN_NT SYSLIBS = \msdev\lib\msvcrt.lib \ \msdev\lib\oldnames.lib \ \msdev\lib\kernel32.lib \ Глава \msdev\lib\advapi32.lib \msdev\lib\wsock32.lib NTUSER32LIBS \ SQLLIB INCLS = \msdev\lib\user32.1ib \ \msdev\lib\advapi32.1ib \ \msdev\lib\libc.lib = $(ORACLE_HOME)\oci\lib\msvc\oci.lib = -I\msdev\include \ -I$(ORACLE_HOME)\oci\include CFLAGS = $(cdebug) $(cflags) $(INCLS) $(LOCAL_DEFINE) LINKOPT = /nologo /subsystem:console /machine:I386 /nodefaultlib $(TARGET): $(SAMPLEOBJS) $(SQLLIB) $(link) $(LINKOPT) \ -out:$(TARGET) $(SAMPLEOBJS) \ $(NTUSER32LIBS) \ $(SYSLIBS) \ $(SQLLIB) A затем просто выполнял для компиляции команду nmake: с:\oracle\rdbms\demo>nmake Теперь можно проверять работу программы: c:\oracle\rdbms\demo>cdemo2 tkyte/tkyte scott connect,resource Ошибка — ORA-28150: proxy not authorized to connect as client Итак, пока еще не работает, но до успешного выполнения осталось совсем немного. Необходимо предоставить промежуточной учетной записи (TKYTE) право подключаться от имени клиента базы данных (SCOTT). Регистрируемся в SQL*Plus и выполняем: sys@TKYTE816> alter user scott grant connect through tkyte;

User altered. Подробно этот новый оператор со всеми опциями мы рассмотрим немного позже. Пока нам просто надо убедиться, что программа работает. Обратите внимание на выделенное полужирным приглашение — я работаю не в среде SQL*Plus. Это работает демонстрационная программа cdemo2, очень похожая на утилиту SQL*Plus: c:\oracle\rdbms\demo>cdemo2 tkyte/tkyte scott connect,resource OCISQL> SELECT user, substr(sys_context('userenv','proxy_user'),l,30) 2 FROM dual;

USER SCOTT 1 row processed. OCISQL> select * from session_roles;

ROLE CONNECT RESOURCE 2 rows processed. SUBSTR(SYS_CONTEXT('USERENV','PRO TKYTE Многоуровневая аутентификация OCISQL> s e l e c t d i s t i n c t authentication_type from v $ s e s s i o n _ c o n n e c t _ i n f o 2 where s i d = ( s e l e c t s i d from v$mystat where rownum = 1 ) ;

AUTHENTICATION_TYPE PROXY 1 row processed. OCISQL> exit C:\oracle\RDBMS\demo> Вот и все. Мы успешно зарегистрировались от имени пользователя SCOTT, не зная его пароля. Кроме того, мы увидели, как можно проверить, что мы работаем через промежуточную учетную запись, сравнивая значение USER c атрибутом PROXY_USER, возвращаемым функцией SYS_CONTEXT, или просматривая информацию сеанса в представлении V$SESSION_CONNECT_INFO. Кроме того, явно видно, что включены роли CONNECT и RESOURCE. Если подключиться следующим образом: c:\oracle\rdbms\demo>cdemo2 tkyte/tkyte scott connect OCISQL> select * from session_roles;

ROLE CONNECT 1 row processed. Видно, что включена роль CONNECT, — мы контролируем, какие роли включает "сервер приложений".

Предоставление привилегии Соответствующий оператор ALTER USER имеет следующий базовый синтаксис: Alter user <имя пользователя> grant connect through <промежуточный пользователь><, промежуточный пользователь>... Это дает возможность пользователям, перечисленным в списке промежуточных пользователей, подключаться от имени указанного после ALTER USER пользователя. По умолчанию для этих пользователей будут устанавливаться все роли данного пользователя. Другая разновидность этого оператора: Alter user <имя пользователя> grant connect through <промежуточный пользователь> WITH NONE;

позволяет промежуточной учетной записи подключаться от имени указанного пользователя, но только с ее базовыми привилегиями —роли включаться не будут. Кроме того, можно использовать: Alter user <имя пользователя> grant connect through <промежуточный пользователь> ROLE имя_роли,имя_роли,... или: Alter user <имя пользователя> grant connect through <промежуточный пользователь> ROLE ALL EXCEPT имя_роли,имя_роли,...

Глава Два представленных выше оператора дают промежуточной учетной записи возможность подключаться в качестве пользователя, но при этом включены будут только определенные роли. Необязательно давать учетной записи сервера приложений все привилегии — достаточно предоставить роли, необходимые для выполнения его функций. По умолчанию сервер Oracle пытается включить все стандартные роли пользователя и роли PUBLIC. Вполне допустимо разрешить серверу приложений использовать только роль HR данного пользователя, и никакие другие прикладные роли этого пользователя. Разумеется, можно и отобрать соответствующую привилегию: Alter user <имя пользователя> REVOKE connect through <промежуточный пользователь><,промежуточный пользователь>... Есть административное представление, PROXY_USERS, которое можно использовать для получения информации обо всех промежуточных учетных записях. После выполнения оператора ALTER user SCOTT GRANT CONNECT through tkyte в представлении PROXY_USERS будет: TKYTE@TKYTE816> select * from proxy_users;

PROXY TKYTE CLIENT SCOTT ROLE FLAGS PROXY MAY ACTIVATE ALL CLIENT ROLES Проверка промежуточных учетных записей Вот новый синтаксис оператора AUDIT для работы с промежуточными учетными записями:

AUDIT <действие> BY <промежуточный пользователь>, <промежуточный пользователь>... ON BEHALF OF <клиент>, <клиент>.. ;

или: AUDIT <действие> BY <промежуточный пользователь>, <промежуточный пользователь> ON BEHALF OF ANY;

Новая часть — BY <промежуточный пользователь>... ON BEHALF OF. Она позволяет явно проверять действия, выполняемые указанными промежуточными пользователями от имени некоторых или всех учетных записей. Предположим, администратор включил проверку, установив параметр инициализации AUDIT_TRAIL=TRUE и перезапустив экземпляр. Тогда можно использовать: sys@TKYTE816> audit connect by tkyte on behalf of scott;

Audit succeeded.

Теперь, если использовать для подключения измененную программу cdemo2.c:

C:\oracle\RDBMS\demo>cdemo2 tkyte/tkyte scott connect OCISQL> exit В таблице DBA_AUDIT_TRAIL я обнаружу следующее:

Многоуровневая аутентификация OS_USERNAME USERNAME USERHOST TERMINAL TIMESTAMP OWNER OBJ_NAME ACTION ACTION_NAME NEW_OWNER NEW_NAME OBJ_PRIVILEGE SYS_PRIVILEGE ADMIN_OPTION GRANTEE AUDIT_OPTION SES_ACTIONS LOGOFF_TIME LOGOFF_LREAD LOGOFF_PREAD LOGOFF_LWRITE LOGOFF_DLOCK COMMENT_TEXT SESSIONID ENTRYID STATEMENTID RETORNCODE PRIV USED : : : : : : : : : : : : : : : : : : : : : : : : : : : Thomas?Kyte SCOTT TKYTE-DELL 08-may-2001 19:19:29 101 LOGOFF : 08-may-2001 1 9 : 1 9 : 3 0 23 0 6 0 A u t h e n t i c a t e d b y : PROXY: TKYTE 8234 1 1 0 CREATE SESSION Интересно отметить, что при подключении пользователя SCOTT или пользователя TKYTE с помощью утилиты SQL*Plus записи проверки не создаются. Проверка строго ограничена конструкцией: connect by tkyte on behalf of s c o t t ;

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

Проблемы Обычно многоуровневая аутентификация работает вполне предсказуемо. Если подключиться следующим образом: C:\oracle\RDBMS\demo>cdemo2 tkyte/tkyte s c o t t connect Это будет равносильно непосредственной регистрации пользователя SCOTT. Средства обеспечения работы с правами вызывающего и правами создателя (см. главу 23) функционируют так, как если бы зарегистрировался пользователь SCOTT. Средства тща Глава тельного контроля доступа (см. главу 21) функционируют так, как если бы зарегистрировался пользователь SCOTT. Триггеры на событие регистрации пользователя SCOTT срабатывают. И так далее. Я не нашел ни одной возможности, которая была бы недоступна при использовании многоуровневой аутентификации. Есть, однако, одна деталь реализации, которая может привести к проблемам. При использовании многоуровневой аутентификации сервер будет автоматически включать набор ролей. Если вы используете атрибут OCI_ATTR_INITIAL_CLIENT_ROLES, как в представленном выше коде, то ожидаете, что в набор войдут только явно заданные роли. Однако всегда включаются также все роли, предоставленные роли PUBLIC. Например, если предоставить роль PLUSTRACE роли PUBLIC (роль PLUSTRACE позволяла использовать установку AUTOTRACE, которую мы постоянно применяли по ходу изложения в среде SQL*Plus для оценки производительности): sys@TKYTE816> grant plustrace to publicGrant succeeded. Теперь при подключении с помощью нашей утилиты мини-SQL*Plus: c:\oracle\rdbms\demo>cdemo2 tkyte/tkyte scott connect OCISQL> select * from session_roles;

ROLE CONNECT PLUSTRACE 2 rows processed. оказывается, что кроме роли CONNECT включена также роль PLUSTRACE. Сначала это не кажется опасным. Однако, если с помощью оператора ALTER USER явно потребовать предоставлять пользователю только строго ограниченный набор ролей: sys@TKYTE816> alter user scott grant connect through tkyte with role -> CONNECT;

User altered. окажется, что: c:\oracle\rdbms\demo>cdemo2 tkyte/tkyte scott connect Ошибка - ORA-28156: Proxy user 'TKYTE' not authorized to set role 'PLUSTRACE' for client 'SCOTT' пользователю TKYTE не разрешено включать эту роль при подключении от имени пользователя SCOTT. Решить эту проблему можно только так: 1. не предоставлять ролей роли PUBLIC;

2. всегда добавлять соответствующие роли к списку ролей в операторе ALTER USER Например, если выполнить: sys@TKYTE816> alter user scott grant connect through tkyte with role 2 connect, plustrace;

User altered.

Многоуровневая аутентификация следующая команда сработает, как и предполагалось: c:\oracle\rdbms\demo>cdemo2 tkyte/tkyte scott connect OCISQL> select * from session_roles;

ROLE CONNECT PLUSTRACE Резюме В этой главе мы изучили возможности многоуровневой, или промежуточной, аутентификации, которые доступны при программировании с использованием библиотеки OCI. Многоуровневая аутентификация позволяет серверу приложений промежуточного уровня действовать в качестве пользующегося доверием агента в базе данных от имени известного приложению клиента. Мы рассмотрели, как сервер Oracle позволяет ограничить набор ролей, доступных промежуточной учетной записи сервера приложений, чтобы от имени промежуточной учетной записи можно было выполнять только ограниченный набор действий, необходимых приложению. Далее мы рассмотрели систему проверки, которая теперь поддерживает использование многоуровневой аутентификации. Можно регистрировать действия, выполняемые промежуточными учетными записями от имени любого указанного пользователя или от имени всех пользователей. При этом всегда легко определить, когда данный пользователь выполнил действие через промежуточную учетную запись, а когда — непосредственно. Изменив одну демонстрационную программу Oracle, мы получили простую среду, напоминающую SQL*Plus, для тестирования возможностей многоуровневой аутентификации. Эта среда идеально подходит для тестирования различных особенностей многоуровневой аутентификации и позволяет изучать их в интерактивном режиме.

Права вызывающего и создателя Для начала представим ряд определений, чтобы гарантировать однозначное понимание терминов вызывающий и создатель: • Создатель. Пользователь, создавший скомпилированный хранимый объект и владеющий им. (Говорят также, что объект находится в схеме пользователя.) К скомпилированным хранимым объектам относятся пакеты, процедуры, функции, триггеры и представления, • Вызывающий. Пользователь, с привилегиями которого работает текущий сеанс. Это может быть текущий зарегистрированный пользователь, но может быть и другой пользователь. До версии Oracle 8i все скомпилированные хранимые объекты выполнялись с правами создателя объекта. По отношению к соответствующей схеме происходило и разрешение имен. Другими словами, набор привилегий, непосредственно предоставленных владельцу (создателю) объекта использовался при компиляции для определения того: • к каким объектам (таблицам и т.д.) фактически обращаться;

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

Глава Начиная с Oracle 8i появилась возможность выполнять процедуры с правами вызывающего, что позволяет создавать процедуры, функции и пакеты, выполняющиеся с набором привилегий вызывающего, а не создателя. В этой главе мы рассмотрим: • когда следует использовать процедуры с правами вызывающего, в том числе, для создания приложений, работающих со словарем данных, универсальных объектных типов и реализации собственных средств контроля доступа;

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

• как работают хранимые процедуры этих двух типов;

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

Пример Возможность выполнять код с правами вызывающего позволяет, например, создать хранимую процедуру, работающую с набором привилегий пользователя, который ее выполняет. В результате хранимая процедура может работать правильно и корректно для одного пользователя (который имеет доступ ко всем необходимым объектам), но не работать для другого (у которого такого доступа нет). Причина состоит в том, что доступ к базовым объектам проверяется не во время компиляции, а во время выполнения (правда, создатель должен иметь доступ к соответствующим объектам или хотя бы к объектам с такими же именами, чтобы PL/SQL-код вообще можно было скомпилировать). Подобный доступ во время выполнения осуществляется с учетом привилегий и ролей текущего пользователя/схемы. Следует отметить, что работа с правами вызывающего не поддерживается для представлений и триггеров. Представления и триггеры создаются и работают только с правами создателя. Возможность работы с правами вызывающего легко реализовать и проверить, поскольку для этого необходимо добавить к процедуре или пакету всего одну строку. Рассмотрим, например, следующую процедуру, выдающую значения атрибутов контекста: • CURRENT_USER. Имя пользователя, с привилегиями которого работает сеанс. • SESSION_USER. Имя пользователя, зарегистрировавшегося и первоначально создавшего этот сеанс. Это значение в течение сеанса неизменно. • CURRENT_SCHEMA. Имя стандартной схемы, которая будет использоваться при разрешении неуточненных ссылок на объекты. Для создания процедуры, работающей с правами создателя, необходим следующий код: tkyte@TKYTE816> create or replace procedure definer_proc 2 as Права вызывающего и создателя 3 begin 4 for x in 5 (select sys_context('userenv', 'current_user') current_user, 6 sys_context('userenv', 'session_user') session_user, 7 sys_context('userenv', 'current_schema') current_schema 8 from dual) 9 loop 10 dbms_output.put_line('CurrentUser: ' || x.current_user);

11 dbms_output.put_line('SessionUser: ' || x.session_user);

12 dbms_output.put_line('Current Schema: ' || x.current_schema);

13 end loop;

14 end;

15 / Procedure created. tkyte@TKYTE816> grant execute on definer_proc to scott;

Grant succeeded. Для создания такой же процедуры, работающей с правами вызывающего, код надо немного изменить: tkyte@TKYTE816> create or replace procedure invoker_proc 2 AUTHID CURRENT_USER 3 as 4 begin 5 for x in 6 (select sys_context('userenv', 'current_user') current_user, 7 sys_context('userenv', 'session_user') session_user, 8 sys_context('userenv', 'current_schema') current_schema 9 from dual) 10 loop 11 dbms_output.put_line('Current User: ' || x.current_user);

12 dbms_output.put_line('SessionUser: ' || x.session_user);

13 dbms_output.put_line('Current Schema: ' || x.current_schema);

14 end loop;

15 end;

16 / Procedure created. tkyte@TKYTE816> grant execute on invoker_proc to scott;

Grant succeeded. Вот и все;

добавили одну строку, и процедура теперь будет выполняться с привилегиями и в пространстве имен вызывающего пользователя, а не создателя. Чтобы глубже понять, что это означает, выполним представленные выше процедуры и сравним выдаваемые ими результаты. Сначала процедура, работающая с правами создателя: tkyte@TKYTE816> connect scott/tiger scott@TKYTE816> Current User: Session User: Current Schema: exec tkyte.definer_proc TKYTE SCOTT TKYTE PL/SQL procedure successfully completed.

Глава Внутри процедуры, работающей с правами создателя, текущий пользователь и схема, с привилегиями которой работает сеанс — TKYTE. Пользователь, зарегистрировавшийся и начавший сеанс — SCOTT. Это значение в течение сеанса не меняется. При этом все неуточненные ссылки будут разрешаться в схеме TKYTE (например, запрос SELECT * FROM T будет разрешаться как SELECT * FROM TKYTE.T). Процедура, работающая с правами вызывающего, дает совсем другие результаты: scott@TKYTE816> exec tkyte.invoker_proc Current User: SCOTT Session User: SCOTT Current Schema: SCOTT PL/SQL procedure successfully completed. Текущий пользователь — SCOTT, а не TKYTE. Текущий пользователь в такой процедуре совпадает с пользователем, непосредственно выполняющим эту процедуру. Пользователь, зарегистрировавшийся и начавший сеанс — SCOTT, как и ожидалось. Текущая схема, однако, — тоже SCOTT, поэтому запрос SELECT * FROM T будет выполняться как SELECT * FROM SCOTT.T. Это показывает фундаментальное отличие процедур, работающих с правами вызывающего: процедура работает с правами пользователя, который ее вызвал. Кроме того, текущая схема также зависит от вызывающего. При выполнении этой процедуры разными пользователями она может обращаться к различным объектам. Интересно, как повлияет на эти процедуры явное изменение текущей схемы: scott@TKYTE816> alter session set current_schema - system;

Session altered. scott@TKYTE816> exec tkyte.definer_proc Current User: TKYTE Session User: SCOTT Current Schema: TKYTE PL/SQL procedure successfully completed. scott@TKYTE816> exec tkyte.invoker_proc Current User: SCOTT Session User: SCOTT Current Schema: SYSTEM PL/SQL procedure successfully completed. Как видите, результаты выполнения процедуры с правами создателя не изменились. В таких процедурах текущий пользователь и текущая схема "статичны". Они жестко задаются при компиляции и не меняются в дальнейшем при изменении текущей среды. Процедура, работающая с правами вызывающего, более динамична. Текущий пользователь устанавливается во время выполнения, а текущая схема может меняться для каждого вызова, даже в пределах одного сеанса. Это очень мощное средство (при правильном и уместном использовании). Оно позволяет реализовать для хранимых процедур и пакетов PL/SQL поведение, более свойственное приложениям, написанным на Pro*C. Приложение, использующее Рго*С(или интерфейсы ODBC, JDBC — в общем, любое клиентское приложение на обычных про Права вызывающего и создателя цедурных языках программирования), выполняется с привилегиями текущего зарегистрированного пользователя (вызывающего) и разрешением имен в его схеме. Теперь можно писать на PL/SQL код, который ранее приходилось писать на обычных языках программирования вне базы данных.

Когда использовать права вызывающего В этом разделе мы рассмотрим различные причины и случаи, когда может потребоваться выполнение с правами вызывающего. Мы сконцентрируемся на правах вызывающего, поскольку это новая возможность, пока еще являющаяся исключением. Хранимые процедуры ранее всегда выполнялись сервером Oracle c правами создателя. Необходимость работать с правами вызывающего чаще всего возникает, когда универсальный фрагмент кода создается одним пользователем, а используется — множеством других. Разработчик не имеет доступа к объектам, к которым будут иметь доступ пользователи. Именно привилегии пользователя будут определять, к каким объектам этот код может обращаться. Другая потенциальная причина использования прав вызывающего — необходимость создать набор процедур, централизованно выбирающих данные из нескольких различных схем. При использовании процедур, работающих с правами создателя, как было показано, привилегии и схема, относительно которой выполняется разрешение имен, — статичны и определяются во время компиляции. При каждом выполнении процедура, работающая с правами создателя, обращается к одному и тому же набору объектов (если, конечно, не используется динамическое формирование SQLоператоров). Работа с правами вызывающего позволяет создать процедуру, способную обращаться к аналогичным структурам в различных схемах, — в зависимости от того, кто ее вызвал. Давайте рассмотрим ряд типичных случаев, когда используются процедуры с правами вызывающего.

Разработка универсальных утилит Пусть создается хранимая процедура, использующая динамический SQL для выполнения запроса и выдачи результатов в виде файла со значениями через запятую. Если не работать с правами вызывающего, эта процедура будет универсальной и полезной всем только при выполнении одного из следующих условий. • Создатель процедуры должен иметь возможность читать любой объект в базе данных. Например, обладать привилегией SELECT ANY TABLE. B противном случае при запуске этой процедуры для представления данных таблицы в виде текстового файла произойдет ошибка, потому что создатель не имеет необходимой привилегии SELECT для этой таблицы. Надо выполнять эту процедуру с правами вызывающего, а не создателя. • Каждый пользователь должен иметь исходный код и устанавливать его копию в своей схеме. Это нежелательно по очевидным причинам — сопровождение превратится в кошмар. Если будет обнаружена ошибка в исходном коде или вследствие обновления версии придется изменять код, обновлять придется десятки Глава копий. Кроме того, эта скопированная процедура все равно не сможет обращаться к объектам, доступным пользователю через роль. Обычно именно второй вариант чаще всего применяется для разработки универсального кода. Этот подход неидеален, но более "безопасен" с точки зрения защиты данных. Используя работу с правами вызывающего, можно создать процедуру один раз, предоставить права на ее выполнение многим пользователям, и они будут использовать ее со своим набором привилегий и с разрешением имен в своих схемах. Давайте рассмотрим небольшой пример. Мне часто приходится просматривать в среде SQL*Plus слишком "широкие" таблицы, имеющие много столбцов. Если просто выполнить SELECT * FROM T для такой таблицы, утилита SQL*Plus будет переносить данные на следующую строку по правому краю окна терминала. Например: tkyte@DEV816> select * from dba_tablespaces where rownum = 1;

TABLESPACE_NAME INITIAL_EXTENT NEXT_EXTENT MIN_EXTENTS CONTENTS LOGGING MAX_EXTENTS PCT_INCREASE MIN_EXTLEN STATUS EXTENT_MAN ALLOCATIO PLU SYSTEM 505 DICTIONARY USER 50 NO 16384 16384 0 ONLINE PERMANENT LOGGING Полученные данные читать очень неудобно. Вот если бы получать результаты в следующем виде: tkyte@DEV816> exec print_table('select * from dba_tablespaces where -> rownum = 1') ;

TABLESPACE_NAME INITIAL_EXTENT NEXT_EXTENT MIN_EXTENTS MAX_EXTENTS PCT_INCREASE MIN_EXTLEN STATUS CONTENTS LOGGING EXTENT_MANAGEMENT ALLOCATION_TYPE PLUGGED_IN : : : : : : : : : : : : : SYSTEM 16384 16384 1 505 50 0 ONLINE PERMANENT LOGGING DICTIONARY USER NO PL/SQL procedure successfully completed. Да, так гораздо лучше! Легко увидеть значение каждого столбца. Увидев результаты использования моей процедуры PRINT_TABLE, все хотят получить ее. Вместо того чтобы давать код, я предлагаю использовать мою, поскольку она создана с конструкцией AUTHID CURRENT_USER. Мне не нужен доступ к чужим таблицам. Эта процедура сможет обращаться к ним (даже к тем, которые доступны через роль, — процедуры, Права вызывающего и создателя работающие с правами создателя, не могут этого делать в принципе). Давайте рассмотрим код и разберемся, как он устроен. Начнем с создания служебной учетной записи для хранения этого универсального кода, а также учетной записи, которую мы будем использовать для проверки зашиты: tkyte@TKYTE816> grant connect to another_user identified by another_user;

Grant succeeded. tkyte@TKYTE816> create user utils_acct identified by utils_acct;

User created. tkyte@TKYTE816> grant create session, create procedure to utils_acct;

Grant succeeded. Я создал пользователя с очень ограниченными привилегиями. Их достаточно для того, чтобы зарегистрироваться и создать процедуру. Теперь я создам процедуру в этой схеме: tkyte@TKYTE816> utils_acct/utils_acct utils_acct@TKYTE816> create or replace 2 procedure print_table(p_query in varchar2) 3 AUTHID CURRENT_USER 4 is 5 l_theCursor integer default dbms_sql.open_cursor;

6 l_columnValue varchar2(4000);

7 l_status integer;

8 l_descTbl dbms_sql.desc_tab;

9 l_colCnt number;

10 begin 11 dbms_sql.parse(l_theCursor, p_query, dbms_sql.native);

12 dbms_sql.describe_columns(l_theCursor, l_colCnt, l_descTbl);

13 14 for i in 1.. l_colCnt loop 15 dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);

16 end loop;

17 18 l_status :=dbms_sql.execute(l_theCursor);

19 20 while (dbms_sql.fetch_rows(l_theCursor) > 0 ) loop 21 for i in 1.. l_colCnt loop 22 dbms_sql.column_value(l_theCursor, i, l_columnValue);

23 dbms_output.put_line(rpad(l_descTbl(i).col_name, 30) 24 || ': ' || 25 l_columnValue);

26 end loop;

27 dbms_output.put_line(' •) ;

28 end loop;

29 exception 30 when others then 31 dbms_sql.dose_cursor(l_theCursor);

32 RAISE;

33 end;

Глава 34 / Procedure created. utils_acct@TKYTE816> grant execute on print_table to public;

Grant succeeded. Теперь я пойду на шаг дальше: сделаю так, чтобы от имени учетной записи UTILS_ACCT зарегистрироваться вообще было невозможно. Это предотвратит подбор пользователем пароля учетной записи UTILS_ACCT и размещение "троянского" кода под видом процедуры PRINT_TABLE. Конечно, администратор базы данных с соответствующими привилегиями сможет снова активизировать эту учетную запись и зарегистрироваться от имени UTILS_ACCT — этого предотвратить нельзя: utils_acct@TKYTE816> connect tkyte/tkyte tkyte@TKYTE816> revoke create session, create procedure 2 from utils_acct;

Revoke succeeded. Итак, имеется учетная запись, в схеме которой хранится код, но она по сути заблокирована, поскольку больше не имеет привилегии CREATE SESSION. При регистрации от имени пользователя SCOTT оказывается, что мы не только по-прежнему можем использовать эту процедуру (хотя учетная запись UTILS_ACCT лишена всех привилегий), но и обращаться к своим таблицам. Убедимся, что другие пользователи не могут использовать эту процедуру для доступа к нашим таблицам (если только они не сделают это непосредственно, с помощью запроса), т.е. продемонстрируем, что процедура работает с привилегиями вызывающего: scott@TKYTE816> exec utils_acct.print_table('select * from scott.dept') DEPTNO : 10 DNAME : ACCOUNTING LOC : NEW YORK PL/SQL procedure successfully completed. Это показывает, что пользователь SCOTT может использовать процедуру, и она имеет доступ к объектам в схеме пользователя SCOTT. Однако при выполнении от имени пользователя ANOTHER_USER обнаруживается следующее: scott@TKYTE816> connect another_user/another_user another_user@TKYTE816> desc scott.dept ERROR: ORA-04043: object scott.dept does not exist another_user@TKYTE816> set serverout on another_user@TKYTE816> exec utils_acct.print_table('select * from -> scott.dept');

BEGIN utils_acct.print_table('select * from scott.dept');

END;

* ERROR at line 1: ORA-00942: table or view does not exist Права вызывающего и создателя ORA-06512: at "UTILS_ACCT.PRINT_TABLE", line 31 ORA-06512: at line 1 Пользователь, не имеющий доступа к таблицам пользователя SCOTT, не сможет использовать процедуру для получения доступа к ним. Для полноты эксперимента снова зарегистрируемся как SCOTT и предоставим пользователю ANOTHER_USER соответствующую привилегию: another_user@TKYTE816> connect scott/tiger scott@TKYTE816> grant select on dept to another_user;

Grant succeeded. scott@TKYTE816> connect another_user/another_user another_user@TKYTE816> exec utils_acct.print_table('select * from scott.dept');

DEPTNO : 10 DNAME : ACCOUNTING LOC : NEW YORK PL/SQL procedure successfully completed. Это демонстрирует практическое использование прав вызывающего в универсальных приложениях.

Приложения, работающие со словарем данных Разработчикам всегда хотелось создать процедуры, выдающие информацию из словаря данных в более удобном виде, чем можно получить с помощью простого оператора SELECT, или, например, средства получения операторов ЯОД. С помощью процедур, работающих с правами создателя, сделать это было очень сложно. Если используются представления USER_* (например, USER_TABLES), будет выдаваться информация об объектах, принадлежащих создателю процедуры, а не вызывающему. Дело в том, что в условиях всех представлений USER_* и ALL_* есть конструкция: where o.owner# = userenv('SCHEMAID') Функция USERENV('SCHEMAID') возвращает идентификатор пользователя для схемы, в которой выполняется процедура. В хранимой процедуре с правами создателя (т.е. в стандартной хранимой процедуре) это значение постоянно — это всегда будет идентификатор пользователя, в схеме которого создана процедура. Это означает, что если кто-то напишет процедуру, обращающуюся к словарю данных, эта процедура будет видеть его объекты, а не объекты пользователя, выполняющего запрос. Более того, в хранимой процедуре роли не используются (мы рассмотрим эту проблему чуть позже), так что, если доступ к таблице в схеме другого пользователя получен через роль, в хранимой процедуре эта таблица будет недоступна. Когда-то единственным решением было создание хранимой процедуры, обращающейся к представлениям DBA_* (после получения непосредственных привилегий для этого) и реализующей собственный механизм защиты, который гарантировал получение пользователями только той информации, Глава которая доступна им в представлениях ALL_* или USER_*. Это более чем нежелательно, поскольку приходится писать большой объем кода, предоставлять права доступа ко всем представлениям DBA_*;

кроме того, при малейшей невнимательности процедура позволит получить несанкционированный доступ к объектам. Здесь поможет процедура, работающая с правами вызывающего. Теперь можно не только создать хранимую процедуру, обращающуюся к представлениям ALL_* и USER_*, — это можно делать от имени текущего зарегистрированного пользователя, c его привилегиями и даже ролями. Мы продемонстрируем это, реализовав "усовершенствованную" команду DESCRIBE. Это будет минимальная по возможностям реализация — разобравшись, как это работает, вы сможете добавить любые возможности: tkyte@TKYTE816> create or replace 2 procedure desc_table(p_tname in varchar2) 3 AUTHID CURRENT_USER 4 as 5 begin 6 dbms_output.put_line('Типа данных для таблицы ' || p_tname);

7 dbms_output.new_line;

8 9 dbms_output.put_line(rpad('Имя столбца',31) || 10 rpad('Тип данных',20) || 11 rpad('Длина',11) || 12 'Пустые значения');

13 dbms_output.put_line(rpad('-\30,'-') || ' ' || 14 rpad('-',19,'-') || ' ' || 15 rpad('-',10,'-') || ' ' || 16 ' ');

17 for x in 18 (select column_name, 19 data_type, 20 substr( 21 decode(data_type, 22 'NUMBER', decode(data_precision, NULL, NULL, 23 '('||data_precision||','||data_scale||')'), 24 data_length),l,ll) data_length, 25 decode(nullable,'Y','null','not null') nullable 26 from user_tab_columns 27 where table_name = upper(p_tname) 28 order by column_id) 29 loop 30 dbms_output.put_line(rpad(x.column_natne,31) || 31 rpad(x.data_type,20) || 32 rpad(x.data_length,ll) || 33 x.nullable);

34 end loop;

35 36 dbms_output.put_line(chr(10) || chr(10) || 37 'Индексы по ' || p_tname);

38 39 for z in 40 (select a.index_name, a.uniqueness Права вызывающего и создателя 41 from user_indexes a 42 where a.table_name - upper(p_tname) 43 and index_type - 'NORMAL') 44 loop 45 dbms_output.put(rpad(z.index_name,31) || 46 z.uniqueness);

47 for y in 48 (selectdecode(column_position,l,'(',' ')|| 49 column_name column_name 50 from user_ind_columns b 51 where b.index_name = z.index_name 52 order by column_position) 53 loop 54 dbms_output.put(y.column_name);

55 end loop;

56 dbms_output.put_line( 1 )' II chr(10));

57 end loop;

58 59 end;

60 / Procedure created. tkyte@TKYTE816> grant execute on desc_table to public 2/ Grant succeeded. Эта процедура интенсивно обращается к представлениям USER_INDEXES и USER_IND_COLUMNS. При работе с правами создателя (без конструкции AUTHID CURRENT_USER) эта процедура сможет выдать информацию только для одного пользователя (и всегда — для одного и того же). Однако при использовании прав вызывающего процедура будет выполняться от имени и с привилегиями пользователя, зарегистрировавшегося во время выполнения. Так что, хотя процедура и принадлежит пользователю TKYTE, ее можно выполнять от имени пользователя SCOTT и получать результат, подобный следующему: tkyte@TKYTE816> connect scott/tiger scott@TKYTE816> set serveroutput on format wrapped scott@TKYTE816> exec tkyte.desc_table('emp') Типы данных для таблицы emp Имя столбца EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO Тип данных NUMBER VARCHAR2 VARCHAR2 NUMBER DАТЕ NUMBER NUMBER NUMBER Длина (4,0) 10 9 (4,0) 7 (7,2) (7,2) (2,0) Пустые значения not null null null null null null null null Глава Индексы по emp EMP_PK UNIQUE(EMPNO) PL/SQL procedure successfully completed.

Универсальные объектные типы Идея в данном случае та же, что и в предыдущем, но результаты могут быть более общезначимыми. С помощью средств Oracle 8, позволяющих создавать собственные объектные типы со специфическими методами обработки данных, можно создавать методы, работающие с набором привилегий текущего зарегистрированного пользователя. То есть, создав универсальные, обобщенные типы, их устанавливают в базе данных один раз и разрешают всем использовать. Без возможности работать с правами вызывающего владелец объектного типа должен был иметь очень мощные привилегии (как было описано ранее) или надо было устанавливать этот объектный тип в каждой схеме, где его предполагалось использовать. Именно с правами вызывающего всегда работали объектные типы, стандартно поставляемые в составе сервера Oracle (например, типы ORDSYS.*, используемые для поддержки компонентов interMedia), что позволяло устанавливать их в базе данных только один раз, а использовать — любому пользователю со своими привилегиями. Это имеет значение, потому что объектные типы ORDSYS выполняют чтение и запись в таблицы базы данных. Набор таблиц, к которым они обращаются, полностью зависит от того, кто именно выполняет соответствующие методы. Именно это свойство позволяет обеспечить универсальность и общедоступность этих типов. Они устанавливаются в схеме ORDSYS, но пользователь ORDSYS не имеет доступа к таблицам, с которыми они фактически работают. Теперь, используя Oracle 8i, разработчики приложений могут создавать такие же типы.

Реализация собственных средств контроля доступа В Oracle 8i появилась возможность тщательного контроля доступа (Fine Grained Access Control — FGAC), благодаря чему можно реализовать правила защиты, предотвращающие несанкционированный доступ к данным. Обычно для этого в каждую таблицу добавляется столбец, например COMPANY. Значения в этом столбце автоматически формируются триггером, а в каждый запрос включается условие WHERE COMPANY = SYS_CONTEXT (...), ограничивающее набор доступных пользователю строк только теми, доступ к которым авторизован (подробнее об этом см. в главе 21). Можно также создавать отдельную схему (набор таблиц) для каждой компании. Другими словами, для каждой компании устанавливается и наполняется данными свой набор таблиц. При этом в принципе невозможен доступ одного пользователя к данным другого, поскольку данные эти физически хранятся в другой таблице. Это — вполне уместный подход, имеющий преимущества (и недостатки) по сравнению со средствами тщательного контроля доступа. Проблема, однако, в том, что хотелось бы поддерживать один набор хранимого кода для всех пользователей. Кэширования же в разделяемом пуле Права вызывающего и создателя десяток копий одного и того же большого PL/SQL-пакета желательно избежать. Не хотелось бы изменять десяток экземпляров одного и того же кода, если в нем будет найдена ошибка. Не хотелось бы, чтобы пользователи выполняли потенциально разные версии одного кода. Работа с правами вызывающего идеально поддерживает эту модель защиты (несколько наборов таблиц и один экземпляр кода). Имея возможность работать с правами вызывающего, можно написать одну хранимую процедуру, обращающуюся к таблицам с правами доступа текущего пользователя и разрешением имен в его схеме. Как было продемонстрировано в примере с процедурой PRINT_TABLE, это можно сделать как с помощью динамического, так и статического SQL Рассмотрим следующий пример. Установим таблицы EMP/DEPT в схеме SCOTT и в моей схеме TKYTE. Третий пользователь будет создавать приложение, использующее таблицы ЕМР и DEPT для создания отчета;

он не будет иметь доступа к таблицам ЕМР и DEPT ни в схеме SCOTT, ни в схеме TKYTE (его таблицы созданы для тестирования). Вы увидите, что процедура, выполненная пользователем SCOTT, будет выдавать данные из схемы SCOTT;

когда же ее выполнит пользователь TKYTE, будут использованы таблицы последнего: tkyte@TKYTE816> connect scott/tiger scott@TKYTE816> grant select on emp to public;

Grant succeeded. scott@TKYTE816> grant select on dept to public;

Grant succeeded. scott@TKYTE816> connect tkyte/tkyte tkyte@TKYTE816> create table dept as select * from scott.dept;

Table created. tkyte@TKYTE816> create table emp as select * from scott.emp;

Table created. tkyte@TKYTE816> insert into emp select * from emp;

14 rows created. tkyte@TKYTE816> create user application identified by pw 2 default tablespace users quota unlimited on users;

User created. tkyte@TKYTE816> grant create session, create table, 2 create procedure to application;

Grant succeeded. tkyte@TKYTE816> connect application/pw application@TKYTE816> create table emp as select * from scott.emp where 1=0;

Table created. application@TKYTE816> create table dept as 2 select * from scott.dept where 1=0;

Table created.

1520 Глава Итак, имеются три пользователя, у каждого из которых собственная пара таблиц EMP/DEPT. Данные же в этих таблицах существенно отличаются. У пользователя SCOTT — обычный набор данных ЕМР, у пользователя TKYTE данных в два раза больше, а у пользователя APPLICATION эти таблицы пусты. Теперь создадим приложение: application@TKYTE816> create or replace procedure emp_dept_rpt 2 AUTHID CURRENT_USER 3 as 4 begin 5 dbms_output.put_line('Зарплаты и количество сотрудников по -> отделам') ;

6 dbms_output.put_line(chr(9)||'Отдел Зарплата Количество');

7 dbms_output.put_line(chr (9) || ' ') ;

8 for x in (select dept.deptno, sum(sal) sal, count(*) cnt 9 from emp, dept 10 where dept.deptno = emp.deptno 11 group by dept.deptno) 12 loop 13 dbms_output.put_line(chr(9) || 14 to_char(x.deptno,'99999') || ' ' || 15 to_char(x.sal,'99,999') || ' ' || 16 to_char(x.cnt,'99,999'));

17 end loop;

18 d b m s _ o u t p u t. p u t _ l i n e ( ' = = = = = = = = = = = = = = = = = = ' ) ;

19 end;

20 / Procedure created. application@TKYTE816> grant execute on emp_dept_rpt to public 2/ Grant succeeded. application@TKYTE816> set serveroutput on format wrapped application@TKYTE816> exec emp_dept_rpt;

Зарплаты и количество сотрудников по отделам Отдел Зарплата Количество PL/SQL procedure successfully completed. Когда процедуру выполняет пользователь APPLICATION таблицы пусты, как и ожидалось. При выполнении же этого приложения пользователями SCOTT и TKYTE: tkyte@TKYTE816> connect scott/tiger scott@TKYTE816> set serveroutput on format wrapped scott@TKYTE816> exec application.ernp_dept_rpt Зарплаты и количество сотрудников по отделам Отдел Зарплата Количество 10 20 30 8,750 10,875 9,400 3 5 Права вызывающего и создателя PL/SQL procedure successfully completed. scott@TKYTE816> connect tkyte/tkyte tkyte@TKYTE816> set serveroutput on format wrapped tkyte@TKYTE816> exec application.emp_dept_rpt Зарплаты и количество сотрудников по отделам Отдел Зарплата Количество 10 17,500 20 21,750 30 18,800 6 10 PL/SQL procedure successfully completed. Как видите, процедура действительно обращается к разным таблицам в разных схемах. Тем не менее, как будет показано в разделе "Проблемы", надо позаботиться о синхронизации этих схем. Не только должны существовать таблицы с соответствующими именами, но типы данных, порядок и количество столбцов в них при использовании статического SQL тоже должны совпадать.

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

• Защита. Процедуры с правами создателя имеют такие особенности с точки зрения защиты, которые делают их применение единственно верным выбором почти во всех случаях.

Производительность и масштабируемость Процедура, работающая с правами создателя, — замечательная вещь с точки зрения защиты и производительности. В разделе "Как работают процедуры с правами вызывающего" будет показано, что, благодаря статическому связыванию на этапе компиляции, можно существенно повысить эффективность во время выполнения. Все проверки защиты, зависимостей и т.п. выполняются один раз, при компиляции. Для процедуры, работающей с правами вызывающего, большая часть этих действий будет делаться во время выполнения. Более того, может потребоваться многократно выполнять эти действия в одном сеансе, после выполнения оператора ALTER SESSION или SET ROLE. Любое изменение среды выполнения приводит к изменению поведения процедуры с правами вызывающего. Процедура же с правами создателя от этих изменений не зависит. Кроме того, как будет показано далее в разделе "Проблемы", процедура, работающая с правами вызывающего, более интенсивно использует разделяемый пул, чем ана 1522 Глава логичная процедура, работающая с правами создателя. Поскольку среда выполнения для процедуры с правами создателя статична, все выполняемые ею статические SQL-операторы могут совместно использоваться в разделяемом пуле. Как было показано в других главах этой книги, необходимо заботиться о правильном использовании разделяемого пула (использовать связываемые переменные, избегать излишних разборов и т.д.). Использование процедур с правами создателя гарантирует максимально эффективное использование разделяемого пула. Процедуры же с правами вызывающего могут приводить к неэффективному использованию разделяемого пула. Вместо того чтобы один запрос, SELECT * FROM T, при использовании в процедуре означал бы одно и то же для всех пользователей, он может иметь для пользователей разный смысл. В разделяемом пуле будет больше различных SQL-операторов. Использование прав создателя обеспечивает более эффективное использование разделяемого пула.

Защита Используя права создателя, можно создать процедуру, безопасно и корректно обрабатывающую определенный набор объектов базы данных. Затем можно предоставить другим пользователям возможность выполнять эту процедуру с помощью оператора GRANT EXECUTE ON <процедура> ТО <пользователь>/public/<роль>. Эти пользователи смогут запускать процедуру для чтения/записи таблиц (способом, предусмотренным в коде этой процедуры), но никаким другим способом читать или записывать данные в наши таблицы они не могут. Другими словами, мы только что создали надежный процесс изменения или чтения объектов безопасным образом и теперь можем предоставлять пользователям право на это, не опасаясь, что они смогут каким-либо другим способом прочитать или изменить эти объекты. Они не смогут вставлять записи в таблицу сотрудников с помощью утилиты SQL*Plus. Это можно будет делать только с помощью хранимой процедуры, реализующей все необходимые проверки. Этот способ работы существенно влияет на разработку приложения и предоставление прав на использование ваших данных. Больше не придется выполнять операторы GRANT INSERT для таблицы, как это делалось для клиент-серверного приложения, непосредственно выполняющего SQL-операторы INSERT. Вместо этого придется выполнить оператор GRANT EXECUTE для процедуры, которая может проверять и оценивать данные и их защищенность. Беспокоиться о целостности данных при этом тоже больше не нужно (процедура точно задает, что и как необходимо делать, а других способов работы с данными просто нет). Сравните это с работой типичных клиент-серверных и даже многих З-уровневых приложений. В клиент-серверном приложении операторы INSERT, UPDATE, DELETE и т.п. включены непосредственно в код клиентского приложения. Для работы этого приложения, пользователю необходимо предоставить привилегии INSERT, UPDATE и DELETE непосредственно для базовых таблиц. Теперь весь мир имеет доступ к базовым таблицам через любой интерфейс, способный взаимодействовать с СУБД Oracle. При использовании процедуры с правами создателя такой проблемы нет. Единственный способ изменения таблиц — с помощью надежной процедуры, которой вполне можно доверять. Это очень важное свойство.

Права вызывающего и создателя Часто разработчики спрашивают: "Как сделать так, чтобы только мое приложение, myapp.exe, могло выполнять действие X в базе данных?". Например, надо, чтобы это приложение могло выполнять вставку в таблицу, но другие приложения этого сделать не могли. Единственно безопасный способ сделать это — поместить алгоритмы работы с данными приложения myapp.exe в базу данных, и никаких операторов INSERT, UPDATE, DELETE или SELECT в клиентском приложении. Разместив приложение непосредственно в базе данных, устранив необходимость выполнять вставку (или любые другие операции с таблицей) в клиентском приложении, вы добьетесь того, чтобы только одно приложение могло обращаться к данным. Размещая алгоритмы работы с базой данных приложения в ней самой, мы делаем из приложения просто еще одни уровень абстракции. Не имеет значения, как вызывается приложение (его компонент, работающий с базой данных) — из SQL*Plus, из графического интерфейса или из другого, еще нереализованного интерфейса, — в базе данных работает одно и то же приложение.

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

Права создателя При использовании прав создателя хранимая процедура компилируется с учетом привилегий, непосредственно предоставленных пользователю, "владеющему" процедурой. Под "непосредственно предоставленными привилегиями" подразумеваются все объектные и системные привилегии, предоставленные пользователю или роли PUBLIC, но не другим ролям, которые предоставлены пользователю или роли PUBLIC. Короче, для процедур с правами создателя роли не учитываются и не используются, ни во время компиляции, ни при выполнении. Процедура компилируется только с учетом непосредственных привилегий. Это описано в руководстве OracleApplication Developer's Guide тaк:. Привилегии, необходимые для создания процедур и функций При создании отдельной процедуры или функции, спецификации или тела пакета должны выполняться следующие требования. Необходимо наличие системной привилегии CREATE PROCEDURE (для создания процедуры или пакета в своей схеме) или системной привилегии CREATE ANY PROCEDURE (для создания процедуры или пакета в другой пользовательской схеме). Внимание: Для успешной компиляции процедуры или пакета требуются следующие дополнительные привилегии: • владелец процедуры или пакета должен явно получить необходимые объектные привилегии для всех объектов, на которые есть ссылки в коде;

Глава • владелец не может получить необходимые привилегии через роли. Если привилегии владельца процедуры или пакета изменяются, процедуру необходимо повторно аутентифицировать перед выполнением. Если необходимая для доступа к объекту привилегия у владельца процедуры (или пакета) отобрана, выполнение процедуры становится невозможным. Хотя это явно и не сказано, предоставление привилегии роли PUBLIC ничуть не хуже, чем непосредственно владельцу процедуры. Необходимость непосредственного предоставления привилегий владельцу процедуры, работающей с правами создателя, иногда приводит к странным ситуациям. Оказывается, можно выполнить запрос к объекту в SQL*Plus и использовать анонимный блок для доступа к этому же объекту, но нельзя создать хранимую процедуру для обращения к этому объекту. Зададим необходимые для данного примера привилегии: scott@TKYTE816> revoke select on emp from publicRevoke succeeded. scott@TKYTE816> grant select on emp to connect;

Grant succeeded. scott@TKYTE816> connect tkyte/tkyte tkyte@TKYTE816> grant create procedure to another_user;

Grant succeeded. А теперь убедимся, что пользователь ANOTHER_USER может делать запросы к таблице SCOTT.EMP: tkyte@TKYTE816> connect another_user/another_user another_user@TKYTE816> select count(*) from scott.emp;

COUNT(*) 14 Пользователь ANOTHER_USER также может выполнять анонимный блок PL/SQL: another_user@TKYTE816> begin 2 for x in (select count(*) cnt from scott.emp) 3 loop 4 dbms_output.put_line(x.cnt);

5 end loop;

6 end;

7/ 14 PL/SQL procedure successfully completed. Однако при попытке создать процедуру, идентичную представленному выше PL/SQLблоку, мы получим следующее: another_user@TKYTE816> create or replace procedure P 2 as Права вызывающего и создателя 3 begin 4 for x in (select count(*) cnt from scott.emp) 5 loop 6 dbms_output.put_line(x.cnt);

7 end loop;

8 end;

9/ Warning: Procedure created with compilation errors. another_user@TKYTE816> show err Errors for PROCEDURE P: LINE/COL ERROR 4/14 4/39 6/9 6/31 PL/SQL: SQL Statement ignored PLS-00201: identifier 'SCOTT.EMP' must be declared PL/SQL: Statement ignored PLS-00364: loop index variable 'X' use is invalid Я не могу создать процедуру (собственно, любой скомпилированный хранимый объект, скажем, представление или триггер), обращающуюся к таблице SCOTT.EMP. Это предсказуемая и описанная в документации особенность. В рассмотренном выше примере пользователь ANOTHER_USER имеет роль CONNECT. Роли CONNECT была предоставлена привилегия SELECT для таблицы SCOTT.EMP. Эта привилегия роли CONNECT, однако, недоступна в хранимой процедуре с правами создателя, поэтому и выдается сообщение об ошибке. Чтобы избежать таких странностей, я рекомендую выполнить оператор SET ROLE NONE в среде SQL*Plus и попытаться выполнить оператор, который предполагается включить в хранимую процедуру. Например: another_user@TKYTE816> set role none;

Role set. another_user@TKYTE816> select count(*) from scott.emp;

select count(*) from scott.emp * ERROR at line 1: ORA-00942: table or view does not exist Если оператор сработает в SQL*Plus без ролей, он, несомненно, сработает и в хранимой процедуре, выполняющейся с правами создателя.

Компиляция процедуры с правами создателя При компиляции процедуры выполняется несколько действий, связанных с привилегиями. Здесь я их вкратце опишу, а затем рассмотрю подробно. • Проверяется существование всех объектов, к которым процедура обращается статически (всех, к которым не обращаются с помощью динамического SQL). Имена разрешаются с помощью стандартных правил области действия по отношению к владельцу процедуры. • Проверяется, все ли объекты доступны в нужном режиме. Например, если выполняется оператор UPDATE T, сервер Oracle проверит, может ли создатель или Глава роль PUBLIC выполнять UPDATE T непосредственно, без использования какихлибо ролей. • Устанавливается и поддерживается зависимость процедуры от объектов, на которые она ссыпается. Если процедура выполняет оператор SELECT FROM T, регистрируется зависимость процедуры от таблицы Т. Если, например, создается процедура P, пытающаяся выполнить оператор SELECT * FROM T, компилятор сначала преобразует T в полностью уточненное имя. Имя T в базе данных неоднозначно — таких таблиц представлений может быть несколько. Чтобы выяснить, какую именно таблицу T использовать, сервер Oracle применяет правила определения области действия. Вместо синонимов подставляются соответствующие базовые объекты, причем для каждого объекта указывается имя соответствующей схемы. Это разрешение имен выполняется для текущего зарегистрированного пользователя (создателя). Другими словами, сервер ищет объект T у данного пользователя и использует его (при этом используются приватные синонимы пользователя), затем сервер ищет T среди общедоступных синонимов и т.д. Определив объект, на который ссылается имя T, сервер Oracle определяет, возможен ли доступ к этому объекту в нужном режиме. В данном случае, если объект T принадлежит создателю процедуры или создатель непосредственно получил привилегию SELECT на объект T (или эта привилегия была предоставлена роли PUBLIC), процедура будет скомпилирована. Если создатель процедуры не имеет доступа к объекту по имени T благодаря непосредственно предоставленной привилегии, процедура P не будет скомпилирована. Таким образом, когда объект (хранимая процедура, ссылающаяся на T) компилируется, сервер Oracle выполняет все эти проверки. Если они "пройдены", сервер Oracle компилирует процедуру, сохраняет двоичный код процедуры и устанавливает зависимости между этой процедурой и объектом Т. Эта зависимость используется для проверки действительности процедуры в дальнейшем, если что-то произошедшее с объектом T может потребовать перекомпиляции процедуры. Например, если позже мы выполним REVOKE SELECT ON T и отберем эту привилегию у владельца процедуры, сервер Oracle пометит все хранимые процедуры этого пользователя, зависящие от объекта T и ссылающиеся на T, как недействительные (INVALID). Если мы добавим столбец с помощью оператора ALTER T ADD..., сервер Oracle сделает недействительными все зависящие от него процедуры. Это приведет к их автоматической перекомпиляции при следующем вызове. Интересно разобраться не только в том, что сохраняется, но и что не сохраняется при компиляции объекта. Сервер Oracle не сохраняет информацию о привилегии, необходимой для получения доступа к объекту Т. Мы знаем только, что процедура P зависит от Т. Мы не знаем, почему получен доступ к объекту T: • потому что создателю процедуры была предоставлена соответствующая привилегия (GRANT SELECT ON T TO USER);

• потому что привилегия была предоставлена роли PUBLIC (GRANT SELECT ON T TO PUBLIC);

• потому что пользователь имеет привилегию SELECT ANY TABLE.

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

Права создателя и роли Теперь нам предстоит разобраться, почему при компиляции и выполнении хранимых процедур с правами создателя роли не учитываются. Сервер Oracle не хранит информацию о том, почему мы можем обращаться к объекту T, — только то, что мы это можем. Любое изменение привилегий, которое может сделать невозможным обращение к объекту T, приведет к пометке процедуры как недействительной и ее перекомпиляции. Если роли не учитываются, такое изменение привилегий может произойти только при выполнении операторов REVOKE SELECT ANY TABLE или REVOKE SELECT ON T для пользователя-создателя или роли PUBLIC. Если роли учитываются, набор операторов, которые могут сделать процедуру недействительной, существенно расширяется. Представьте на минуту, что роли позволяют обращаться к объектам из хранимых процедур. Тогда при изменении любой из ролей, лишении ее привилегии или роли (ведь роли можно предоставлять ролям), мы рискуем сделать недействительными множество процедур (даже тех, которые не использовали привилегии измененной роли). Рассмотрим результат лишения роли системной привилегии. Это будет аналогично лишению роли PUBLIC мощной системной привилегии (не делайте этого, просто представьте, а если уж хотите сделать, то в тестовой базе данных). Если роли PUBLIC была предоставлена привилегия SELECT ANY TABLE, в результате лишения ее этой привилегии будут помечены как недействительные практически все процедуры в базе данных. Если процедуры зависят от ролей, любая процедура в базе данных затрагивается даже наименьшими изменениями прав доступа. Поскольку одно из основных преимуществ хранимых процедур — однократная компиляция при многократном выполнении, это крайне негативно повлияет на производительность. Учтите также следующие особенности ролей. • Роли могут быть нестандартными. Если я создам нестандартную роль, включу ее и скомпилирую процедуру, работа которой зависит от привилегий этой роли, по завершении сеанса у меня этой роли больше не будет. Станет ли при этом процедура недействительной? Почему? А почему — нет? Я легко могу обосновать оба варианта. • Роли могут быть защищены паролями. Если изменен пароль роли, надо ли перекомпилировать все объекты, которым эта роль может понадобиться? Мне эта роль может быть предоставлена, но, не зная ее нового пароля, я не смогу ею восполь Глава зоваться. Будут ли по-прежнему доступны соответствующие привилегии? Почему — да или почему — нет? Есть аргументы и за, и против. Подведем итоги по использованию ролей в процедурах, работающих с правами создателя. • Вы можете работать с тысячами или десятками тысяч пользователей. Они не создают хранимые объекты. Для управления всеми этими пользователям необходимы роли. Именно для этого и создавались роли. • Вы можете использовать намного меньше схем приложений (в них и находятся хранимые объекты). Нужно точно знать, какие для них необходимы привилегии и почему. С точки зрения защиты это называется концепцией минимальных привилегий. Надо явно указать, какие привилегии нужны и зачем. Если унаследовано множество привилегий от ролей, добиться минимальности привилегий практически невозможно. При явном задании привилегий не возникают проблемы, поскольку количество схем приложений невелико (но количество их пользователей огромно). • Наличие непосредственной взаимосвязи между создателем и процедурой позволяет сделать базу данных намного эффективней. Мы перекомпилируем объекты только в случае необходимости. Это существенно повышает эффективность их работы.

Права вызывающего Между процедурами с правами вызывающего и процедурами с правами создателя (и анонимными блоками PL/SQL) есть существенное отличие с точки зрения использования привилегий и разрешения ссылок на объекты. Что касается выполнения SQL-операторов, процедуры с правами вызывающего подобны анонимному блоку PL/SQL, но PL/SQL-операторы выполняются в них так же, как в процедурах с правами вызывающего. Кроме того, роли могут учитываться в процедуре с правами вызывающего, в зависимости от того, как к ней обращаются (в отличие от процедуры с правами создателя, которая игнорирует роли при доступе к объектам). Рассмотрим две части процедур, работающих с правами вызывающего: • "SQL-части" - все операторы SELECT, INSERT, UPDATE, DELETE и все операторы, динамически выполняемые с помощью DBMS_SQL или EXECUTE IMMEDIATE (включая динамически выполняемый PL/SQL-код);

• "PL/SQL-части" — статические ссылки на объектные типы в объявлениях переменных, вызовы хранимых процедур, пакетов, функций и т.п. В процедурах с правами вызывающего обработка этих "частей" очень отличается. Имена в "SQL-части" разрешаются при компиляции (для определения структур данных и т.п.) и еще раз — при выполнении. Именно это позволяет хранимой процедуре с запросом SELECT * FROM ЕМР обращаться к другим таблицам ЕМР при выполнении другими пользователями. Однако "PL/SQL-части" при компиляции связываются статически, как Права вызывающего и создателя и в процедуре с правами создателя. Поэтому, если в процедуре с правами вызывающего имеется следующий код: AUTHID CURRENT_USER as begin for x in (select * from T) loop proc(x.cl);

end loop;

то ссылки на T будут разрешаться во время выполнения (как и во время компиляции, чтобы понять, что означает SELECT *) динамически, что позволяет использовать разные объекты T для каждого пользователя. Ссылка на процедуру PROC, однако, будет разрешена только при компиляции, поэтому процедура будет статически связана с одной процедурой PROC. Пользователю, вызывающему эту процедуру, не нужна привилегия EXECUTE ON PROC, но вот привилегия SELECT для объекта T нужна. Не хочется вас запутывать, но если необходимо разрешать вызов PROC при выполнении, есть механизм и для этого. Можно написать следующий код: AUTHID CORRENT_USER as begin for x in (select * from T) loop execute immediate 'begin proc(:x);

end;

' USING x.cl;

end loop;

В этом случае ссылка на процедуру PROC будут разрешаться на основе набора привилегий вызывающего, и этим вызывающим (или соответствующим ролям, если действуют роли) необходимо предоставить привилегию EXECUTE.

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

• вызов из процедуры с правами создателя;

• вызов из другой процедуры с правами вызывающего;

• вызов из SQL-оператора;

• вызов из представления, ссылающегося на процедуру с правами вызывающего;

• вызов из триггера. Для одной и той же процедуры результат в каждой из перечисленных сред может отличаться. В каждом из этих случаев процедура с правами вызывающего может обращаться при выполнении к другим таблицам и объектам базы данных.

Глава Начнем с изучения того, как связываются объекты и какие привилегии доступны в процедуре с правами вызывающего при выполнении в каждой из перечисленных сред. Случаи представления и триггера будем считать одинаковыми, поскольку там процедура может работать только с правами создателя. Кроме того, поскольку статические объекты PL/SQL всегда, во всех средах разрешаются во время выполнения, мы их рассматривать не будем. Они всегда разрешаются в схеме и с привилегиями создателя. Текущему зарегистрированному пользователю не нужен доступ к объекту PL/SQL, на который делается ссылка. В следующей таблице описано предполагаемое поведение в каждой из сред: Среда SQL-объекты и динамически вызываемый PL/SQL Роли действуют? Да. Все роли, включенные до выполнения процедуры, действуют и в процедуре. Они будут использоваться для разрешения или запрещения доступа ко всем SQL-объектам и динамически вызываемому PL/SQL-коду.

Непосредственный Ссылки на эти объекты разрешаются вызов в стандартной схеме и со пользователем. стандартными привилегиями Например: текущего пользователя. SQL> exec p;

Неуточненные ссылки на объекты будут разрешаться в их схеме. Все объекты должны быть доступны текущему зарегистрированному пользователю. Если процедура выполняет SELECT из T, текущий пользователь также должен иметь привилегию SELECT для объекта T (полученную либо непосредственно, либо через роль). Вызов из процедуры с правами создателя (P1), где P2 процедура с правами вызывающего. Например: procedure p1 is begin p2;

end;

Этот вызов разрешается в схеме создателя, в схеме вызывающей процедуры. Неуточненные имена объектов будут разрешаться в этой схеме, схеме вызывающей процедуры, а не в схеме текущего зарегистрированного пользователя и не в схеме, где была создана процедура с правами вызывающего, В нашем примере, владелец P1 всегда будет "вызывающим" внутри процедуры P2.

Нет. Роли не учитываются, поскольку вызвана процедура с правами создателя. В момент входа в процедуру с правами создателя все роли отключаются и не учитываются до выхода из этой процедуры, Вызов из другой Аналогично непосредственному процедуры с вызову пользователем. правами вызывающего. Вызов из SQL-оператора. Аналогично непосредственному вызову пользователем.

Да. Точно так же, как и при вызове пользователем, Да. Точно так же, как и при вызове пользователем.

Права вызывающего и создателя Среда SQL-объекты и динамически вызываемый PL/SQL Роли действуют?

Вызов из Аналогично вызову из процедуры представления с правами создателя. или триггера, ссылающегося на процедуру с правами вызывающего.

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

Как видите, среда выполнения может существенно влиять на выполнение процедуры с правами вызывающего. Одна и та же хранимая процедура на PL/SQL при непосредственном вызове и при вызове из другой хранимой процедуры может обращаться к различным наборам объектов, даже при регистрации от имени одного и того же пользователя. Чтобы продемонстрировать это, я создам процедуру, показывающую, какие роли активны во время выполнения и обращающуюся к таблице за данными, свидетельствующими о "владельце" таблицы. Мы сделаем это для каждого из представленных выше случаев, за исключением вызова процедуры с правами вызывающего из другой процедуры с правами вызывающего, поскольку это ничем не отличается от ее непосредственного вызова. Начнем с создания двух учетных записей, которые будут использоваться для демонстрации: tkyte@TKYTE816> drop user a cascade;

User dropped. tkyte@TKYTE816> drop user b cascade;

User dropped. tkyte@TKYTE816> create user a identified by a default tablespace data temporary tablespace temp;

User created. tkyte@TKYTE816> grant connect, resource to a;

Grant succeeded. tkyte@TKYTE816> create user b identified by b default tablespace data temporary tablespace temp;

User created. tkyte@TKYTE816> grant connect, resource to b;

Grant succeeded. Итак, созданы два пользователя, А и В, и каждому из них предоставлены две роли, CONNECT и RESOURCE. Пользователь А создаст процедуру с правами вызывающего, а также процедуру с правами создателя и представление, из которых будет вызываться процедура с правами вызывающего. При каждом выполнении процедура будет выдавать количество действующих ролей, имя текущего пользователя (с набором привилегий какой схемы она работает), имя текущей схемы и, наконец, какая таблица используется запросом. Начнем с создания таблицы, определенно принадлежащей пользователю А:

1532 Глава tkyte@TKYTE816> connect a/a a@TKYTE816> create table t (x varchar2(255));

Table created. a@TKYTE816> insert into t values ('Таблица пользователя A ' ) ;

1 row created. Затем пользователь А создает функцию с правами вызывающего, процедуру с правами создателя и представление: a@TKYTE816> c r e a t e function Invoker_rights_function r e t u r n varchar 2 AUTHID CURRENT_USER as 4 l_data varchar2(4000);

5 begin 6 dbms_output.put_line('H — функция с правами вызывающего, -> принадлежащая А ' ) ;

7 select 'current_user=' || 8 sys_context('userenv', 'current_user') || 9 ' current_schema=' || 10 sys_context('userenv', 'current_schema') || 11 ' active roles=1 || cnt || 12 ' data from T=' || t.x 13 into l_data 14 from (select count(*) cnt from session_roles), t;

15 16 return l_data;

17 end;

18 / Function created. a@TKYTE816> grant execute on Invoker_rights_function to publicGrant succeeded. a@TKYTE816> create procedure Definer_rights_procedure 2 as 3 l_data varchar2(4000);

4 begin 5 dbms_output.put_line('Я — процедура с правами создателя, -> принадлежащая А') ;

6 select 'current_user=' || 7 sys_context('userenv', 'current_user') || 8 ' current_schema=' || 9 sys_context('userenv', 'current_schema') || 10 ' active roles=' || cnt || 11 ' data from T=' | | t.x 12 into l_data 13 from (select count(*) cnt from session_roles), t;

14 15 dbms_output.put_line(l_data);

16 dbms_output.put_line -> ('Теперь вызываем функцию с правами вызывающего...');

17 dbms_output.put_line(Invoker_rights_function);

Права вызывающего и создателя 18 end;

/ created.

Procedure a@TKYTE816> grant execute on Definer_rights_procedure to publicGrant succeeded. a@TKYTE816> c r e a t e view V 2 as 3 select invoker_rights_function from dual 4/ View created. a@TKYTE816> grant select on v to public 2/ Grant succeeded. Зарегистрируемся как пользователь В, создадим таблицу T с идентифицирующей пользователя строкой и выполним созданную ранее функцию: a@TKYTE816> connect b/b b@TKYTE816> create table t (x varchar2(255));

Table created. b@TKYTE816> insert into t values ("Таблица пользователя B ' ) ;

1 row created. b@TKYTE816> exec dbms_output.put_line(a.Invoker_rights_function) Я — функция с правами вызывающего, принадлежащая А current_user=B current_scheroa=B active roles=3 data from Т=Таблица -> пользователя В PL/SQL procedure successfully completed. Итак, мы видим: когда пользователь В непосредственно вызывает функцию с правами вызывающего, принадлежащую пользователю А, во время ее выполнения используются привилегии пользователя В (current_user=B). Далее, поскольку current_schema — тоже пользователь В, запрос выбирает данные из таблицы B.T, а не из A.T. Это доказывает строка data from Т=Таблица пользователя В в представленных выше результатах. Наконец, мы видим, что при выполнении запроса в сеансе активны три роли (третья роль — PLUSTRACE, необходимая для использования AUTOTRACE;

в моей базе данных она предоставлена роли PUBLIC). Посмотрим, что произойдет при вызове через процедуру с правами создателя: b@TKYTE816> exec a.Definer_rights_procedure Я — процедура с правами создателя, принадлежащая А current_user=A current_schema=A active roles=0 data from Т=Таблица -> пользователя А Теперь вызываем функцию с правами вызывающего... Я — функция с правами вызывающего, принадлежащая А current_user=A current_schema=A active roles=0 data from Т=Та6лица -> пользователя А PL/SQL procedure successfully completed.

Глава Вы видите, что процедура с правами создателя выполняется с привилегиями пользователя А, кроме ролей (active roles=0). Процедура с правами создателя жестко связана с таблицей A.T и не обращается к таблице B.T. Важнее всего то, что происходит при вызове функции с правами вызывающего из процедуры с правами создателя. Обратите внимание, что на этот раз вызывающий пользователь А, а не В. Вызывающий определяется текущей схемой в момент вызова процедуры с правами вызывающего. Функция больше не выполняется от имени В, как в предыдущем случае, — теперь она выполняется от имени пользователя А. Поскольку current_user и current_schema теперь задают пользователя А, функция с правами вызывающего обращается к таблице пользователя А. Еще один важный факт: на этот раз роли в функции с правами вызывающего не действуют. При входе в процедуру с правами создателя роли отключаются и остаются отключенными до выхода из этой процедуры. Теперь рассмотрим последствия вызова функции с правами вызывающего из SQLоператора: b@TKYTE816> select a.invoker_rights_function from dual;

INVOKER_RIGHTS_FUNCTION current_user=B current_schema=B active roles=3 data from Т=Таблица -> пользователя В b@TKYTE816> select * from a.v;

INVOKER_RIGHTS_FUNCTION current_user=A current_schema=A active roles=0 data from Т=Таблица -> пользователя А Как видите, вызов процедуры с правами вызывающего непосредственно из SQL-оператора (как в нашем случае, когда мы выбирали значение функции из таблицы DUAL), ничем не отличается от непосредственного вызова. Более того, вызов функции из представления, как во втором запросе, показывает, что она ведет себя так же, как при вызове из процедуры с правами создателя, поскольку представления всегда сохраняются с правами создателя.

Компиляция процедуры с правами вызывающего А сейчас разберемся, что происходит при компиляции и сохранении в базе данных процедуры с правами вызывающего. Может показаться странным, но происходит в точности то же, что и при компиляции процедуры с правами создателя. Выполняются следующие шаги: • Проверяется существование всех объектов, к которым процедура обращается статически (всех, к которым она не обращается с помощью динамического SQL). Имена разрешаются с помощью стандартных правил области действия по отношению к владельцу процедуры. Роли не учитываются. • Проверяется, все ли объекты доступны в нужном режиме. Например, если выполняется оператор UPDATE T, сервер Oracle проверит, может ли создатель или роль PUBLIC выполнять UPDATE T непосредственно, не используя ролей.

Права вызывающего и создателя • Устанавливается и поддерживается зависимость процедуры от объектов, на которые она ссылается. Если процедура выполняет оператор SELECT FROM T, регистрируется зависимость процедуры от таблицы Т. Это означает, что подпрограмма с правами вызывающего при компиляции обрабатывается точно так же, как подпрограмма с правами создателя. Многих это сбивает с толку. Они слышали, что процедуры с правами вызывающего используют роли, и это так. Но (повторяю) они не используются в ходе компиляции. Это означает, что пользователь, компилирующий хранимую процедуру, ее владелец, должен иметь непосредственный доступ ко всем статически используемым таблицам. Вспомните пример из раздела "Права создателя", где было показано, что можно успешно выполнить SELECT COUNT(*) FROM ЕМР в SQL и в анонимном блоке PL/SQL, но такой же оператор в хранимой процедуре приводит к ошибке компиляции. То же самое произойдет и в подпрограмме с правами вызывающего. Правила, сформулированные в разделе Privileges Required to Create Procedures and Functions руководства Oracle 8i Application Developer's Guide остаются в силе: все равно необходим непосредственный доступ к базовым объектам. Причина — в механизме зависимостей, используемом сервером Oracle. Если действие, выполняемое в базе данных (например, оператор REVOKE), делает процедуру с правами создателя недействительной, аналогичная процедура с правами вызывающего тоже становится недействительной. Различие между процедурами с правами вызывающего и создателя наблюдается только при выполнении. С точки зрения зависимостей, пометки процедур как недействительных и привилегий, необходимых владельцу процедуры, никаких различий нет. Эту проблему можно обойти, и для большинства процедур с правами вызывающего проблема эта вообще не актуальна. Однако из-за нее иногда приходится создавать объекты-шаблоны. В следующем разделе мы рассмотрим, что такое объекты-шаблоны и как их использовать, чтобы избежать необходимости предоставления непосредственных привилегий.

Использование объектов-шаблонов Теперь, зная, что при компиляции процедура с правами вызывающего не отличается от процедуры с правами создателя, можно понять, почему необходим непосредственный доступ ко всем объектам. При разработке процедур с правами вызывающего, в которых предполагается использование ролей, создателю необходимы непосредственные привилегии, а не роли. Их получение может оказаться невозможным по любой причине Достаточно, чтобы кто-то решил: "Привилегии seIect на эту таблицу я не дам"), и придется искать решение. Тут пригодятся объекты-шаблоны. Объект-шаблон — это объект, к которому пользователь-создатель имеет непосредственный доступ и по структуре совпадающий с объектом, к которому предполагается обращаться при выполнении. Его можно рассматривать как конструкцию struct языка С, Java-класс, PL/SQL-запись или структуру данных. Он создается для того, чтобы сервер знал количество и типы столбцов, и другие свойства объекта. Рассмотри это на примере. Предположим, необходимо создать процедуру, обращающуюся к представлению DBA_USERS и выдающую в удобном формате оператор CREATE USER для любого существующего пользователя. Можно попытаться создать эту процедуру, например, так:

Глава tkyte@TKYTE816> create or replace 2 procedure show_user_info(p_username in varchar2) 3 AUTHID CURRENT_USER 4 as 5 l_rec dba_users%rowtype;

6 begin 7 select * 8 into l_rec 9 from dba_users 10 where username = upper(p_username);

11 12 dbms_output.put_line('create user ' || p_username);

13 if (l_rec.password = 'EXTERNAL') then 14 dbms_output.put_line(' identified externally');

15 else 16 dbms_output.put_line 17 ( identified by values ''' || l_rec.password || '''');

' 18 end if;

19 dbms_output.put_line 20 (' temporary tablespace ' || l_rec.temporary_tablespace || 21 ' default tablespace ' || l_rec.default_tablespace || 22 ' profile ' || l_rec.profile);

23 exception 24 when no_data_found then 25 dbms_output.put_line('*** Нет такого пользователя: ' II -> p_username) ;

26 end;

27 / Warning: Procedure created with compilation errors. tkyte@TKYTE816> show err Errors for PROCEDURE SHOW_USER_INPO: LINE/COL ERROR 4/13 4/13 6/5 8/12 12/5 12/10 18/5 19/ PLS-00201: identifier 'SYS.DBA_OSERS' must be declared PL/SQL: Item ignored PL/SQL: SQL Statement ignored PLS-00201: identifier 'SYS.DBA_USERS' must be declared PL/SQL: Statement ignored PLS-00320: the declaration of the type of this expression is incomplete or malformed PL/SQL: Statement ignored PLS-00320: the declaration of the type of this expression is incomplete or malformed Эта процедура не компилируется, не потому, что не существует объект SYS.DBA_USERS, а потому, что обращаться к DBA_USERS мы можем только благодаря предоставленной роли, а в ходе компиляции хранимой процедуры роли не используются. Так что же сделать, чтобы эта процедура скомпилировалась? Для этого можно создать собственную таблицу DBA_USERS. Это позволит успешно скомпилировать процедуру. Однако no Права вызывающего и создателя скольку это не "реальная" таблица DBA_USERS, желаемые результаты при выполнении не будут получены, пока мы не выполним процедуру от имени другого пользователя, который может обращаться к реальному представлению DBA_USERS: tkyte@TKYTE816> create table dba_users 2 as 3 select * from SYS.dba_users where 1=0;

Table created. tkyte@TKYTE816> alter procedure show_user_info compile;

Procedure altered. tkyte@TKYTE816> exec show_user_info(USER);

*** Нет такого пользователя TKYTE PL/SQL procedure successfully completed. tkyte6TKYTE816> connect system/manager system@TKYTE816> exec tkyte.show_user_info('TKYTE') create user TKYTE identified by values '698FlE51F530CA57' temporary tablespace TEMP default tablespace DATA profile DEFAULT PL/SQL procedure successfully completed. Теперь мы получили процедуру, которая, при вызове любым пользователем, кроме создателя, обращается к "правильному" представлению DBA_USERS (если вызывающий не имеет права обращаться к DBA_USERS, он получит сообщение о том, что таблица или представление не существует). Если же процедуру выполняет создатель, он получает сообщение "Нет такого пользователя", поскольку у него объект-шаблон DBA_USERS пустой. Все остальные пользователи, однако, получают ожидаемые результаты. Во многих случаях это вполне приемлемо. Например, когда предполагается работа одного и того же кода с разными таблицами. В данном случае, однако, хотелось бы, чтобы эта процедура всегда работала с одним представлением, DBA_USERS. Итак, возвращаемся к тому, как обеспечить работу этой процедуры для всех пользователей, включая создателя? Надо использовать объект-шаблон другого типа. Создадим таблицу, структурно совпадающую с представлением DBA_USERS, но с другим именем, скажем, DBA_USERS_TEMPLATE. Используем эту таблицу для определения типа записи, в которую выбираются данные. После этого мы сможем динамически обращаться к представлению DBA_USERS во всех случаях: system@TKYTE816> connect tkyte/tkyte tkyte@TKYTE816> drop table dba_users;

Table dropped. tkyte@TKYTE816> create table dba_users_TEMPLATE 2 as 3 select * from SYS.dba_users where 1=0,Table created. tkyte@TKYTE816> create or replace 2 procedure show_user_info(p_username in varchar2) Глава 3 AUTHID CURRENT_USER 4 as 5 type rc is ref cursor;

6 7 l_rec dba_users_TEMPLATE%rowtype;

8 l_cursor rc;

9 begin 10 open l_cursor for 11 'select * 12 from dba_users 13 where username = :x' 14 USING upper(p_username);

15 16 fetch l_cursor into l_rec;

17 if (l_cursor%found) then 18 19 dbms_output.put_line('create user ' || p _ u s e r n a m e ) ;

20 if (l_rec.password = 'EXTERNAL') then 21 dbms_output.put_line(' identified externally');

22 else 23 dbms_output.put_line 24 (' identified b y values ''' || l_rec.password '''');

25 end if;

26 dbms_output.put_line 27 (' temporary tablespace ' || l_rec.temporary_tablespace || 28 ' default tablespace ' || l_rec.default_tablespace || 29 ' profile ' || l_rec.profile);

30 else 31 dbms_output.put_line( '*** Н е т такого пользователя: ' || - > p_username ) ;

32 end if;

33 close l_cursor;

34 end;

35 / Procedure created. tkyte@TKYTE816> exec show_user_info(USER);

create user TKYTE identified by values '698FlE51F530CA57' temporary tablespace TEMP default tablespace DATA profile DEFAULT PL/SQL procedure successfully completed. Итак, в данном случае мы использовали таблицу DBA_USERS_TEMPLATE только для того, чтобы упростить создание типа записи, в которую будут выбираться данные. Можно было бы получить описание представления DBA_USERS и создать тип записи со всеми соответствующими полями, но мне больше нравится, когда работу за меня делает сервер. После перехода на новую версию Oracle достаточно будет просто пересоздать таблицу-шаблон и процедура перекомпилируется автоматически, при этом все новые/дополнительные столбцы или изменения типов данных будут автоматически учтены.

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

Права вызывающего и использование разделяемого пула При использовании прав вызывающего для обеспечения доступа одной процедуры к данным в различных схемах, в зависимости от того, кто ее вызывает, следует помнить, что это достигается за счет менее эффективного использования разделяемого пула. При использовании процедур с правами создателя для каждого запроса в процедуре в разделяемом пуле будет не более одного экземпляра соответствующего SQL-оператора. Процедуры с правами создателя максимально эффективно применяют возможности совместного использования SQL-операторов (почему это принципиально важно, см. в главе 10). Процедуры с правами вызывающего подобную эффективность могут не обеспечивать. Это не хорошо и не плохо. Это просто надо учитывать при задании размера разделяемого пула. При использовании процедур с правами вызывающего мы будем использовать разделяемый пул аналогично тому, как это делает клиент-серверное приложение, использующее интерфейс ODBC или JDBC и непосредственно посылающее серверу операторы ЯМД. Каждый пользователь будет выполнять запрос с одним и тем же текстом, но запросы эти могут различаться. Так что, хотя все выполняют SELECT * FROM T, но поскольку таблицы T у всех пользователей разные, для каждого пользователя будет создаваться и помещаться в разделяемый пул отдельный план запроса и другая соответствующая информация. Это необходимо, поскольку каждый раз используется другая таблица T, с другими правами и планами доступа. Влияние на разделяемый пул легко продемонстрировать на примере. Я создал в одной схеме следующие объекты: tkyte@TKYTE816> create table t (x int);

Table created. tkyte@TKYTE816> create table t2 (x int);

Table created. tkyte@TKYTE816> create public synonym T for T;

Synonym created. tkyte@TKYTE816> create or replace procedure dr_proc 2 as 3 l_cnt number;

4 begin 5 select count(*) into l_cnt from t DEMO_DR;

6 end;

7/ Procedure created.

1540 Глава tkyte@TKYTE816> create or replace procedure ir_procl 2 authid current_user 3 as 4 l_cnt number;

5 begin 6 select count(*) into l_cnt from t DEMO_IR_1;

7 end;

8/ Procedure created. tkyte@TKYTE816> create or replace procedure ir_proc2 2 authid current_user 3 as 4 l_cnt number;

5 begin 6 select count(*) into l_cnt from tkyte.t DEMO_IR_2;

7 end;

8/ Procedure created. tkyte@TKYTE816> create or replace procedure ir_proc3 2 authid current_user 3 as 4 l_cnt number;

5 begin 6 select count(*) into l_cnt from t2 DEMO_IR_3;

7 end;

8 / Procedure created. tkyte@TKYTE816> grant select on t to public;

Grant succeeded. tkyte@TKYTE816> grant execute on dr_proc to public;

Grant succeeded. tkyte@TKYTE816> grant execute on ir_procl to public;

Grant succeeded. tkyte@TKYTE816> grant execute on ir_proc2 to public;

Grant succeeded. tkyte@TKYTE816> grant execute on ir_proc3 to public;

Grant succeeded. Мы создали две таблицы, T и T2. Существует также общедоступный синоним Т для таблицы TKYTE.T. Все четыре процедуры обращаются либо к таблице T, либо к таблице T2. Для процедуры с правами создателя, статически связываемой при компиляции, уточнять имя таблицы именем схемы не надо. Процедура с правами вызывающего, IR_PROC1, будет обращаться к таблице T через общедоступный синоним. Вторая процедура, IR_PROC2, будет использовать полностью уточненную ссылку, а третья процедура, IR_PROC3, будет обращаться к T2 без уточнения схемы. Обратите внимание, что Права вызывающего и создателя общедоступного синонима для таблицы T2 нет: я намеренно сделал, чтобы процедура IR_PROC3 при выполнении обращалась к разным таблицам T2. Затем я создал десять пользователей с помощью следующего сценария: tkyte@TKYTE816> begin 2 for i in 1.. 10 loop 3 begin 4 execute immediate 'drop user u' || i || ' cascade';

5 exception 6 when others then null;

7 end;

8 execute immediate 'create user u'||i || ' identified by pw';

9 execute immediate 'grant create session, create table to u'||i;

10 execute immediate 'alter user u' || i || ' default tablespace 11 data quota unlimited on data';

12 end loop;

13 end;

14 / PL/SQL procedure successfully completed. и для каждого пользователя мы выполняем: create table t2 (x int);

exec tkyte.dr_proc exec tkyte.ir_procl exec tkyte.ir_proc2 exec tkyte.ir_proc3 Необходимо зарегистрироваться как очередной пользователь, создать таблицу T2, а затем выполнить четыре интересующих нас процедуры. После того как это сделано от имени всех десяти пользователей, можно исследовать содержимое разделяемого пула с помощью представления V$SQLAREA, используя представленную ранее в этой главе процедуру PRINT_TABLE: tkyte@TKYTE816> set serveroutput on size 1000000 tkyte@TKYTE816> begin 2 print_table ('select sql_text, sharable_mem, version_count, 3 loaded_versions, parse_calls, optimizer_mode 4 from v$sqlarea 5 where sql_text like "% DEMO\R%" escape " \ " 6 and lower(sql_text) not like "%v$sqlarea%'' ' ) ;

7 end;

8/ SQL_TEXT DEMO_IR_2 SHARABLE_MEM VERSION_COUNT LOADED_VERSIONS PARSE_CALLS OPTIMIZER_MODE SQL_TEXT SHARABLE MEM : SELECT : : : : : 4450 1 1 10 CHOOSE FROM T DEMO_DR COUNT(*) FROM OPS$TKYTE.T : SELECT COUNT(*) : Глава VERSION_COUNT LOADED_VERSIONS PARSE_CALLS OPTIMIZER_MODE SQL_TEXT SHARABLE_MEM VERSION_COUNT LOADED_VERSIONS PARSE_CALLS OPTIMIZER_M0DE SQL_TEXT SHARABLE_MEM VERSION_COUNT LOADED_VERSIONS PARSE_CALLS OPTIMIZER_MODE : : : :

1 1 10 CHOOSE FROM T DEMO_IR_ : SELECT COUNT(*) : 4212 :1 :1 : 10 : CHOOSE : SELECT COUNT(*) FROM T2 DEMO_IR_3 : 31941 : 10 : 10 : 10 : MULTIPLE CHILDREN PRESENT PL/SQL procedure successfully completed. Хотя SQL-оператор во всех случаях один и тот же — SELECT COUNT(*) FROMT2 DEMO_IR_3, — в разделяемом пуле для него есть десять разных экземпляров кода. Каждому пользователю необходим собственный оптимизированный план выполнения, поскольку запрос ссылается на разные объекты. В тех случаях, когда базовые объекты совпадали и привилегий хватало, планы выполнения SQL-операторов использовались совместно, как и ожидалось. Итак, если с помощью прав вызывающего вы собираетесь использовать один экземпляр кода для доступа к нескольким различным схемам, необходимо увеличить разделяемый пул, чтобы он вмещал все планы выполнения запросов. Так мы подходим кследующей проблеме.

Производительность При использовании процедур с правами вызывающего, как вы уже знаете, каждому пользователю может потребоваться отдельный специфический план выполнения запроса. На построение этих дополнительных планов могут потребоваться существенные ресурсы. Анализ запроса — одно из наиболее интенсивно нагружающих процессор действий сервера. "Стоимость" анализа уникальных запросов, который возможен при использовании подпрограмм с правами вызывающего, можно продемонстрировать с помощью утилиты TKPROF, показывающей продолжительность анализа операторов. Для выполнения следующего примера необходима привилегия ALTER SYSTEM: tkyte@TKYTE816> alter system flush shared_pool;

System altered. tkyte@TKYTE816> alter system set timed_statistics=true;

System altered. tkyte@TKYTE816> alter session set sql trace=true;

Права вызывающего и создателя Session altered. tkyte@TKYTE816> declare 2 type rc is ref cursor;

3 l_cursor rc;

4 begin 5 for i in 1.. 500 loop 6 open l_cursor for 'select * from all_objects t' || i;

7 close l_cursor;

8 end loop;

9 end;

10 / PL/SQL procedure successfully completed. При этом был выполнен анализ 500 уникальных операторов (в них используются уникальные псевдонимы таблицы). Ситуация аналогична использованию одной процедуры с правами вызывающего 500 пользователями в 500 различных схемах. В итоговом отчете утилиты TKPROF для этого сеанса можно найти следующее: OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS call Parse Execute Fetch total count 1148 1229 1013 3390 cpu 17.95 0.29 0.14 18.38 elapsed 18.03 0.25 0.17 18.45 dj.sk 0 0 0 0 query 55 0 2176 2231 current 15 0 0 15 rows 0 0 888 Misses in library cache during parse: 536 504 user SQL statements in session. 648 internal SQL statements in session. 1152 SQL statements in session. 0 statements EXPLAINed in this session. Теперь выполним блок, не анализирующий уникальный оператор 500 раз: tkyte@TKYTE816> alter system flush shared_pool;

System altered. tkyte@TKYTE816> alter system set timed_statistics=true;

System altered. tkyte@TKYTE816> alter session set sql_trace=true;

Session altered. tkyte@TKYTE816> declare 2 type rc is ref cursor;

3 l_cursor rc;

4 begin 5 for i in 1.. 500 loop 6 open l_cursor for 'select * from all_objects t';

7 close l_cursor;

Глава 8 end loop;

9 end;

10 / PL/SQL procedure successfully completed.

и в отчете T K P R O F увидим:

OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS call Parse Execute Fetch total 504 114 618 0 count 614 671 358 1643 cpu 0.74 0.09 0.08 0.91 elapsed 0.53 0.31 0.04 0.88 disk, query current 1 0 8 9 55 0 830 885 9 0 0 9 rows 0 0 272 Misses in library cache during parse: 22 user SQL statements in session. internal SQL statements in session. SQL statements in session. statements EXPLAINed in this session.

Разница огромна. Для анализа 500 уникальных операторов (эмулирующих поведение процедуры с правами вызывающего, которая обращается при каждом вызове к другой таблице) требуется 17,95 секунд процессорного времени. Для анализа же 500 одинаковых операторов (эмулирующих использование стандартной процедуры с правами создателя) понадобилось 0,74 секунды процессорного времени. В 24 раза меньше! Конечно, это надо учитывать. Когда SQL-операторы не используются повторно, система может тратить больше времени на анализ запросов, чем на их фактическое выполнение. Причины этого были рассмотрены в главе 10, посвященной стратегиям и средствам настройки производительности. Там я продемонстрировал необходимость использования связываемых переменных для повторного использования планов запросов. Однако это не повод отказываться от использования процедур с правами вызывающего. Используйте их, но помните о последствиях.

Более надежный код для обработки ошибок При создании хранимой процедуры со следующим кодом: begin for x in (select pk from t) loop update y set c - c+0.5 where d = x.pk;

end loop;

end;

вполне можно быть уверенным, что при отсутствии синтаксических и семантических ошибок (компилируется успешно) она будет работать. При использовании процедур с Права вызывающего и создателя правами создателя это верно. Я точно знаю, что объекты (таблицы или представления) T и Y существуют, что T доступен для чтения, a Y можно изменять. При использовании процедуры с правами вызывающего, ни в чем нельзя быть уверенным. Существует ли объект T, и если — да, то имеется ли в нем столбец с именем PK? И имею ли я для него привилегию SELECT? А если имею, то не через роль ли она получена? Ведь тогда при вызове процедуры из подпрограммы с правами создателя, она не сработает, хотя при непосредственном вызове будет работать прекрасно. Существует ли объект Y? И так далее. Другими словами, все условия, которые раньше можно было считать гарантированно выполненными, вызывают сомнению в процедурах с правами вызывающего. Так что, хотя процедуры с правами вызывающего и открывают новые возможности программирования, в некотором отношении они его усложняют. При использовании представленного выше кода надо готовиться к обработке множества вполне вероятных случаев: • объекта T нет;

• объект T есть, но нет необходимых для доступа к нему привилегий;

• объект T есть, но в нем нет столбца PK;

• объект T существует и имеет столбец PK, но тип данных столбца отличается от использованного при компиляции;

• все то же в отношении объекта Y. Поскольку изменение объекта Y происходит только при получении определенных данных из T, мы можем многократно успешно выполнить эту процедуру, но однажды, когда в T будут помещены данные, процедура не сработает. Мы никогда не могли обратиться к объекту Y, но процедура не сработала потому, что мы впервые "попытались". Ошибка во фрагменте кода произойдет только тогда, когда он выполнится. Для получения "надежной" процедуры, перехватывающей все возможные ошибки, необходим примерно такой код: create or replace procedure P authid current_user as no_such_table exception;

pragma exception_init(no_such_table,-942);

insufficient_privs exception;

pragma exception_init(insufficient_privs,-1031);

invalid_column_name exception;

pragma exception_init(invalid_column_name,-904);

inconsistent_datatypes exception;

pragma exception_init(inconsistent_datatypes,-932);

begin for x in (select pk from t) loop update y set c = c+0.5 where d = x.pk;

end loop;

exception when NO_SUCH_TABLE then dbms_output.put_line('Перехвачена ошибка: ' || sqlerrm);

Глава when INSUFFICIENT_PRIVS then dbms_output.put_line('Перехвачена ошибка: When INVALID_COLUMN_NAME then dbms_output.put_line('Перехвачена ошибка: when INCONSISTENT_DATATYPES then dbms_output.put_line('Перехвачена ошибка: —... (дальше идет множество других обработчиков end;

/ ' || sqlerrm);

' || sqlerrm);

' || sqlerrm);

ошибок)...

Побочные эффекты использования SELECT * Использование конструкции SELECT * в PL/SQL-процедуре с правами вызывающего, обращающейся к разным таблицам при вызове разными пользователями, может быть очень опасно. При этом данные могут быть получены "поврежденными" или в другом порядке. Причина в том, что запись, в которую выполняется выборка данных, настраивается при компиляции, а не при выполнении. Поэтому список столбцов для PL/SQL-объектов (записей) вместо * формируется при компиляции, а данные получаются при выполнении запроса. Если в другой схеме имеется объект с тем же именем, но с другим порядком столбцов и к нему обращаются из процедуры с правами вызывающего с помощью оператора SELECT *, возникает именно такой побочный эффект: tkyte@TKYTE816> create table t (msg varchar2(25), c1 int, c2 int);

Table created. tkyte@TKYTE816> insert into t values ('c1=l, c2=2', 1, 2 ) ;

1 row created. tkyte@TKYTE816> create or replace procedure P 2 authid current_user 3 as 4 begin 5 for x in (select * from t) loop 6 dbms_output.put_line('msg= ' || x.msg);

7 dbms_output.put_line('C1 = ' || x.cl);

8 dbms_output.put_line('C2 = ' || x.c2);

9 end loop;

10 end;

11 / Procedure created. tkyte@TKYTE816> exec p msg= cl=l, c2=2 C1 = 1 C2 = 2 PL/SQL procedure successfully completed. tkyte@TKYTE816> grant execute on P to u1;

Grant succeeded.

Права вызывающего и создателя Итак, мы создали процедуру, показывающую содержимое таблицы Т. Значение которое она выдает в столбце MSG, я использую, чтобы продемонстрировать предполагаемый ответ. Кроме того, она выдает значения столбцов C1 и C2. Все просто и понятно. Теперь давайте посмотрим, что произойдет, если выполнить процедуру от имени другого пользователя, со своей собственной таблицей T: tkyte@TKYTE816> @connect ul/pw ul@TKYTE816> drop table t;

Table dropped. ul@TKYTE816> create table t (msg varchar2(25), c2 int, c1 int);

Table created. ul@TKYTE816> insert into t values ('cl=2, c2=l', 1, 2 ) ;

1 row created. Обратите внимание, что при создании таблицы я изменил порядок столбцов C1 и C2. Здесь я предполагаю, что C1 = 2 и C2 = 1. При выполнении процедуры, однако, получаем следующее: ul@TKYTE816> exec tkyte.p msg= cl=2, c2=l C1 = 1 C2 = 2 PL/SQL procedure successfully completed. Не совсем так, как ожидалось, но если вдуматься глубже... При компиляции была автоматически создана неявная запись X. Запись X — это просто структура данных с тремя элементами — MSG VARCHAR2, C1 NUMBER и C2 NUMBER. Когда список столбцов SELECT * формировался на этапе анализа запроса от имени пользователя TKYTE, были получены столбцы MSG, C1 и C2 (именно в таком порядке). При выполнении процедуры пользователем U1 были, однако, получены столбцы MSG, C2 и C1. Поскольку все типы данных совпадают с типами полей неявно созданной записи X, мы не получили сообщения об ошибке INCONSISTENT DATATYPE (это тоже могло произойти, если бы типы данных оказались несовместимыми, с точностью до неявного преобразования). Данные были успешно выбраны, но значение столбца C2 помещено в поле записи C1. Это вполне предсказуемый побочный эффект и еще одна причина не использовать операторы SELECT * в производственном коде.

Помните о "скрытых" столбцах Это очень похоже на представленную ранее проблему SELECT *. Она тоже связана с тем, как компилируется PL/SQL-процедура с правами вызывающего и как разрешаются имена и ссылки на объекты. Рассмотрим оператор UPDATE, который при выполнении непосредственно в среде SQL*Plus даст другой результат, чем при выполнении в подпрограмме с правами вызывающего. Он работает "правильно" в обеих средах, просто — по-разному. Когда PL/SQL-код компилируется в базе данных, каждый статический SQL-оператор анализируется и уточняются все идентификаторы. Эти идентификаторы могут быть Глава именами столбцов или именами PL/SQL-переменных (связываемых переменных). Если это имена столбцов, они оставляются в запросе без изменений. Если же это имена переменных PL/SQL, они заменяются в запросе ссылкой :BIND_VARIABLE. Эта замена производится при компиляции, а не при выполнении. Рассмотрим пример: tkyte@TKYTE816> create table t (cl int);

Table created. tkyte@TKYTE816> insert into t values (1);

1 row created. tkyte@TKYTE816> create or replace procedure P 2 authid current_user 3 as 4 c2 number default 5;

5 begin 6 update t set cl = c2;

7 end;

8 / Procedure created. tkyte@TKYTE816> exec p PL/SQL procedure successfully completed. tkyte@TKYTE816> select * from t;

Cl 5 tkyte@TKYTE816> grant execute on P to ul;

Grant succeeded. Пока все выглядит нормально. C1 — это столбец в таблице T, a C2 — переменная PL/SQL. Оператор UPDATE T SET C1 = C2 обрабатывается сервером при компиляции и преобразуется в UPDATE T SET C1 = :BIND_VARIABLE, а значение :BIND_VARIABLE передается при выполнении. Теперь, если зарегистрироваться как U1, и создать в этой схеме таблицу T: tkyte@TKYTE816> connect ul/pw ui@TKYTE816> drop table t;

Table dropped. ul@TKYTE816> create table t (cl int, c2 int);

Table created. ul@TKYTE816> insert into t values (1, 2 ) ;

1 row created. ul@TKYTE816> exec tkyte.p PL/SQL procedure successfully completed. ul@TKYTE816> s e l e c t * from t;

Права вызывающего и создателя C1 C2 Это может показаться правильным или неправильным — смотря как к этому подойти. Мы выполнили оператор UPDATE T SET C1 = C2, но если бы мы это сделали в командной строке SQL*Plus, то столбец C1 получил бы значение 2, а не 5. Однако, поскольку сервер переписал этот запрос при перекомпиляции так, что в нем не осталось ссылок на C2, он делает то же самое с нашим экземпляром T, что и с другим экземпляром T: устанавливает столбцу C1 значение 5. Эта PL/SQL-процедура не может "видеть" столбец C2, поскольку C2 не существует в объекте, с которым она была скомпилирована. Сначала это кажется странным, поскольку оператора UPDATE в переписанном виде мы обычно не видим, но если вы знаете об этом, результат становится вполне объяснимым.

Java и права вызывающего PL/SQL-процедуры по умолчанию компилируются с правами создателя. Чтобы такая процедура работала с правами вызывающего, надо это специально указать. Java-процедура по умолчанию работает с правами вызывающего. Если необходимо, чтобы она выполнялась с правами создателя, надо явно указать это при загрузке. В качестве примера я создал таблицу T, такую, что: ops$tkyte@DEV816> create table t (msg varchar2(50));

Table created. ops$tkyte@DEV816> insert into t values ('Это таблица Т, принадлежащая -> пользователю ' || user);

1 row created. Я также создал и загрузил две хранимые процедуры на Java (для выполнения этого примера необходима привилегия CREATE PUBLIC SYNONYM). Эти хранимые процедуры на Java очень похожи на рассмотренные ранее примеры PL/SQL. Они обращаются к таблице T, которая содержит строку, со сведениями о том, кому "принадлежит" эта таблица, и выдают информацию о пользователе сеанса, о текущем пользователе (схеме, определяющей привилегии) и текущей схеме: tkyte@TKYTE816> host type ir_java.java import java.sql.*;

import oracle.jdbc.driver.*;

public class ir_java { public static void test() throws SQLException { Connection cnx = new OracleDriver().defaultConnection();

String sql = "SELECT MSG, sys_context('userenv','session_user'), "+ "sys_context('userenv','current_user'), "+ Глава "sys_context('userenv','current_schema') "+ "FROM T";

Statement stmt = cnx.createStatement();

ResultSet rset = stmt.executeQuery(sql);

if (rset.next()) System.out.println( rset.getString(l) + " session_user=" + rset.getString(2)+ " current_user=" + rset.getString(3)+ " current_schema=" + rset.getString(4));

rset.close();

stmt.close();

} } tkyte@TKYTE816> host dropjava -user tkyte/tkyte ir_java.java tkyte@TKYTE816> host loadjava -user tkyte/tkyte -synonym -grant u1 verbose -resolve ir_java.java initialization complete loading : ir_java creating : ir_java resolver :

resolving: ir_java synonym : ir_java Представленная выше подпрограмма загружается с правами вызывающего. Теперь загрузим ту же подпрограмму с другим именем. При загрузке подпрограммы с помощью loadjava укажем, что она должна выполняться с правами создателя: tkyte@TKYTE816> host type dr_java.java import java.sql.*;

import oracle.jdbc.driver.*;

public class dr_java {... тот же код, что и в предыдущем примере... } tkyte@TKYTE816> host dropjava -user tkyte/tkyte dr_java.java tkyte@TKYTE816> host loadjava -user tkyte/tkyte -synonym -definer -grant u1 -verbose -resolve dr_jav initialization complete loading : dr_java creating : dr_java resolver : resolving: dr_java synonym : dr_java Итак, отличия между IR_JAVA и DR_JAVA — имена классов и тот факт, что подпрограмма DR_JAVA была загружена с опцией -definer. Затем я создал спецификацию вызова PL/SQL, чтобы можно было выполнять эти процедуры из SQL*Plus. Обратите внимание, что создано четыре версии. Все вызовы хранимых процедур на Java выполняются только через SQL-уровень. Поскольку SQL-ypo Права вызывающего и создателя вень, фактически, представляет собой интерфейсную PL/SQL-подпрограмму, в ней тоже можно задавать конструкцию AUTHID. Надо разобраться, что происходит, когда из подпрограммы с правами вызывающего/создателя на уровне PL/SQL вызывается Java-процедура с правами вызывающего/создателя: tkyte@TKYTE816> create OR replace procedure ir_ir_java 2 authid current_user 3 as language java name 'ir_java.test()';

4/ Procedure created. tkyte@TKYTE816> grant execute on ir_ir_java to u1;

Grant succeeded. tkyte0TKYTE816> create OR replace procedure dr_ir_java 2 as language java name 'ir_java.test()';

3 / Procedure created. tkyte@TKYTE816> grant execute on dr_ir_java to u1;

Grant succeeded. tkyte@TKYTE816> create OR replace procedure ir_dr_java 2 authid current_user 3 as language java name 'dr_java.test()';

4/ Procedure created. tkyte@TKYTE816> grant execute on ir_dr_java to u1;

Grant succeeded. tkyte@TKYTE816> create OR replace procedure dr_dr_java 2 authid current_user 3 as language java name 'dr_java.test()';

4/ Procedure created. tkyte@TKYTE816> grant execute on dr_dr_java to u1;

Grant succeeded.

Теперь необходимо создать и наполнить данными таблицу T в схеме TKYTE:

tkyte@TKYTE816> drop table t;

Table dropped. tkyte@TKYTE816> create table t (msg varchar2(50));

Table created. tkyte@TKYTE816> insert into t values ('Это таблица Т, принадлежащая -> пользователю ' || user);

1 row created. Итак, теперь мы готовы к проверке выполнения от имени пользователя U1, у которого сейчас появится таблица T со строкой, тоже идентифицирующей владельца:

Глава tkyte@TKYTE816> @connect ul/pw ul@TKYTE816> drop table t;

Table dropped. ul@TKYTE816> create table t (msg varchar2(50));

Table created. ul@TKYTE816> insert into t values ('Это таблица Т, принадлежащая -> пользователю ' || user);

1 row created. ul@TKYTE816> set serveroutput on size 1000000 ul@TKYTE816> exec dbms_java.set_output(1000000);

PL/SQL procedure successfully completed. ul@TKYTE816> exec tkyte.ir_ir_java Это таблица Т, принадлежащая пользователю U1 session_user=U1 current_user=U1 current_schema=U1 PL/SQL procedure successfully completed. Это показывает, что, когда хранимая Java-процедура с правами вызывающего вызывается из PL/SQL-процедуры с правами вызывающего, она работает как процедура с правами вызывающего. Текущий пользователь и текущая схема — U1, SQL-оператор в хранимой Java-процедуре, обращается к таблице U1.T, а не TKYTE.T. Теперь давайте вызовем тот же Java-код из процедуры с правами создателя: ul@TKYTE816> exec tkyte.dr_ir_java Это таблица Т, принадлежащая пользователю TKYTE session_user=U1 current_user=TKYTE current_schema=TKYTE PL/SQL procedure successfully completed. Теперь, хотя хранимая Java-процедура загружена с правами вызывающего, она работает как процедура с правами создателя. Это вполне можно было предвидеть исходя из того предыдущих примеров. Процедура с правам вызывающего, вызываемая из процедуры с правами создателя, работает аналогично процедуре с правами создателя. Роли не учитываются;

текущая схема фиксируется статически, как и текущий пользователь. Эта процедура обращается к таблице TKYTE.T, а не к Ul.T, как в предыдущем примере, а текущий пользователь и схема имеет фиксированное значение — TKYTE. Давайте рассмотрим, что произойдет, если PL/SQL-процедура с правами вызывающего вызывает хранимую Java-процедуру, загруженную с правами создателя: ul@TKYTE816> exec tkyte.ir_dr_java Это таблица Т, принадлежащая пользователю TKYTE session_user=U1 current_user=TKYTE current_schema =TKYTE PL/SQL procedure successfully completed. Это показывает, что если Java-процедура загружена с опцией -definer, она работает с правами создателя, даже если вызывается из процедуры с правами вызывающего. Результат последнего примера теперь уже очевиден. Из PL/SQL-процедуры с правами создателя вызывается Java-процедура с правами создателя:

Права вызывающего и создателя ul@TKYTE816> exec tkyte.dr_dr_java Это таблица Т, принадлежащая пользователю TKYTE session_user=U1 current_user=TKYTE current_schema =TKYTE PL/SQL procedure successfully completed. Разумеется, она выполняется с правами создателя. Вы можете даже не заметить, что хранимая Java-процедура загружается с правами вызывающего, поскольку вызывается она обычно из PL/SQL-подпрограмм, а они стандартно компилируются с правами создателя. Обычно Java-код загружается в ту же схему, где создается спецификация вызова;

и если она создана с правами создателя, Javaкод также работает с правами создателя. Я возьму на себя смелость предположить, что большинство пользователей не подозревает о такой особенности загрузки Java-кода, поскольку по результатам почти никогда нельзя догадаться о работе с правами вызывающего. Только если спецификация вызова на PL/SQL создана с конструкцией AUTHID CURRENT_USER, это свойство может проявиться. Еще одни случай, когда стандартная загрузка Java-кода с правами вызывающего имеет значение, — создание спецификации вызова не в той схеме, в которой загружен байткод Java. Используя тот же загруженный ранее Java-код, я создал от имени пользователя U1 несколько спецификаций вызова Java-кода из схемы пользователя TKYTE. Для этого пользователю U1 предоставлена привилегия CREATE PROCEDURE. Кроме того, используется тот факт, что при загрузке Java-кода задана опция -synonym, благодаря которой для загруженного кода создан общедоступный синоним, а также опция -grant U1, предоставившая пользователю U1 непосредственный доступ к Java-коду. Вот результат: u1@TKYTE816> create OR replace procedure ir_java 2 authid current_user 3 as language java name 'ir_java.test()';

4/ Procedure created. ul@TKYTE816> exec ir_java Это таблица Т, принадлежащая пользователю U1 session_user=Ul current_user=U1 current_schema=Ul PL/SQL procedure successfully completed. Вы видите, что процедура с правами вызывающего (процедура с правами создателя дала бы тот же результат), принадлежащая пользователю U1, выполняет SQL-операторы в Java-коде так, как если бы ее загрузил пользователь U1. Это свидетельствует о том, что Java-код загружен с правами вызывающего. В противном случае, SQL-оператор в Java-коде работал бы с разрешением имен и привилегиями пользователя TKYTE, а не U1. Следующий пример показывает работу процедуры с правами создателя, созданной в схеме U1. Java-код работает от имени TKYTE: ul@TKYTE816> create OR replace procedure dr_java 2 as language java name 'dr_java.test()1;

3/ Procedure created.

Глава ul@TKYTE816> exec dr_java Это таблица Т, принадлежащая пользователю TKYTE session_user=U1 current_user=TKYTE current_schema =TKYTE PL/SQL procedure successfully completed. Java-код, загруженный с правами создателя, работает от имени TKYTE, а не U1. Загружать Java-код с правами создателя пришлось принудительно с помощью опции -definer, поскольку свойства хранимых процедур на Java отличаются в этом отношении от свойств PL/SQL-процедур.

Pages:     | 1 |   ...   | 17 | 18 || 20 | 21 |   ...   | 24 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.