Saturday 3 July 2010

ADF Security revisited (again again)

This is a revisit of the ADF Security features under JDeveloper 11g and subsequent deployment to WLS. There are plenty of good examples and documentation on this feature and readers are encouraged to seek those out. This post is my cut of implementing ADF Security to make sure I've all the moving pieces clearly defined in my head. As usual I thought this post may be of interest to readers.

Assumptions

Readers understand the security concepts of authentication and authorization, and readers are familiar with the WLS security "myrealm" concept.

Sample Application

What I find difficult with the JDev 11g Fusion Guide is the raw depth of information to read and assimilate around the security space. Section 29 has 80 odd pages on security which is a lot to digest. Without a doubt security is an important topic but trying to understand the full 80 pages leaves me wiped out.

To make the discussion on ADF Security easier, let's discuss an extremely simple application and then describe what we're attempting to achieve in terms of security.

Consider the following application:


... and the following page flow:


The diagram reveals the following points about our application:

1) The Splash page is a landing page for our application where new unauthenticated sessions will land as they access the application for the first time. This page will be a simple web page with no ADF Bindings. It will provide links to the other 3 pages.

2) ViewBookings, a page showing data using ADF Bindings, will be a secured page. Only authenticated users will be able to access the page and they must have the ADMIN role.

3) ViewEvents, another databound ADF page using ADF Bindings, will also be a secured page. Only authenticated users will be able to access the page, but there are no role restrictions on the page, giving any authenticated user the right to access the page.

4) ViewOrganisations, another databound ADF page using ADF Bindings, will not be secured. Therefore authenticated and non-authenticated users will be able to access the page, and implicitly via this, there are no role restrictions on the page.

(You'll note in the above points I've been careful to identify databound pages that have ADF Bindings (ie. ViewBookings) and those that don't (ie. Splash). The ADF Security mechanisms *only* work for pages that have ADF Bindings and the relating pagedef files. A page that doesn't have ADF Bindings is not secured. This implies how we treat the unbound Splash page vs the bound ViewOrganisations page is different. The mechanisms for this will become apparent further in this post.)
Best practice side note: as a general recommendation while this article demonstrates a page that is totally unsecured via the ADF Bindings mechanism, readers should give strong consideration to create PageDef files for all pages regardless if they are databound or not. This is done by selecting the Page Definitions option from the page's context menu, then allocating them the anonymous-role making them accessible to anyone as explained later in this article. Implementing this gives you a number of advantages:

a) Improved control of the public page's content, displaying different content for authenticated vs unauthenticated users
b) A consistent security implementation across all pages
c) Easier to audit the security implementation
In order to support our application as described in the points above, we'll need a number of security "parts" for our application. Following is a summary of those parts, of which we'll describe in detail next in this document

1) A login page to authenticate users
2) An application role called ADMIN
3) An application role that identifies authenticated users
4) An application role that identifies a user regardless if they're authenticated or not
5) The ability to secure the ViewBookings page for users with the ADMIN role
6) The ability to secure the ViewEvents page for users who are authenticated
7) A mechanism to create users, create roles and assign users roles

Luckily JDeveloper provides a number of features to make this easy.

We'll add one other further security requirement. Though our application will internally make use of a role named ADMIN, once deployed to our application server the role will be called ADMINISTRATOR. As such our additional requirement is:

8) Map the application role ADMIN to the enterprise role ADMINISTRATOR.

A final point before continuing, in order to keep this example simple I've steered away from the ADF Task Flow features of JDev 11g, specifically bounded task flows. In this simple example I've stuck to the default single unbounded task flow with separate pages in order to aid understanding. In reality both task flows and pages are treated in a similar fashion so what you learn here will apply to both.

Configure ADF Security

The ADF Security wizard implements a number of the security features we require. For our open application selecting the Application menu -> Secure -> Configure ADF Security option invokes the Configure ADF Security wizard. This wizard has been described in a number of other documents and examples, so I'll not talk through all it's options, but rather just show the options picked for our example.

In step 1 of 5 we pick the ADF Authentication and Authorization option:


Step 2 of 5 we'll get JDev to create a login and error login page for us, such that the user will see an in-page login page in our application to authenticate themselves:


Step 3 of 5 we'll ask JDev to blanket grant privileges to our existing pages. We'll tidy this up in a later step. Typically we wouldn't use this option but rather the "No Automatic Grants". No grants forces developers to explicitly think about each page and what privileges to define. For demonstration purposes we're showing the other option:


Step 4 of 5 we'll leave the "Redirect" option unselected. When selected this option provides the ability to have all authenticated users redirected to a standard landing page:


Step 5 of 5 tells you what changes JDev will make on your behalf to implement the features. This screen is useful as it gives you a good understanding of what configurations are required to make ADF Security work.


Once completed the most important JDeveloper screen from here for configuring security in your application is the jazn-data.xml editor. It's accessible by opening the file under Application Resources -> Descriptors -> META-INF -> jazn-data.xml, or the Application menu -> Secure -> ADF Policies. Ensure the Overview tab at the bottom of the editor is selected. On first opening the jazn-data.xml file you'll see:


As our application includes no bounded task flows, just web pages, select the Web Pages tab at the top of the editor. For our specific application it reveals:


From here you can see three of our four pages. As the Splash screen has no ADF Bindings, it does not display and therefore has no options for defining grants and priviliges. This is because as we mentioned early in the post, only databound pages are secured by ADF Security. If we alternatively select one of our databound pages we see:


In this case the ViewBookings databound page has the role "test-all" with "View" access granted to it. In a similar fashion ViewEvents and ViewOrganisations will have the same mappings. The role "test-all" and blanket assignments to each page was generated by step 3 of the Configure ADF Security wizard.

At this stage, of our initial 8 security requirements, we've implemented #1 only. Obviously the last few steps have set up a lot of internal infrastructure and configuration for us behind the scenes which assist the next sections of work.

Application Roles vs Enterprise Roles

Our application has the security requirement to create a role called ADMIN and secure the ViewBookings page with that role. In JDeveloper security "speak" this sort of role is referred to as an *Application Role*, effectively a role that the application defines and wants to enforce against certain resources, namely web pages and task flows in our ADF application. At deployment time this role is essentially private to the application.

Application Roles are distinctly different from Enterprise Roles as they have a finer level of granularity, they only apply to one application. Conversely Enterprise Roles map to user groups within an organisation and can apply across applications, though they allow the flexibility of a 1 to 1 mapping with Application Roles too.

Another way to categorize the difference between Application Roles and Enterprise Roles is where they're implemented. Application Roles are implemented in the application at hand, while Enterprise Roles are configured in our application server or higher level identity management systems such as LDAP, MS Active Directory, OID and more.

This distinction becomes important as we configure our Application Roles, as we need to define both the Application Role, the Enterprise Role, and the mappings between them. If we don't do this, when the application is deployed, and a user accesses the system with the Enterprise Role ADMINISTRATOR, the application server has no way of knowing that this maps to the application's ADMIN role.

As per the previous section the JDeveloper security wizard predefines an Application Role named "test-all" for us. From the jazn-data.xml editor this is evident on the existing grants to pages and task flows. The intention is for development and testing purposes, any user granted the test-all role has unlimited access to the application. For a production environment this role is not suitable as it is granted to all anonymous users; any user accessing the application is already a member of this role allowing unrestricted access.

To see where the Applications Roles are defined select the User and Roles tab on the bottom of the jazn-data.xml page, followed by the Application Roles node on the left hand side:. This will display the Edit JPS Identity & Policy Store dialog:


You'll note you can see the "test-all" role defined under the Application Roles node. In our case we'll add a new ADMIN role, and remove the "test-all" role as follows:


From here we want to map the Application Role to the Enterprise Role. Remember again that Enterprise Roles exist within our application server, while the Application Role is defined within our application.

We now move to creating the ADMINISTRATOR Enterprise Role under the same left node menu option:


Next we need to map the Enterprise Role to the Application Role. With the ADMINISTRATOR Enterprise Role selected, select the Assigned Roles tab on the right, followed by the plus sign and the Assign Application Role option. In the resulting Select Roles dialog select the ADMIN Application Role. The result:


Protecting our pages with Application Roles

On completing the previous steps, on returning to the ADF Policies tab followed by the Web Pages tab, we'll note that because we removed the test-all role, none of the databound pages have roles assigned to them (identified by the fact they no longer have arrows to the right of them):


To allow users with the ADMIN Application Role to access the ViewBookings page, we simply select the page, the plus button allows us to pick the ADMIN role from the appropriate dialog, and finally we select the View option on the right hand side:


The 4 other privileges, Customize, Edit, Grant and Personalize are relevant to MDS and not applicable to this post. As such the remaining View privilege tells us ADF Security via the jazn-data.xml file is at it's most basic determining if a user can access the page or not. It's not concerned with any other privileges.

Testing

This presents a good time to test our solution. Before we can test our solution we need to create a user with the appropriate roles.

There's two ways we can do this. In WLS we can create the user and Enterprise Role ADMINISTRATOR, and allocate the role to the user.

For development purposes JDeveloper presents a "cheats" way of doing this. We return to the jazn-data.xml editor and select the Users and Roles tab followed by selecting the Users node on the left. From here we can define users and their passwords (aka Credentials) and then via the Assigned Roles section we can select the ADMINISTRATOR Enterprise Role:


