The mystery has finally been solved. England’s surrendering of the Ashes last winter was nothing to do with Australia being a much better cricket team. Thanks to Kevin Pietersen’s recently published Autobiography, we now know that the problem was that there were rather too many silly points in the England dressing room.
Moving swiftly on from that weak pun, the subject at hand can also be rather mystifying at first glance.
In a “traditional” Oracle Forms application, you would have one database user per application users.
Connections via the Application to the database would be done as the individual users.
It’s quite likely that database roles would be used to grant the appropriate privileges.
For applications using other web technologies, the application may interact with the database via a single account, often that of the Application Owner. Whether or not this is a good idea is probably a discussion for another time.
For now though, the question we’re asking is, how an APEX application connect to the database ?
On the face of it, it would seem that it’s pretty similar to the second of the two approaches above. APEX connects as the Parsing Schema (usually the application owner).
As Kevin will tell you, appearances can be deceiving…
For the purposes of this post, I’ll be using a simple APEX application that’s been created in it’s own workspace.
The application is called NEW_HR and uses the default APEX Authentication Scheme.
The parsing schema is defined as HR.
At this point the application consists of a login screen and a blank Home Page.
I’ve also created a Workspace Admin user called…well…let’s call it Kevin.
The database version is Oracle 11g Express Edition and the APEX version is 4.2.
This environment uses the embedded PL/SQL Gateway to manage database connections from APEX. This is the default setup on Oracle 11g XE.
Who am I ? No, really
Now, I know that there is no user called KEVIN in my database….
select count(*) from dba_users where username = 'KEVIN' / COUNT(*) ---------- 0 SQL>
…so I’d like to know who the database thinks I am when I login through my APEX app. I’d also like to check who the APEX itself thinks I am.
The first step then, is to add a couple of fields to the application Home Page…
First of all, I’ve add an HTML Region called whoami. Apart from the name I’ve just accepted the defaults.
Now to add a field to display the Application User – i.e. who APEX thinks I am.
This is a Display Only Item called P1_APEX_USER in the whoami region.
The source settings for this item are the defaults except for :
Source Used : Always, replacing any existing value in session state
Source value or expression : APP_USER
Next up is to add a field to display the database user.
The field is defined in the same way as P1_APEX_USER, except for :
Source Type : SQL Query (return single value)
and the source itself which is the following query :
select user from dual
Now, if we connect as Kevin….
…we can start to resolve our identity crisis….
So, as expected, APEX knows that Kevin is the Application user. However, the database user is not HR, rather it’s something called ANONYMOUS.
NOTE – If you’re using the Embedded PL/SQL Gateway ( the default setup for Express Edition) then you’ll be connected as ANONYMOUS. If you have the APEX Listener setup then, unless you’ve changed the default, you’ll be connected as APEX_PUBLIC_USER.
For our current purposes we can treat these accounts as synonymous from a database standpoint.
I’ll continue to refer to ANONYMOUS from here on because (a) I’m running this on XE and (b) the name has slightly more comedic potential.
Let’s find out a bit more about this user whilst trying not to worry that our application has been visited by hacktivists.
Hmmm, maybe not so much comedic potential.
The ANONYMOUS User
Looking in the database, we can confirm that ANONYMOUS is indeed a database user :
select account_status, profile, authentication_type from dba_users where username = 'ANONYMOUS' / ACCOUNT_STATUS PROFILE AUTHENTI -------------------------------- ------------------------------ -------- OPEN DEFAULT PASSWORD
Doesn’t seem to be anything out of the ordinary there.
Now let’s see what ANONYMOUS has granted to it. For good measure, we can see what objects it owns ( if any).
The query looks like this :
select 'SYSTEM PRIVILEGE' as priv_type, null as db_object, privilege from dba_sys_privs where grantee = 'ANONYMOUS' union select 'ROLE GRANTED' as priv_type, granted_role as db_object, null as privilege from dba_role_privs where grantee = 'ANONYMOUS' union select 'OBJECT PRIVILEGE' as priv_type, owner||'.'||table_name as db_object, privilege from dba_tab_privs where grantee = 'ANONYMOUS' union select 'OWNED OBJECT' as priv_type, object_name as db_object, null as privilege from dba_objects where owner = 'ANONYMOUS' order by 1,2 /
When we run it we get variations on the theme of :
PRIV_TYPE DB_OBJECT PRIVILEGE -------------------- ------------------------------------------------------------ ------------------------------ OBJECT PRIVILEGE APEX_040000.WWV_FLOW_EPG_INCLUDE_MODULES EXECUTE OBJECT PRIVILEGE APEX_040200.WWV_FLOW_EPG_INCLUDE_MODULES EXECUTE OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ ALTER OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ DELETE OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ FLASHBACK OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ INDEX OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ INSERT OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ ON COMMIT REFRESH OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ QUERY REWRITE OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ REFERENCES OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ SELECT OBJECT PRIVILEGE FLOWS_FILES.WWV_FLOW_FILE_OBJECTS$ UPDATE SYSTEM PRIVILEGE CREATE SESSION
Now, the Object Privileges listed here are probable the result of some of the sample APEX applications I’ve installed.
By default, the only thing granted to ANONYMOUS is the CREATE SESSION privilege.
More pertinent here though is that it has no permissions at all on any objects owned by HR. This begs the question as to how our APEX application will work. Remember, our parsing schema ( essentially the Application Owner) is HR. Therefore, it’s reasonable to assume that we’ll want to interact with the tables in that schema.
NOTE – at this point I should add that, of course, ANONYMOUS does have additional privileges – i.e. everything granted to PUBLIC in the database. Whilst this is not strictly relevant to the matter at hand, it’s probably worth bearing in mind when you look at how you implement security around this user.
Anyway, let’s put it to the test…
The Regions Report
In our application we’re going to create a new page – a Report on the HR.REGIONS table so…
In the Application Builder, click on Create Page :
Select Report and click Next
Select Interactive Report and click Next
Accept the defaults for Page Region Attribute and click Next
In Tab Options choose Use an existing tab set and create a new tab within the existing set
New Tab Label is Regions :
For the SQL Query :
select region_id, region_name from regions
Note – we’re not specifying the table owner in this query, even though ANONYMOUS does not have a synonym on the HR.REGIONS table ( let alone any privileges)
…and click Create
When we now connect to the application as Kevin and click on the Regions tab….
So, the report has worked without error, despite the lack of privileges and synonyms. So what’s happening ?
Session Privileges in APEX
To answer this, we’ll need to tweak our earlier privileges query. This time, we’ll use the USER_ version of the views.
We can then it to the Application Home Page in a new reports region to see what ANONYMOUS can actually do when connected via APEX.
First, the new query, using USER_ versions of the views and without the order by clause.
select 'SYSTEM PRIVILEGE' as priv_type, null as db_object, privilege from user_sys_privs union select 'ROLE GRANTED' as priv_type, granted_role as db_object, null as privilege from user_role_privs union select 'OBJECT PRIVILEGE' as priv_type, owner||'.'||table_name as db_object, privilege from user_tab_privs union select 'OWNED OBJECT' as priv_type, object_name as db_object, null as privilege from user_objects where object_type != 'INDEX' /
Spoiler Alert – the reason I’m not using the SESSION_PRIVS view here is because it will list privileges granted via roles. The distinction between these and directly granted privileges will shortly become apparent.
We now simply create a new interactive reports region called User Privileges on the Home Page, using the above query.
If we now filter on PRIV_TYPE = ‘OWNED OBJECT’, we can see that we’ve magically acquired ownership of all the HR objects…
If we filter on PRIV_TYPE = ‘SYSTEM PRIVILEGE’, we can see that we also seem to have inherited HR’s System Privileges…
So, we can infer from this that, although the database connection from APEX is as the ANONYMOUS user, the session will inherit all of the objects and privileges of the parsing schema.
A reasonable assumption, given the evidence, and a correct one…mostly.
Objects not owned by the parsing schema
I’ve created a simple function in my own schema :
create or replace function name_scandal_fn( i_basename varchar2) return varchar2 as begin return i_basename||'gate'; end; /
Next we’re going to create a role and then grant execute on this function to that role. Finally, we’re going to grant the role to hr :
create role hr_role / grant execute on name_scandal_fn to hr_role / grant hr_role to hr /
First off, we’ll test this in SQL*Plus. Connect as HR and …
select mike.name_scandal_fn('Twitter') from dual / MIKE.NAME_SCANDAL_FN('TWITTER') -------------------------------------------------------------------------------- Twittergate SQL>
So, we should have no problem invoking this function from our application then.
Let’s create a page with a Display Only field that is populated by a call to this function :
Blank Page :
…With an HTML Region…
…on a new tab…
Now, add the Item…
Accept the defaults for the Item Attributes settings, and Settings…
… and change the Source settings to :
Source Used : Always, replacing any existing value in session state
Source Type : SQL Query (return single value)
Item Source Value – here we put in our call to the function :
select mike.name_scandal_fn('Twitter') from dual
Finally, hit the create button.
No problems so far. Now, let’s try running the page…
Hmmm, not quite what we were expecting.
Looking at the error stack, a possible source of the problem emerges.
In the background, it looks as if APEX is calling a package called WWV_FLOW_FORMS, which in turn calls WWV_FLOW_DYNAMIC_EXEC.
Whilst the source for both of these packages is wrapped, there are some notes availble on the next package in the call stack, WWV_DBMS_SQL here.
Putting all together and looking at the package headers, it would seem reasonable to assume that, rather than running the SQL statement directly, APEX does this via a series of package calls which then run the statement as dynamic SQL.
The effect of calling a (presumably) Definer’s Rights package is that any privileges granted via roles are ignored.
In order to test this theory, we can revoke the role from HR and instead, grant execute on the function directly.
So, connected to SQL*Plus as the function owner ( in my case MIKE) :
revoke hr_role from hr / grant execute on name_scandal_fn to hr /
Now a quick sanity check to make sure that HR can see the function.
Connect as HR and :
SQL> select mike.name_scandal_fn('Twitter') from dual 2 / MIKE.NAME_SCANDAL_FN('TWITTER') -------------------------------------------------------------------------------- Twittergate SQL>
Now let’s see what APEX makes of this.
Re-run the page and we can see…
There you have it. APEX, like Kevin, is just a little bit different.