Wednesday, July 30, 2008

Intro to JBoss Seam Security, Part 3 -- Authorization Rules with Drools

In previous articles, we have looked at how JBoss Seam handles security, particularly user authentication. Through the use of its Identity and Authenticator APIs, Seam provides an easy way to authenticate a prospective user without forcing a developer to implement hundreds of lines of code. In addition, Seam also provides CAPTCHA support, enabling an application to test for the presence of a human user, rather than a bot attempting to access an application. No doubt, Seam 2.1's forthcoming identity management API will make the process of creating and managing user accounts even easier. (Although, that is an article for another day)

While authentication is vital to application security, it is not the only aspect of security that must be accounted for. Authorization -- the process whereby it is determined what resources or actions a user may access -- takes authentication one step further, and cannot be overlooked. It is easy to come up with examples of where authorization should be determined within an application. A banking site would want to ensure that an authenticated user is, in fact, the owner of the bank account they are trying to access. A health insurance provider needs to check if an individual should be allowed to view recent claims that have been submitted. To accomplish this, some sort of authorization rules need to be established. Java EE already allows for programmtic authorization checks through the use of the HttpServletRequest.isUserInRole() method. Seam, however, takes authorization further and allows for integration within enterprise applications through the use of an authorization rules engine.

Seam uses the Drools business rules engine to allow complex authorization rules to exist outside of the Java code itself. Business rules engines have long been used to drive business operations or make decisions related to a specific business process (i.e. Approve a loan for an individual if they make more than $150,000 per year and have less than $25,000 in existing debt.) Now, these same types of rules can be applied to application security logic. Imagine a site like Facebook and how it approaches security. Facebook has very well-defined rules for how its information can be accessed. A profile is only visible to a member if the profile owner is friends with the member. Picture X can only be viewed by family members and friends, not coworkers. Member A can send a friend request to Member B provided that: 1) Member A != Member B, and 2) Member A is not already friends with Member B. It would be very easy to implement rules like this in Java code, either in the view or business layer. However, in our example application, we will separate these rules from our Java code and implement them using Drools.

Our simple example will be a social network site. Like any social network, ours will have some basic rules established to ensure the security of our site. The rules we will implement are:

  1. Administrators may do whatever they please
  2. A friend request may only be submitted if the sender != recipient
  3. A profile is only visible by its owner and their friends

Our example will consist of two main objects:


public class Member implements Serializable {

...snip...

public Set<Member>getFriends() {
...
}

public void setFriends(Set<Member> friends) {
...
}
}

@Name(value="member")
public class MemberAction {

...snip...

@In
private Member authenticatedMember = null;

public void viewProfile(Member member) {
...
}

public void sendFriendRequest(Member recipient) {
...
}

public void deleteProfile(Member member) {
...
}
}



For the sake of time, we will not discuss basic dependency injection concepts. It should be obvious that MemberAction.authenticatedMember is related to the profile of a user who has successfully authenticated against the application. There are two primary ways we can perform an authorization check in the business layer using Seam: 1) Through annotations, or 2) Inline, using Java code. In addition, we can also perform security checks in the view layer to display or hide links or data based on the outcome of the check. We will discuss all of these methods for performing the checks as we move forward.

Before we are able to write and test our security rules, we must first enable them in our Seam configuration, and ensure our rules file is located in the right place. The components.xml file must have the following lines to tell Seam that we wish to use Drools for authorization:


<drools:rule-base name="securityRules">
<drools:rule-files>
<value>/META-INF/security.drl</value>
</drools:rule-files>
</drools:rule-base>


As you can see, we have configured our security rules file to be located at /META-INF/security.drl. It is important that this file exists, otherwise our applications will not work as we expect. To tackle our first rule, "Administrators may do whatever they please", we need to establish a rule that allows administrators to be granted whatever permissions they request. Since this is a relatively simple rule, it will serve as a good starting point for our introduction into Drools syntax. Our rule will look like this:



package MySecurityRules;

import org.jboss.seam.security.PermissionCheck;
import org.jboss.seam.security.Role;

rule AdminsCanDoAnything
when
c: PermissionCheck(name == "member")
Role(name == "admin")
then
c.grant();
end;


Let's take a look at the syntax for this rule. On line 1, you will see a package declaration. This is not the same as Java packages. Drools packages are used to group Drools rules together. They are not used for anything else. On lines 2 and 3, you see two import statements. This lets Drools know that we will be referrencing the specified classes as part of our rule. It is imporant to include these lines, otherwise our rule will fail. Line 4 marks the beginning of the rule itself. Our rule is called "AdminsCanDoAnything". There is no specific naming requirement for your rule, other than the fact that it must be unique within the given package. Typically, the rule name is a description of what the rule actually accomplishes. The rule is divided into 2 sections, the "when" and the "then". The "when" section consists of one or more conditions that must be true for this rule to fire. If any of these conditions is not met, the rule will not fire. Once all conditions have been met, the rules engine moves on to the "then" section, and performs whatever commands are specified.

Lines 6 and 7 provide the authorization check itself. Line 6 is the first condition that must be met for this rule to fire. The line:


c: PermissionCheck(name == "member")


can be explained as, "there must exist a PermissionCheck object with a name property of "member" within the current working memory. This object will be called 'c' in this rule." If this condition is met, the engine moves on to line 7. Line 7 specifies that, "there must exist a Role with the name 'admin' within the current memory." By now you're probably wondering, "What is 'the current memory'?" Drools has its own context or working memory that it uses when evaluating a rule. It essentially acts as a session for the rules engine. Before an object can be considered within a rule, it must be added to the working memory. Whenever a permission check is performed via the hasPermission() method, a PermissionCheck object is created and inserted into the working memory. This object contains the name of the object the check corresponds to, and the action that is being attempted. In our example, the name of the object is "member". You'll notice that we have not specified an action in our check. This results in the rule corresponding to all possible actions pertaining to member. In addition, any Roles that a user belongs to will be inserted into the working memory prior to a rule check. This is how we determine the contents of our example security rule. It is also possible to insert an arbitrary object into the working memory by calling


((RuleBasedIdentity) RuleBasedIdentity.instance()).getSecurityContext().insert();


This will come in handy in our next article as we discuss Seam security rules futher. Now that we have defined our authorization rule, we can apply it and perform a permission check. For this example, we want to hide the link for the deleteProfile action from our view layer. Normally, however, we would also apply this permission check in the action itself to prevent users from circumventing our view-layer check. To perform a permission check on our link, we simply add the following to our JSF file:


<s:link value="Delete Profile" action="#{member.deleteProfile(...)}"
rendered="#{s:hasPermission('member', 'deleteProfile', null)}" />


Now, when our view is displayed, the permission check will be performed prior to rendering the link. Since our admin users have been granted all permissions, the link will be rendered. If we wrote an additional rule restricting access to the deleteProfile(), our admins would still be able to view the link, while other users that fail the permission check will not see the link.

In my next article, I will discuss how to implement our two remaining authorization rules, as well as the other ways of performing a permission check within your code.

No comments: