Skip Headers
Oracle® Application Development Framework Developer's Guide
10g Release 3 (10.1.3)
B25386-01
  Go To Documentation Library
Home
Go To Product List
Solution Area
Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
Next
Next
 

18.7 Implementing Authorization Programmatically

You can set authorization policies against resources and users. For example, you can allow only certain groups of users the ability to view, create, or change certain data or invoke certain methods. Or, you can prevent components from rendering based on the group a user belongs to. Because the user has been authenticated, the application can determine whether or not to allow that user access to any object that has an authorization restraint configured against it.

The application can reference roles programmatically to determine whether a specific user belongs to a role. In the SRDemo application this is accomplished using the method isUserInRole() defined by the FacesContext interface (and also available from the HttpServletRequest interface).

The SRDemo application uses three core roles to determine who will have access to perform specific functions. Each user is classified with by the roles: user, technician, or manager. The remoteUser value (obtained from the Faces Context through the userid property) matches the email password in the SRDemo application's USERS table. These criteria are implemented using container managed BASIC authentication provided by Oracle Application Server as described in Section 18.3.3, "How to Enable J2EE Container-Managed Authentication".

18.7.1 Making User Information EL Accessible

Once the security container is set up, performing authorization is a task of

  • Reading the container security attributes the first time the application references it

  • Making the key security information available in a form that can be accessed through the expression language

To accomplish this the JSF web application can make use of a managed bean that is registered with session scope. The managed beans are Java classes that you register with the application using the faces-config.xml file. When the application starts, it parses this configuration file and the beans are made available and can be referenced in an EL expression, allowing access by the web pages to the bean's content.

For detailed information about working with managed beans, see Section 10.2, "Using a Managed Bean to Store Information".

This sample from SRList.jspx controls whether the web page will display a button that the manager uses to display an edit page.

<af:commandButton text="#{res['srlist.buttonbar.edit']}"
           action="#{backing_SRList.editButton_action}"
           rendered="#{userInfo.manager}"/>

This sample from SRCreateConfirm.jspx controls whether the web page will display a user name based on the user's authentication status.

<f:facet name="infoUser">
     <!-- Show the Logged in user -->
     <h:outputFormat value="#{res['srdemo.connectedUser']}"
                     rendered="#{userInfo.authenticated}" escape="false">
            <f:param value="#{userInfo.userName}"/>
     </h:outputFormat>
</f:facet>

18.7.1.1 Creating a Class to Manage Roles

The managed bean's properties allow you to invoke methods in a class that contains the code needed to validate users and to determine the available roles. This class should be created before you create the managed bean so you know the property names to use when you define the managed bean.

To create the Java class:

  1. In the New Gallery select the General category and the Java Class item.

  2. In the Create Java Class dialog, enter the name of the class and accept the defaults to create a public class with a default constructor.

Example 18-7 shows the key methods that the SRDemo application implements:

Example 18-7 SRDemo Application UserInfo.java Sample

/**
 * Constructor
 */
