Skip to content

DBAzine.com

Sections
Personal tools
You are here: Home » 10g » Oracle10g Articles » Demystifying Application Contexts
Who Are You?
I am a:
Mainframe True Believer
Distributed Fast-tracker

[ Results | Polls ]
Votes : 3548
 

Demystifying Application Contexts

by Arup Nanda

Learn all about a reliable mechanism for passing data from within a session securely to help with a variety of cases such as Virtual Private Database, Application User Management in Connection Pools, and more.

A few months ago, I wrote an article on the Virtual Private Database (VPD) feature in Oracle Magazine, available on the Oracle Technology Network. The article had a good reception from the readers. Of course, I’m not going to reproduce the article here; from the feedback of the readers, I came to realize another powerful concept is grossly overlooked and not well understood by most users. This feature — application contexts — allows you to transfer sensitive bits of information securely across a session, and is the focus of this article.

A database with VPD restricts the rows a user sees based on his credentials. The restriction is enforced by a WHERE clause automatically appended to the query. This clause, called a predicate, is generated by a user-defined function called policy function. A VPD policy on the table EMP may create a predicate in such a way that the query

SELECT * FROM ACCOUNTS

becomes

SELECT * FROM ACCOUNTS
WHERE AM_NAME = USER

In this case, we assume that the logged-in user is also an account manager (AM). Hence, only those rows that record her username (returned by the function USER) as the Account Manager (shown by the column AM_NAME) are displayed; nothing else. So, if Scott logs in and issues this query, the query effectively becomes

SELECT * FROM ACCOUNTS
WHERE AM_NAME = ‘SCOTT’

We wanted to make sure that when the user Scott logs in, he sees only those records for which he is the account manager, not others. The automatic application of the predicate via the clause WHERE AM_NAME = ‘SCOTT’ guaranteed that. This is what we wanted, right?

Let’s pause for a moment. In this case, the requirement was rather simple: The Account Manager’s Oracle login ids were used to identify the accounts they represent. This makes sense in a client-server application in which a stand-alone client application connects to the database. Suppose the bank had a different requirement; instead of a stand-alone client application, the bank used a Web-based application in which the users log in to the application server. The application server, such as WebSphere, Weblogic, or Oracle Application Server, connected to the database using a generic userid (such as CONNPOOL) to form a connection pool, and the regular user’s browser simply used one of the existing connections from the pool. In such a case, the Oracle userid will be CONNPOOL, not SCOTT. The VPD policy would not return a useful predicate, and the query would always be rewritten to

SELECT * FROM EMP
WHERE AM_NAME = ‘CONNPOOL’

regardless of which user is logged in to the application server (e.g., SCOTT). As the readers of my earlier article pointed out, a VPD policy would be quite useless here. This simply gives credence to the model in which the application, not the database, enforces the security, they asserted.

Not so fast, I responded. I have never been a proponent of application-based security models. The application is just one of the gateways to the data; there are plenty more, including the ubiquitous SQL*Plus. What prevents a user from circumventing the application to get to the data directly? To enable the database-centric security model, the predicate must be different. What if there were a way to pass the information about the logged-in user to the database? Wouldn’t that make it possible to use VPD in all cases? For instance, if the query were rewritten to

SELECT * FROM EMP
WHERE (
AM_NAME = <oracle userid>
OR
AM_NAME = <application user id>
)

wouldn’t it enforce the same type of security? In fact, it would be far safer than application-based security enforcement. Regardless of how users connected to the database — using the application server, Web browser, SQL*Plus, TOAD, SQL Worksheet, even homegrown tools — this VPD policy would still be invoked and applied. So, the trick is to pass the extra information — the application userid — to the database reliably and securely. Note the importance of the words, the information must be passed with assurance that it has not been tampered; otherwise, Joe can pass himself off as Scott and access all that Scott has access to.

So, we need a sort of global variable to pass the information on from the application user to the database. A mechanism such as a package variable comes to mind. Here is one example:

create or replace package myvars
as
   app_userid varchar2(20);
end;

Then you simply assign the app userid to this package through

begin
   myvars.app_userid := 'SCOTT';
end;

This assignment could be done in the application after the user is authenticated. In the policy predicate, the package variable would then be referenced:

SELECT * FROM EMP
WHERE (
AM_NAME = USER
OR
AM_NAME = MYVARS.APP_USERID
)