(Note if you have trouble defining the user's password, where JDeveloper shows a red box around the password field, this is because passwords must be a minimum length of 8 characters)

For testing purposes we'll also create a 2nd user who isn't allocated any roles at all:


Normally we wouldn't be creating such users in our JDev application, they'd be created in our WLS server or a connected LDAP server or similar. JDeveloper allows us to get away with this because of the following option. If you select the Applications menu -> Deployment node, you'll see JDev has autoconfigured an EAR deployment descriptor for you, with a number of options below:


In particular the Users and Groups check box means that JDev will attempt to deploy the Enterprise Role ADMINISTRATOR and the two users we created CMUIR and JDOE to the server when the application is deployed.

On running our app for the first time we can happily land on the Splash page.


(ok, ok, it's a very uninspiring application. If you want whiz bang go read a post on DVT controls)

Clicking the View Bookings link, the user is interrogated for their credentials via the logon screen:


Of note in the JDev log window you'll see the authorisation challenge as follows:

[JpsAuth] Check Permission
PolicyContext: [ADFSecurityDemo#V2.0]
Resource/Target: [view.pageDefs.ViewBookingsPageDef]
Action: [view]
Permission Class: [oracle.adf.share.security.authorization.RegionPermission]
Result: [FAILED]
For more information on this failure, please set -Djps.auth.debug.enable=true

Presumably this is the mechanism that tells the ADF application to show the login page.

If we log in as CMUIR allocated with the ADMIN role we see the View Bookings page in all it's glory. Alternatively if we log in as JDOE without the ADMIN role we see the rather flattering exception:

oracle.adf.controller.ControllerException: ADFC-06000: The ADF Controller caught exception {0} while performing control flow routing. See the stack trace for details.
at oracle.adfinternal.controller.util.Utils.createAndLogControllerException(Utils.java:203)
at oracle.adfinternal.controller.state.SuspendedNavigationState.resume(SuspendedNavigationState.java:54)
at oracle.adfinternal.controller.application.LoginSuccessHandler.resumeNavigation(LoginSuccessHandler.java:117)
at oracle.adfinternal.controller.application.LoginSuccessHandler.doCreateView(LoginSuccessHandler.java:61)
at oracle.adfinternal.controller.application.BaseRequestHandlerImpl.createView(BaseRequestHandlerImpl.java:57)
-snip-
oracle.adf.controller.security.AuthorizationException: ADFC-0619: Authorization check failed: '/ViewBookings.jspx' 'VIEW'.
at oracle.adf.controller.internal.security.AuthorizationEnforcer.handleFailure(AuthorizationEnforcer.java:147)
at oracle.adf.controller.internal.security.AuthorizationEnforcer.checkPermission(AuthorizationEnforcer.java:126)
at oracle.adf.controller.internal.security.AuthorizationEnforcer.checkRead(AuthorizationEnforcer.java:363)
at oracle.adfinternal.controller.activity.ViewActivityLogic.execute(ViewActivityLogic.java:77)
at oracle.adfinternal.controller.engine.ControlFlowEngine.doRouting(ControlFlowEngine.java:876)
-snip-
This is also recorded in the WLS logs. While it doesn't look nice, effectively ADF Security is telling JDOE to get nicked because he doesn't have the ADMIN role. If you wish to handle this in a more graceful way you can make use of the ADF controller's declarative exception handler to route the user to a "friendly" denied-access page, or alternatively we wouldn't have given the user the option to navigate to this page in the first place. Of course this article shows the above for learning purposes.

Of note if we were to log in as CMUIR or JDOE and access either the ViewEvents or ViewOrganisations pages we'll also see the same error. This occurs because we haven't allocated any privileges to these pages yet.

We've now satisfied our original security requirements #1, #2, #5, #7 and #8.

Allowing any authenticated user access

Our next requirements #3 and #6 are to secure the ViewEvents page, allowing only authenticated users to access this page, regardless of which roles they've been allocated.

On returning to the jazn-data.xml editor, selecting the ViewEvents page and then the plus button, you'll note in the dialog that shows there are 2 other predefined roles besides our ADMIN role, namely "anonymous-role" and "authenticated-role":


Quoting straight from the JDev online help:

"The anonymous-role is a role you can use when you want to grant access privileges to unauthenticated users. All users, including unauthenticated users, for example, those not required to log on to the application, are automatically considered by Oracle Platform Security to be a member of the anonymous-role. The authenticated-role is a generic role that you can use to allow authenticated users to access the databound web pages of your application. The authenticated-role requires the user to be successfully logged on."

As such if we allocate the authenticated-role to the ViewEvents page, and....


Alllowing unauthenticated users access

...for our requirement #4, we allocate the anonymous-role to the ViewOrganisations page:


....we satisfy the remaining requirements.

Testing

Now on testing our application, landing on the Splash screen we discover:

a) cmuir user can access all 4 pages
b) jdoe can access Splash, ViewEvents and ViewOrganisations, but not ViewBookings
c) an unauthenticated user can access Splash and ViewOrganisations, but not ViewBookings or ViewEvents

Troubleshooting

Some developers may find regardless of the above post, they may still encounter ADFC-06000/ADFC-0619 errors when accessing a page with a user who has the appropriate application role. A common reason you can still get this error is you've forgotten to map the Enterprise Role to the Application Role as described in the last part of the Application Roles vs Enterprise Roles section.

Further to this it's possible to see further debug output out of the ADF security engine (implemented by something called OPSS) by following this post by Duncan Mills.

Credit

My thanks must go to Frank Nimphius for reviewing this article oh-so-long-ago back in September 2009. Apparently having a 2nd daughter has somewhat delayed publication of some of my articles. God knows how people with 3 kids get anything done.

3 comments:

Nick Aiva said...

Thank you so much for your usefull articles!
The ones who have kids, are blessed!
http://nickaiva.blogspot.com/

Unknown said...

Is it possible for you to post the source code of the sample?
Thanks in advance....

Chris Muir said...

With apologies Eke, I've subsequently deleted the sample application.

CM.