public UserInfo() {

        FacesContext ctx = FacesContext.getCurrentInstance();
        ExternalContext ectx = ctx.getExternalContext();
 
        //Only allow Development mode functions if security is not active        _devMode = (ectx.getAuthType() == null);
 
        //Ask the container who the user logged in as
        _userName = ectx.getRemoteUser();
 
        //Default the value if not authenticated
        if (_userName == null || _userName.length()==0) {
            _userName = "Not Authenticated";
        }        //Set the user role flag...
        //Watch out for a tricky bug here:
        //We have to evaluate the roles Most > Least restrictive 
        //because the manager role is assigned to the technician and user roles 
        //thus checking if a manager is in "user" will succeed and we'll stop 
        //there at the lower level of priviledge
        for (int i=(ROLE_NAMES.length-1);i>0;i--)  {
            if (ectx.isUserInRole(ROLE_NAMES[i])){
                _userRole = i;
                break;
            }
        }
    }    /*
     * Function to take the login name from the container and match that
     * against the email id in the USERs table.
     * Note this is NOT an authentication step, the user is already
     * authenticated at this stage by container security. The binding 
     * container is injected from faces-config.xml and refers to a special
     * pageDef "headless_UserInfoPageDef.xml" which only contains the definition     * of this method call,
     */
    private Integer lookupUserId(String userName) {
        if (getBindings() != null) {
            OperationBinding oper =
           (OperationBinding)getBindings().getOperationBinding("findUserByEmail");
            //now set the argument to the function with the username we want
            Map params = oper.getParamsMap();
            params.put("emailParam",userName);
            // And execute
            User user = (User)oper.execute();
            setUserobject(user);
            //It is possible that the data in the database has changed and             //there is no match in the table for this ID - return an appropriate            //Error in that case
            if (user != null){
                return user.getUserId();    
            }
            else{
                FacesContext ctx = FacesContext.getCurrentInstance();
                ctx.addMessage(null,JSFUtils.getMessageFromBundle
                 ("srdemo.dataError.userEmailMatch",FacesMessage.SEVERITY_FATAL));
                return -1;
            }
        }
        else {
            //This can happen if the ADF filter is missing from the web.xml
            FacesContext ctx = FacesContext.getCurrentInstance();
            ctx.addMessage(null,JSFUtils.getMessageFromBundle
               ("srdemo.setupError.missingFilter",FacesMessage.SEVERITY_FATAL));
            return -1;
        }
    }    /**
     * @return the String role name
     */
    public String getUserRole() {
        return ROLE_NAMES[_userRole];
    }    /**
     * Get the security container user name of the current user.
     * As an additional precaution make it clear when we are running in     * Dev mode
     * @return users login name which in this case is also their email id     */
    public String getUserName() {
      StringBuffer name = new StringBuffer(_userName);
      if (_devMode) {
        name.append(" (Development Mode)");
      }
      return name.toString();
    }    /**
     * Function designed to be used from Expression Language
     * for swiching UI Features based on role.
     * @return boolean
     */
    public boolean isCustomer() {
        return (_userRole==USER_ROLE);
    }    /**
     * Function designed to be used from Expression Language
     * for switching UI Features based on role.
     * @return boolean
     */
    public boolean isTechnician() {
        return (_userRole==TECHNICIAN_ROLE);
    }    /**
     * Function designed to be used from Expression Language
     * for switching UI Features based on role.
     * @return boolean
     */
    public boolean isManager() {
        return (_userRole==MANAGER_ROLE);
    }    /**
     * Function designed to be used from Expression Language
     * for switching UI Features based on role.
     * This particular function indicates if the user is either
     * a technician or manager
     * @return boolean
     */
    public boolean isStaff() {
        return (_userRole>USER_ROLE);
    }    /**
     * Function designed to be used from Expression Language
     * for switching UI Features based on role.
     * This particular function indicates if the session is actually authenticated
     * @return boolean
     */
    public boolean isAuthenticated() {
        return (_userRole>NOT_AUTHENTICATED);
    }

18.7.1.2 Creating a Managed Bean for the Security Information

The UserInfo bean is registered as a managed bean called userInfo in the JSF faces-config.xml file. The managed bean uses expressions for managed properties which the UserInfo class implements.

For example, in the SRDemo application the following expressions appear in the UserInfo managed bean:

  • #{userInfo.userName} either returns the login Id or the String "Not Authenticated"

  • #{userInfo.userRole} returns the current user's role in its String value, for example, manager

  • #{userInfo.staff} returns true if the user is a technician or manager

  • #{userInfo.customer} returns true if the user belongs to the role user

  • #{userInfo.manager} returns true if the user is a manager

To define the managed bean properties and expressions:

  1. In the Application Navigator, open the faces-config.xml file in the user interface WEB-INF folder.

  2. In the window, select the Overview tab.

  3. In the element list on the left, select Managed Beans and click New.

  4. In the Create Managed Bean dialog specify the class information for the managed bean. If you have not created the class, see Section 18.7.1.1, "Creating a Class to Manage Roles".

  5. To permit the security information defined by the managed bean to accessible by multiple web pages, set Scope to Session.

  6. In the Overview window, click the arrow to the left of the Managed Properties bar (appears below the managed bean list) to display properties of the bean.

  7. Click New to create the security properties that will accessed by your application. For example, the SRDemo application defines the userName and userRole properties as Strings. Example 18-8 shows the managed bean definition created for the SRDemo application.

  8. You must also define a unique managed bean property bindings with the value #{data.<ManagedBeanName+PageDefID}. In the Oracle ADF model, the variable bindings makes the binding objects accessible to EL expressions. The importance of this expression is described in Section 18.7.2.3, "Create a Page Definition to Make the Method an EL Accessible Object".

Example 18-8 shows the portion of the faces-config.xml file that defines the managed bean userInfo to hold security information for the SRDemo application. Note that the managed bean also defines the managed property bindings. The values shown for managed property values are ignored by the SRDemo application and were included for test purposes only.

Example 18-8 Managed Beans in the SRDemo faces-config.xml File

