001    package hirondelle.web4j.ui.tag;
002    
003    import java.util.*;
004    import java.util.logging.*;
005    import java.security.Principal;
006    import hirondelle.web4j.util.Util;
007    import static hirondelle.web4j.util.Consts.PASSES;
008    import static hirondelle.web4j.util.Consts.FAILS;
009    import static hirondelle.web4j.util.Consts.EMPTY_STRING;
010    
011    /**
012     Toggle display according to user role.
013     
014     <P><span class="highlight">It is important to note that the <em>sole</em> use of this 
015     tag does <em>not</em> robustly enforce security constraints.</span>
016     This tag is meant as a "cosmetic convenience" for removing items from JSPs (usually a link). 
017     The problem is that a hacker can always construct any given URI manually and send it to the server. 
018     Such malicious requests can only be handled robustly by a <tt>security-constraint</tt>
019     defined in <tt>web.xml</tt>.   
020     
021     <P>Example:
022     <PRE>
023     &lt;w:show ifRole="webmaster,translator"&gt;
024       (tag content - typically a link)
025     &lt;/w:show&gt;
026     </PRE>
027     
028     <P>Example with role specified by negation:
029     <PRE> 
030     &lt;w:show ifRoleNot="read-only"&gt;
031       (tag content - typically a link)
032     &lt;/w:show&gt;
033     </PRE>
034     
035     The above two styles are mutually exclusive.
036     
037     <P>The body of this class is either echoed as is, or is suppressed entirely.
038     It is echoed only if the user is logged in, <em>and</em> their role is compatible with the 
039     specified list. By definition, a user is logged in when <tt>request.getUserPrincipal()</tt> 
040     returns a value having content.
041    */
042    public final class ShowForRole extends TagHelper {
043    
044      /** Optional, comma-delimited list of accepted roles. */
045      public void setIfRole(String aRoles){
046        fAcceptedRoles = getRoles(aRoles);
047      }
048      
049      /** Optional, comma-delimited list of denied roles.  */
050      public void setIfRoleNot(String aRoles){
051        fDeniedRoles = getRoles(aRoles); 
052      }
053      
054      /**
055       One and only one of {@link #setIfRole} and {@link #setIfRoleNot} must be set.
056      */
057      protected void crossCheckAttributes() {
058        if( isAccepting() && isDenying() ) {
059          String message = "Please do not specify BOTH the ifRole, ifRoleNot attributes - just use one. Page Name: " + getPageName();
060          fLogger.severe(message);
061          throw new IllegalArgumentException(message);
062        }
063        if( !isAccepting() && !isDenying() ){
064          String message = "Please specify ONE of the ifRole, ifRoleNot attributes. Page Name : " + getPageName();
065          fLogger.severe(message);
066          throw new IllegalArgumentException(message);
067        }
068      }
069      
070      /** See class comment.  */
071      @Override protected String getEmittedText(String aOriginalBody) {
072        boolean showBody = false;
073        Principal user = getRequest().getUserPrincipal();
074        if ( user != null ) {
075          if( isAccepting() ){
076            showBody = isUserAccepted();
077          }
078          else if ( isDenying() ){
079            showBody = ! isUserDenied();
080          }
081          else {
082            throw new AssertionError("Unexpected branch. Page Name : " + getPageName());
083          }
084        }
085        return showBody ? aOriginalBody : EMPTY_STRING;
086      }
087      
088      // PRIVATE //
089      private List<String> fAcceptedRoles = new ArrayList<String>();
090      private List<String> fDeniedRoles = new ArrayList<String>();
091      private static final String DELIMITER = ",";
092      private static final Logger fLogger = Util.getLogger(ShowForRole.class);
093      
094      private List<String> getRoles(String aRawRoles){
095        List<String> result = new ArrayList<String>();
096        StringTokenizer parser = new StringTokenizer( aRawRoles, DELIMITER);
097        while ( parser.hasMoreTokens() ) {
098          result.add( parser.nextToken().trim() );
099        }
100        return result;
101      }
102      
103      private boolean isAccepting(){
104        return ! fAcceptedRoles.isEmpty();
105      }
106      
107      private boolean isDenying(){
108        return ! fDeniedRoles.isEmpty();
109      }
110      
111      private boolean isUserAccepted(){
112        boolean result = FAILS;
113        for (String role: fAcceptedRoles){
114          if ( getRequest().isUserInRole(role) ) {
115            result = PASSES;
116            break;
117          }
118        }
119        return result;
120      }
121      
122      private boolean isUserDenied(){
123        boolean result = FAILS;
124        for (String role: fDeniedRoles){
125          if ( getRequest().isUserInRole(role) ) {
126            result = PASSES;
127            break;
128          }
129        }
130        return result;
131      }
132    }