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 the user's role.
013     
014     <P><span class="highlight">It's 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       show tag content only if the user is logged in, 
025       and has at least 1 of the specified roles
026     &lt;/w:show&gt;
027     </PRE>
028     
029     Example with role specified by negation:
030     <PRE> 
031     &lt;w:show ifRoleNot="read-only"&gt;
032       show tag content only if the user is logged in, 
033       and has none of the specified roles
034     &lt;/w:show&gt;
035     </PRE>
036    
037     Example with logic attached not to role, but simply whether or not the user has logged in:
038     <PRE>
039     &lt;w:show ifLoggedIn="true"&gt;
040       show tag content only if the user is logged in 
041     &lt;/w:show&gt;
042     
043     &lt;w:show ifLoggedIn="false"&gt;
044       show tag content only if the user is not logged in 
045     &lt;/w:show&gt;</pre>
046     
047     The above styles are all mutually exclusive. You can specify only 1 attribute at a time with this tag.
048     
049     <P>The body of this class is either echoed as is, or is suppressed entirely.
050     
051     <P>By definition (in the servlet specification), a user is logged in when <tt>request.getUserPrincipal()</tt> 
052     returns a value having content. When a user is logged in, the container can assign 
053     1 or more roles to the user. Roles are only assigned after a successful login.
054    */
055    public final class ShowForRole extends TagHelper {
056    
057      /** Optional, comma-delimited list of accepted roles. */
058      public void setIfRole(String aRoles){
059        fAcceptedRoles = getRoles(aRoles);
060      }
061      
062      /** Optional, comma-delimited list of denied roles.  */
063      public void setIfRoleNot(String aRoles){
064        fDeniedRoles = getRoles(aRoles); 
065      }
066      
067      /** 
068       Optional, simple flag indicating if user is or is not logged in.
069       @param aFlag - see {@link Util#parseBoolean(String)} for the list of accepted values. 
070      */
071      public void setIfLoggedIn(String aFlag){
072        fIfLoggedIn = Util.parseBoolean(aFlag); //null if the flag is null
073      }
074      
075      /**
076       One and only one of the {@link #setIfRole}, {@link #setIfRoleNot}, or 
077       {@link #setIfLoggedIn(String)} attributes must be set.
078      */
079      protected void crossCheckAttributes() {
080        int numAttrsSpecified = 0;
081        if (isModeAcceptingRoles()) ++numAttrsSpecified;
082        if (isModeDenyingRoles())  ++numAttrsSpecified;
083        if (isModeLogin()) ++numAttrsSpecified;
084        
085        if(numAttrsSpecified != 1) {
086          String message = "Please specify 1 (and only 1) attribute for this tag: ifRole, ifRoleNot, or ifLoggedIn. Page Name: " + getPageName();
087          fLogger.severe(message);
088          throw new IllegalArgumentException(message);
089        }
090      }
091      
092      /** See class comment.  */
093      @Override protected String getEmittedText(String aOriginalBody) {
094        boolean showBody = false;
095        Principal user = getRequest().getUserPrincipal();
096        boolean isCurrentlyLoggedIn = (user != null);
097        
098        if (isModeAcceptingRoles() && isCurrentlyLoggedIn){
099          showBody = hasOneOrMoreOfThe(fAcceptedRoles);
100        }
101        else if (isModeDenyingRoles() && isCurrentlyLoggedIn){
102          showBody = ! hasOneOrMoreOfThe(fDeniedRoles);
103        }
104        else if (isModeLogin()){
105          showBody = fIfLoggedIn ? isCurrentlyLoggedIn : ! isCurrentlyLoggedIn; 
106        }
107        return showBody ? aOriginalBody : EMPTY_STRING;
108      }
109      
110      // PRIVATE
111      private List<String> fAcceptedRoles = new ArrayList<String>();
112      private List<String> fDeniedRoles = new ArrayList<String>();
113      /** The null-ity of this field is needed to indicate the 'unspecified' state. */
114      private Boolean fIfLoggedIn; 
115      
116      private static final String DELIMITER = ",";
117      private static final Logger fLogger = Util.getLogger(ShowForRole.class);
118      
119      private List<String> getRoles(String aRawRoles){
120        List<String> result = new ArrayList<String>();
121        StringTokenizer parser = new StringTokenizer( aRawRoles, DELIMITER);
122        while ( parser.hasMoreTokens() ) {
123          result.add( parser.nextToken().trim() );
124        }
125        return result;
126      }
127      
128      private boolean isModeAcceptingRoles(){
129        return ! fAcceptedRoles.isEmpty();
130      }
131      
132      private boolean isModeDenyingRoles(){
133        return ! fDeniedRoles.isEmpty();
134      }
135      
136      private boolean isModeLogin(){
137        return fIfLoggedIn != null;
138      }
139    
140      private boolean hasOneOrMoreOfThe(List<String> aRoles){
141        boolean result = FAILS;
142        for (String role: aRoles){
143          if ( getRequest().isUserInRole(role) ) {
144            result = PASSES;
145            break;
146          }
147        }
148        return result;
149      }
150    }