<!-- The managed bean used to hold security information -->
  <managed-bean>
    <managed-bean-name>userInfo</managed-bean-name>
    <managed-bean-class>oracle.srdemo.view.UserInfo</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
      <property-name>bindings</property-name>
      <value>#{data.UserInfoPageDef}</value>
    </managed-property>
    <!-- Test Data ignored if real security is in use-->
    <managed-property>
      <property-name>userName</property-name>
      <property-class>java.lang.String</property-class>
      <value>sking</value>
    </managed-property>
    <managed-property>
      <property-name>userRole</property-name>
      <property-class>java.lang.String</property-class>
      <value>manager</value>
    </managed-property>
  <!-- End Test Data -->
</managed-bean>

18.7.2 Integrating the Managed Bean with Oracle ADF Model

The managed bean does have some interaction with the Oracle ADF Model layer. Once the user logs in and the logon ID is obtained, the application needs to translate the login ID into the unique userid that permits the application to identify the user. This information can then be used throughout the application to determine what menus and functionality to display. For instance, in the SRDemo application, the SRList page will only display the Edit button when the user logged in belongs to the manager role. The same authorization restraints is applied to the SRCreate page.

To obtain a unique userid that databound web pages can use to perform authorization:

  • Create a TopLink named query that will return a user object for a particular id (which is the value that we get from the container security).

  • Create a method on the session bean facade that wraps this lookup method up.

  • Create an ADF page definition file for the managed bean to describe its use of this lookup method on the session bean.

  • Inject the binding information into the UserInfo bean to provide access to the ADF Model layer to invoke the method on the session bean.

  • Execute the custom method from the UserInfo bean the first time the particular id is required for authorization.

18.7.2.1 Creating a TopLink Named Query To Return a User Object

You can identify the user who logs into the application through a named query. This query will return a user object for a unique identifier, such as a particular email id, received from the container security. The query is read-only and takes a String parameter containing the identifier.

To create a named query, use the descriptor of the TopLink SRMap file that corresponds to the USERS table. The query in SRDemo application is based on the email id and receives its value from the security container.

For more information about TopLink named queries, see Section 3.8, "Creating and Modifying Objects with a Unit of Work".

To create a named query for the User entity:

  1. In the Application Navigator, expand the data model project and open SRMap to display the Mapping editor.

  2. In the Structure window, expand the entities package.

  3. In the Mapping editor, select the descriptor User and click Add to define a new TopLink named query. For example, SRDemo application uses findUserByEmail.

  4. Use the General panel to add a parameter that identifies the unique attribute. For example, the SRDemo application uses emailParam of type java.lang.String.

  5. Use the Format panel to define an expression for the named query. For example, the SRDemo application uses email EQUAL emailParam.

  6. Save the query.

18.7.2.2 Create a Session Facade Method to Wrap the Named Query

Oracle recommends that you use a session facade to access entities and methods in order to expose services to clients. The session bean that implements the session facade design pattern, becomes your application's entry point for the Oracle ADF data control. Chapter three describes how to expose services with ADF data controls. Like other methods to be invoked at application runtime, the finder method for the named query must be registered with the Oracle ADF EJB data control in your project. This step begins the process of allowing the ADF Model layer to access the user security information for a uniquely identified user.

If you have not already created a session facade to wrap the TopLink queries, see Section 3, "Building and Using Application Services".

To add a finder method to an existing the session facade:

  1. Expand the data model package that contains the session bean for which you created the ADF EJB data control.

  2. Double-click the session bean .java file to open it in the source editor.

  3. Add the new method. Example 18-9 shows the session facade finder method implemented in the SRDemo application.

  4. In the Application Navigator, right-click the session bean and choose Edit Session Facade.

  5. In the Application Navigator, add the new method to the remote interface.

  6. Save the .java file and recompile.

  7. In the Application Navigator, right-click the session bean and choose Create Data Control. The new method will appear on the Data Control Palette.

Example 18-9 SRDemo SRPublicFacadeBean.java Finder Method to Expose Unique ID

public User findUserByEmail(String emailParam) {    Session session = getSessionFactory().acquireSession();
    Vector params = new Vector(1);
    params.add(emailParam);
    User result = 
      (User)session.executeQuery("findUserByEmail", User.class, params);
    session.release();

    return result;
}

18.7.2.3 Create a Page Definition to Make the Method an EL Accessible Object

After the finder method used to return a unique id for the user has been registered with the ADF data control, the next step in exposing the finder methods to the Oracle ADF Model layer is to provide a page definition description, where it will be defined as a method action binding. Once the binding is exposed by the Oracle ADF Model, it can be used throughout the application pages.

Typically, each web page maps to a single page definition file. However, because we want the action binding to be accessible throughout the application, the binding definition must belong to its own page definition—one that is "headless"—without a corresponding web page.