Since a package variable, once set in a session, persists throughout the session, this works to enforce the security.

However, there is a huge security hole. The assignment of the package variable is done in the application, but what if I log in to the database as a non-critical user, and then set the package variable myself?

execute myvars.app_userid := ‘SCOTT’

And that makes me Scott immediately! Not just Scott; I can be anyone I wish to be — Tom, Dick, Harry, anyone, as long as they are named as users of the system. Thus, the security goes down the drain, and it falls back on application-based security for a foolproof implementation, argued the critics.

But, as I already mentioned, I’m not a huge fan of the model; I still think the database should handle authorization, not the application. This is a failure of the reliability aspects of the mechanism; the method of passing the information about the user was neither reliable nor secure enough. We’ve got to find a different way.

A Different Type of Global Variable

This is where application contexts come into the picture. Simply put, application contexts are analogous to global variables in the sense that once a value is assigned, they can be referenced throughout the session. However, that’s where the similarity ends. The biggest differentiator is the way the assignment is made — and that’s where the security is provided.

You may have been using application contexts without realizing it. Note the clause,

am_name = USER

Here the function USER is actually an implementation of

SYS_CONTEXT(‘USERENV’,’SESSION_USER’)

The built-in function SYS_CONEXT is a function that reads a certain part of the memory called Application Context, which can be defined by the user. The context can hold values that are set for a session and can be referenced throughout the session. A different session may hold a different value of the variable inside an application context.

Here an attribute named SESSION_USER of the application context USERENV has been accessed to get the user information. The context SYS_CONTEXT was already defined when the database was created. It has two distinct parts:

      1. Namespace
      2. Attribute

In this example, the namespace was USERENV and the attribute was SESSION_USER. The value of the attribute is set at the runtime. When the Oracle user Scott logs in, the value is set to SCOTT. Similarly, there are several other attributes of the namespace USERENV as well.

Application Contexts also resemble the table record structure — they have attributes, which are analogous to columns. The difference is the attributes are not named during creation. You define an application context as

create context acc_ctx
using set_acc_ctx;

Note the second line, “using …” carefully. This indicates that the context attributes can only be set by the procedure set_acc_ctx. Obviously, we need to create one such procedure.

   1  create procedure set_acc_ctx 
2  ( 
3      p_attribute_name in varchar2, 
4      p_attribute_value in varchar2 
5  ) is 
6  begin 
7      dbms_session.set_context( 
8         'acc_ctx', 
9          p_attribute_name,
10         p_attribute_value);
11* end;

Note line 7, through which the context attributes are set. Instead of setting a specific value, we have left a placeholder in form of a parameter, which can be passed to the procedure. Since attributes are not named at runtime, but rather, during the assignment, we can pass any attribute name and its associated value. For instance, we can pass the attribute named app_userid and its associated value, such as “SCOTT” as in:

execute set_acc_ctx (‘app_userid’,’SCOTT’)

This sets the value of the attribute app_userid to SCOTT. If after calling the procedure, you want to check the contexts set in the session, you could issue

select * from session_context
/

NAMESPACE         ATTRIBUTE  VALUE
----------------- ---------- -----
ACC_CTX           APP_USERID SCOTT
ACC_CTX           ATTRIBUTE2 VAL1

As in case of USERENV contexts, you can also check the value of a specific attribute of a specific context in a session. To check the attribute APP_USERID in ACC_CTX, you could use:

SELECT SYS_CONTEXT('ACC_CTX','APP_USERID')
FROM DUAL;

This returns the value of the attribute. If we can set the value to the application userid, the policy function in the VPD policy should return a predicate such as:

SELECT * FROM EMP
WHERE (
AM_NAME = USER
OR
AM_NAME = SYS_CONTEXT(‘ACC_CTX’,’APP_USERID’)
)

Trusted Procedure

The attribute is actually set in line seven by calling the dbms_session.set_context supplied PL/SQL procedure. So, you may ask, what is the big deal about writing another wrapper procedure around it? Can’t we just call dbms_session.set_context directly?

Let’s see ... suppose Joe tries to set the context directly:

begin
    dbms_session.set_context(
        namespace => 'acc_ctx',
        attribute => 'app_userid',
        value => 'SCOTT'

He gets an error.

ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 78
ORA-06512: at "ANANDA.SET_CLAIM_APP_CTX", line 7
ORA-06512: at line 1

The errors show insufficient privileges on dbms_session. However, granting the execute privileges to the user does not make the error go away. The user simply cannot directly set the context. This makes this procedure a trusted one — meaning that only the procedure can set the associated context. This is a very important concept in a discussion about contexts.

So far you have seen that a context can only be set using its associated trusted procedure, no other way. So, how does that make the context more secure? First, consider the mechanism to authenticate and authorize. Since executing the procedure is the only way to set the context, we can place all types of security check inside this procedure to make sure all the checks are satisfied before the context is set. If any of the checks fail, the procedure raises an error, and the context is never set. These checks can be:

      1. Making sure that the connections are coming from the right client (IP, client name, and so on)
      2. Making sure that the connections are requesting the type of data usually expected at that time of the day. For instance, brokerage transactions are not executed after the stock market closes. So, those types of transactions can be set to return a null predicate for the policy function. However, some brokerage companies use alternative exchanges such as Instinet and Island, where the transactions are permitted up to a later time; those transactions can be allowed until such time.
      3. Making sure the user making a request is supposed to be making requests from those clients. For instance, the connection pool exists for application servers appsvr1 through appsvr5, and they connect to the database using the Oracle userid CONNPOOL. The checks could ensure that the when userid is CONNPOOL, the client machines are between appsvr1 to appsvr5. The same test could be other way around as well, i.e., all connections coming from appsvr1 should be only as user CONNPOOL, and so on.

Other Uses of Application Contexts

In VPD, the predicate string is constructed by the policy function. Suppose, instead of having a dedicated account manager for an account, a department is allocated a chunk of accounts that anyone in the department can view. This makes the policy predicate look like

where account_no in (111,222,333,…etc…)

Sure, the string of account numbers separated by commas can be easily constructed by a PL/SQL procedure by looping through the numbers, but this creates a serious performance problem. Each of the strings will be unique, and a bind variable can’t be used. This increases parsing rate for the statement. As you know from any performance tuning exercise, more parsing leads to more CPU consumption, latch contention and library cache lock waits. One way to reduce is to make the latter part of the string an attribute of a context. For instance, the context acc_ctx could have an attribute named acc_list, which can be populated with the account numbers separated by commas and then the predicate can be written as:

where account_no in sys_context(‘acc_ctx.’,’acc_list’)

When the statement is parsed, it is done so as

where account_no in :b1

or something similar. This reduces hard parses, CPU cycles and shared-pool fragmentations, among other things.

Generic User Privilege Management

Now that you understand how powerful application contexts can be, let’s see more examples of it in action. One of the thorny problems in managing generic users such as those used by connection pools is deciding their privileges. Application users Scott, Joe, and John have three different types of authority levels. Scott has privileges to read any table, but update the ones in Checking Account only; Joe can read checking account customers only, and cannot update anything, and John can update any account. Since the user CONNPOOL must have all the privileges required by the application users, at the minimum, it must have the largest common denominator of the privilege list for the user to work properly. In this case, CONNPOOL must have the privilege to update all the tables, or else John can’t have the privileges he needs. However, that means that Joe also has the same privileges that John has, which Joe does not need. This is unacceptable from a security point of view. What can be done?

The answer lies in using application contexts and roles effectively. Here you need to create three roles with the associated privileges:

      1. UPDATE_ANY – update any table
      2. SELECT_ANY – select any table
      3. SELECT_CHECKING – select checking account tables only

These roles are then granted to the user CONNPOOL:

grant update_any, select_any, select_checking to connpool;

Well, you might ask, this does not resolve the issue. Instead of granting the privileges directly, we assigned it via roles, which means CONNPOOL still has the privilege to update any table, and since Joe uses a connection from the pool, he can update any table — the very thing we are trying to avoid.

The trick lies in making the roles disabled. To use the privileges granted, a user must not only be granted the role, but the role must be enabled as well. If a role is granted but disabled, the privileges are not available. Take the following example:

SQL> connect connpool/******

Connected.

SQL> Update savings.accounts set acc_name = ‘DICK’ where acc_no = 1;

1 row updated.

The user can do the operation. Now disable all the roles and retry.

SQL> set role none;

Role set.

SQL> update savings.accounts set acc_name = ‘DICK’ where acc_no = 1;

ERROR at line 1:
ORA-00942: table or view does not exist

The operation failed, since the role UPDATE_ANY, which allows the update, was not enabled. Now enable it, and retry the operation:

SQL> set role update_any;

Role set.

SQL> update savings.accounts set acc_name = ‘DICK’ where acc_no = 1;

1 row updated.

The operation was successful. The roles must be enabled to have the privileges in place; granting them to a user is not enough. However, we didn’t enable the role during the very first case; so why were the role privileges in place?

This is because of the way a role is defined for a user. A user can have one or more default roles, which means that those roles are automatically enabled when the user logs in. If the user has no default role, then a role granted to him must be explicitly enabled using the SET ROLE command. Be default, the roles granted to a user are default. When CONNPOOL was granted the roles, all of them became default roles. This can be prevented by issuing

alter user CONNPOOL default role none;

Now CONNPOOL has no default role and when it logs in no roles will be enabled. This is the trick we are going to use for the privilege management.

Before we go further, let’s see how to check the roles enabled in a session. We can check it by issuing:

select * from session_roles;

no rows selected

No roles have been enabled in the session — yet. Now, we can build a procedure to set the role:

  1  create or replace procedure set_user_role
  2  authid current_user
  3  is
  4      l_user_role   varchar2(2000);
  5  begin
  6      select sys_context('role_ctx','user_role')
  7      into l_user_role
  8      from dual;
  9      dbms_session.set_role (l_user_role);
 10  end;

In line nine, the equivalent of SET ROLE command, dbms_session.set_role, was used. It does the same thing — it enables a role in the session. Note some very important points about this procedure.

      1. First, there are no arguments to the procedure. This means that when the user calls the procedure, he does not specify the role to enable; it will be retrieved from the application context known as ROLE_CTX. The value of the attribute USER_ROLE will be read by this procedure and that role will be enabled by default.
      2. Second, the execute privileges for the user on dbms_session package are revoked are revoked; he can’t just call dbms_session.set_role (‘UPDATE_ANY’). Also, the SET command has been disabled by the product user profile tables, so SET ROLE will not work.
      3. Third, we will not let the user set the context by calling its trusted procedure. Note that this is a different context — ROLE_CTX, not APP_CTX, so we can control the access. This will be called from the context setting procedure after extensive checks. When checks are successfully completed, the attribute USER_ROLE will be set to UPDATE_ANY when the application user is John. If the application user is Joe, the application context will be set to SELECT_CHECKING.

Now let’s see this in action. Joe is trying to update some records, a clear violation of the policies.

      1. First he logs in to the database as user CONNPOOL.
      2. Then he tries to update the table, but since there are no default roles for the user CONNPOOL, the session has no privilege to do anything, and the update fails due to insufficient privileges.
      3. He tries to set the role by dbms_session.set_role(‘UPDATE_ANY’), but since CONNPOOL has no privileges on that package, it will fail.
      4. Then he realizes that application user John has privileges to update any table. He tries to pretend he is John to set the context by calling set_acc_ctx(‘acc_ctx’,’app_userid’,’JOHN’). This operation fails since the checks inside the procedure fail. The application context attribute app_userid is null and so is the attribute user_role of the context role_ctx.
      5. Then he tries to set the role by calling set_user_role, but it does nothing, since the value of role_ctx.user_role is null.

At the end of these tries, Joe is still connected as a user CONNPOOL, but with no privileges. He can, of course, log in as himself, which was allowed, anyway.

From this example, you can see how powerful application contexts can be to manage complex requirements such as the user role management.

Conclusion

In summary, application contexts are akin to global variables that are accessible in a session once set. Unlike global variables, they can be set through a special procedure known as trusted procedure, which is defined when the context is created. Since the trusted procedure is the only way to set a context, it can be designed with a variety of checks and balances to prevent unauthorized access. The applications contexts can be used in Virtual Private Databases to provide a predicate string that does not need to be hard parsed, saving considerable CPU resources. Using non-default roles for generic userids, applications can manage varied privileges for diverse sets of application users.

The type of application contexts discussed here are visible only from within a session. Since they reside in PGA, not SGA, other sessions can’t access them, making them highly secure. Another type of application contexts — global contexts — aid in development of secure, Web-based applications. Global contexts will be discussed in a later article.


Contributors : Arup Nanda
Last modified 2005-04-18 09:23 PM
Transaction Management
Reduce downtime and increase repeat sales by improving end-user experience.
Free White Paper
Database Recovery
Feeling the increased demands on data protection and storage requirements?
Download Free Report!
 
 

Powered by Plone