To create a headless page definition file for the user interface project:

  1. In the Application Navigator, expand the user interface package that contains the page definition files.

  2. Right-click the pageDefs package node and choose New.

  3. In the New Gallery, create an XML document from the General - XML category.

  4. In the Create XML File dialog, name the file for the managed bean that defines the security properties and append PageDef. For example, in the SRDemo application, the headless page definition is named headless_UserInfoPageDef.xml.

  5. Open the XML file in the source editor and add the method binding definition. Example 18-10 shows the binding definition created for the SRDemo application.

  6. Save the file.

The value 999 (or CUSTOM) set on the action property of the methodBinding specifies the method to be invoked is a custom method defined by the application service.

Example 18-10 SRDemo headless_UserInfoPageDef.xml Page Definition File

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="10.1.3.35.65" id="UserInfoPageDef"
                Package="oracle.srdemo.view.pageDefs">
  <bindings>
    <methodAction id="findUserByEmail"
                  InstanceName="SRPublicFacade.dataProvider"
                  DataControl="SRPublicFacade"
                  MethodName="findUserByEmail" RequiresUpdateModel="true"
                  Action="999"
                  ReturnName="SRPublicFacade.methodResults.
                        SRPublicFacade_dataProvider_findUserByEmail_result">
      <NamedData NDName="emailParam" NDType="java.lang.String"/>
    </methodAction>
  </bindings>
</pageDefinition>

The ADF Model layer loads the page definition from the path reference that appears in the DataBinding.cpx file. The new page definition file needs to have this reference to id "UserInfoPageDef" within DataBindings.cpx. This can be done from the Structure window for the CPX file.

To create a headless page definition file for the user interface project:

  1. In the Application Navigator, expand the root user interface package and locate the DataBindings.cpx file. The packages appear in the Application Sources folder.

  2. Double-click DataBindings.cpx and open the Structure window.

  3. In the Structure window, select the pageDefinitionUsages node and choose Insert Inside pageDefinitionUsages > page.

  4. Set Id to the name you specified for your headless page definition file (contains the single methodAction binding). For example, the SRDemo application uses UserInfoPageDef.

  5. Set path to package that where you added the page definition file. For example, in the SRDemo application, the path is oracle.srdemo.view.pageDefs.userInfo.

At runtime, a reference to data.<Headless_PageDefID> will now resolve to this binding definition. Example 18-11 shows the id specified for the headless page definition file in the SRDemo application.

Example 18-11 SRDemo DataBindings.cpx Page Definition Reference

<?xml version="1.0" encoding="UTF-8" ?>
<Application xmlns="http://xmlns.oracle.com/adfm/application" ...
  <pageDefinitionUsages>
    <page id="SRListPageDef"
          path="oracle.srdemo.view.pageDefs.app_SRListPageDef"/>
    <page id="UserInfoPageDef"
          path="oracle.srdemo.view.pageDefs.headless_UserInfoPageDef"/>
     ...
</Application>

18.7.2.4 Executing the Session Facade Method from the UserInfo Bean

In the managed bean definition userInfo, you may have already defined a managed property bindings that has the value #{data.UserInfoPageDef}. For details, see Section 18.7.1.2, "Creating a Managed Bean for the Security Information".

To compliment the expression, the class that implements the security methods (UseInfo.java) requires a corresponding getter and setter method for the bindings property:

public void setBindings(BindingContainer bindings) {
   this._bindings = bindings;
 } 
public BindingContainer getBindings() {    return _bindings;
} 

The first time the application requires the UserId, the session bean method is called. This is done using the getUserId() method in UserInfo.java. The getUserId() method checks to see if the UserId is currently populated. If not, it makes a call to a private method lookupUserId() that actually calls the session facade method:

public Integer getUserId() {
   if (_userId == null){
    _userId = lookupUserId(_userName);
   }
   return _userId;
}

The lookupUserId() method is responsible for invoking the methodAction binding which calls the session facade method defined to get the user ID:

private Integer lookupUserId(String userName) {
        if (getBindings() != null) {
            OperationBinding oper = (OperationBinding)getBindings().
                                 getOperationBinding("findUserByEmail");
            //now set the argument to the function with the username we
            //are interested in
            Map params = oper.getParamsMap();
            params.put("emailParam",userName);
            // And execute
            User user = (User)oper.execute();
            setUserobject(user);
            return user.getUserId();
        }
}

The method uses getBindings() to get the injected binding container from the Faces configuration. Once the binding container is obtained, the method looks up the methodAction binding responsible for coordinating with the session facade method. For details about the session facade method, see Section 18.7.2.2, "Create a Session Facade Method to Wrap the Named Query".