Grouper UI authentication

The Grouper UI will check request attribute REMOTE_USER, or request.getUserPrincipal(), or request.getRemoteUser(), so it should work with common SSO solutions (e.g. Shib, Cosign, etc)

See debug information in logs in log4j.properties

log4j.logger.edu.internet2.middleware.grouper.ui.GrouperUiFilter = DEBUG



Here is an example of doing something else... in the web.xml declare your filter:

{code}
<filter>
    <filter-name>Your Filter</filter-name>
    <filter-class>com.path.whatever.YourFilter</filter-class>
</filter>
{code}
Note, this part Im not sure about... I think you can just protect everything \(*), though you could pick and choose URL patterns like the existing config does...&nbsp;&nbsp;&nbsp; Note, this part has to be above the existing filter mappings in the web.xml so it is outside the other filters.

{code}
 <filter-mapping>
    <filter-name>Your Filter</filter-name>
    <url-pattern>* </url-pattern>
 </filter-mapping>
{code}
Make a Java class called that, and do this: here is a simple example that gets the username from a cookie (note, you should use encryption or a lookup for a token for this design)


{code}
 /*
 * @author mchyzer
 * $Id: WebsecFilter.java,v 1.3 2009-11-25 20:01:26 mchyzer Exp $
 */
package com.path.whatever;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;


/**
 *
 */
public class YourFilter implements Filter {

  /**
   * @see javax.servlet.Filter#destroy()
   */
  public void destroy() {
  }

  /**
   * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
   */
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
      throws IOException, ServletException {
   
    HttpServletRequest request  = (HttpServletRequest)req;
   
    String username = cookieValue(request, "someCookieName");
   
    //do some decryption or something?  :)
   
    request.setAttribute("REMOTE_USER", username);
   
    filterChain.doFilter(request, res);
  }

  /**
   * get a cookie value by name, null if not there
   * @param httpServletRequest
   * @param name
   * @return the cookie value or null if not there
   */
  public static String cookieValue(HttpServletRequest httpServletRequest, String name) {
    Cookie cookie = findCookie(httpServletRequest, name);
    return cookie == null ? null : cookie.getValue();
  }

  /**
   * find a cookie or null if cant find
   * @param httpServletRequest
   * @param name
   * @return the cookie or null if not found
   */
  public static Cookie findCookie(HttpServletRequest httpServletRequest, String name) {
    //no nulls
    if (name != null) {
      Cookie[] cookies = httpServletRequest.getCookies();
      //go through all cookies and find the cookie by name
      int cookiesLength = cookies == null ? 0 : cookies.length;
      for (int i=0;i<cookiesLength;i++) {
        if (name.equals(cookies[i].getName())) {
          return cookies[i];
        }
      }
    }
    return null;
  }

 
  /**
   * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
   */
  public void init(FilterConfig arg0) throws ServletException {
  }

}
{code}

h4. Previous documentation

Gary already has an example of custom Grouper UI authentication, the [Yale CAS auth|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/yale-cas-auth/?root=I2MI].&nbsp; I configured the grouper UI to work with [Penn's single signon|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/?root=I2MI] , and I thought another example committed to grouper UI cvs would be useful for people (not because you would use Penn's SSO, but because you might integrate with the UI similarly).&nbsp; Here are the steps I used to get it to work:

1. Add a new eclipse project (mine is in the same as UI in contrib, but you will probably keep in your local source control), this will depend on the grouper-ui project.&nbsp; Here is what [my dir looks like|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/?root=I2MI]



2. There is a [build.xml|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/build.xml?root=I2MI&view=co] which will be linked from the grouper-ui build.xml.  I kept things simple by not having any build config params, you might have a build.properties...


3. You need a custom web.something.xml which will merge into the web.core.xml.&nbsp; In my case, it is [web.penn.xml|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/web.penn.xml?root=I2MI&view=co], and it again is simplified, I just protect all .do resources (even public ones, Im ok having everything protected I think), but the URL we publish will probably be a protected page (e.g. grouper/home.do), and not the public one (grouper/).

4. That [filter we added to the web.xml|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/java/src/edu/upenn/isc/grouper_ui/security/WebsecFilter.java?root=I2MI&view=co] will do two things, first it will redirect the user to our single signon login screen if there is no detected user.&nbsp; Second, it [wraps the HttpSevletRequest object|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/java/src/edu/upenn/isc/grouper_ui/security/PennWebsecRequestWrapper.java?root=I2MI&view=co] so that any calls to getRemoteUser() (which is what grouper-ui uses to get the logged in user) will get the user from the token passed in from single signon.

5. The [request wrapper|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/java/src/edu/upenn/isc/grouper_ui/security/PennWebsecRequestWrapper.java?root=I2MI&view=co] caches the user identity, but makes sure the token from single signon doesnt change (e.g. if a user logs in after another user didnt log off).&nbsp; If there is a mismatch, it kills the session and cookies which should allow the user to login again.&nbsp; If the identity is in session cache, use it (since it is expensive to decode a token).&nbsp; If not, then decode the token (if there wasnt a token, the user will be redirected to the login page by the filter above).&nbsp; To decode the token, we use [our jar|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/java/lib/fastWebsec.jar?root=I2MI&view=log] which calls a command line program which has the security associated with it.&nbsp; Then cache the result.

6. If you dont have an additional-build.xml file for grouper-ui (used to link additional build steps without editing the build.xml directly), then you can copy and [example|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/additional-build.template.xml?root=I2MI&view=co], and rename to additional-build.xml.&nbsp; Edit the contents to point to your build.xml for your auth mechanism (e.g. build.xml above).&nbsp; Note this build file must have a webapp and resources target.

7. In your build.properties for grouper-ui, specify where this additional-build.xml is, e.g.
{noformat}
#add an additonal build file to incorporate site specific changes
additional.build=additional-build.xml
{noformat}
8. For configuration settings, you can either use params in the web.xml (like Gary's example), or a config file (I used media.properties since I dont expect to have to change my settings, but if so I want a way without compiling).&nbsp; Note that my use of media.properties happens before the local.media.properties is considered since the user isnt logged yet, so again, it might not be a good example, up to you.&nbsp; You can look in the [request wrapper|http://anoncvs.internet2.edu/cgi-bin/viewvc.cgi/grouper-ui/contrib/penn-auth/java/src/edu/upenn/isc/grouper_ui/security/PennWebsecRequestWrapper.java?root=I2MI&view=co] for my example.


9. Remove the simple auth in the web.core.xml
{noformat}
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Tomcat login</web-resource-name>
      <url-pattern>/login.do</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <!-- NOTE:  This role is not present in the default users file -->
       <role-name>grouper_user</role-name>
    </auth-constraint>
  </security-constraint>

  <!-- Define the Login Configuration for this Application -->
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>Grouper Application</realm-name>
  </login-config>

  <!-- Security roles referenced by this web application -->
  <security-role>
    <description>
      The role that is required to log in to the Manager Application
    </description>
    <role-name>grouper_user</role-name>
  </security-role>
{noformat}<!--  /* Style Definitions */  table.MsoNormalTable 	{mso-style-name:"Table Normal"; 	mso-tstyle-rowband-size:0; 	mso-tstyle-colband-size:0; 	mso-style-noshow:yes; 	mso-style-priority:99; 	mso-style-qformat:yes; 	mso-style-parent:""; 	mso-padding-alt:0in 5.4pt 0in 5.4pt; 	mso-para-margin:0in; 	mso-para-margin-bottom:.0001pt; 	mso-pagination:widow-orphan; 	font-size:11.0pt; 	font-family:"Calibri","sans-serif"; 	mso-ascii-font-family:Calibri; 	mso-ascii-theme-font:minor-latin; 	mso-hansi-font-family:Calibri; 	mso-hansi-theme-font:minor-latin;} -->True, we should add that capability... I assume you have a way so that nefarious users don’t set their cookie value to act as other users... \:)

This is what you need to do:

1. in the web.xml declare your filter:

<filter>

&nbsp;&nbsp;&nbsp;<filter-name>Your Filter</filter-name>

&nbsp;&nbsp;&nbsp;<filter-class>com.path.whatever.YourFilter</filter-class>

&nbsp; </filter>

Note, this part Im not sure about... I think you can just protect everything \(*), though you could pick and choose URL patterns like the existing config does...&nbsp;&nbsp;&nbsp; Note, this part has to be above the existing filter mappings in the web.xml so it is outside the other filters.

<filter-mapping>

&nbsp; &nbsp;&nbsp;&nbsp; <filter-name>Your Filter</filter-name>

&nbsp; &nbsp;&nbsp;&nbsp; <url-pattern>\* </url-pattern>

&nbsp;</filter-mapping>

2. Make a Java class called that, and do this: here is a simple example

/\*

&nbsp;\* @author mchyzer

&nbsp;\* $Id: WebsecFilter.java,v 1.3 2009-11-25 20:01:26 mchyzer Exp $

&nbsp;\*/

package com.path.whatever;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

/*\*

&nbsp;\*

&nbsp;\*/

public class YourFilter implements Filter {

&nbsp; /*\*

&nbsp;&nbsp; \* @see javax.servlet.Filter#destroy()

&nbsp;&nbsp; \*/

&nbsp; public void destroy() {

&nbsp; }

&nbsp; /*\*

&nbsp;&nbsp; \* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)

&nbsp;&nbsp; \*/

&nbsp; public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws IOException, ServletException {

&nbsp;&nbsp;&nbsp;

&nbsp;&nbsp;&nbsp;HttpServletRequest request&nbsp; = (HttpServletRequest)req;

&nbsp;&nbsp;&nbsp;

&nbsp;&nbsp;&nbsp; String username = cookieValue(request, "someCookieName");

&nbsp;&nbsp;&nbsp;

&nbsp;&nbsp;&nbsp; //do some decryption or something?&nbsp; \:)

&nbsp;&nbsp;&nbsp;

&nbsp;&nbsp;&nbsp;request.setAttribute("REMOTE_USER", username);

&nbsp;&nbsp;&nbsp;

&nbsp;&nbsp;&nbsp;filterChain.doFilter(request, res);

&nbsp; }

&nbsp; /*\*

&nbsp;&nbsp; \* get a cookie value by name, null if not there

&nbsp;&nbsp; \* @param httpServletRequest

&nbsp;&nbsp; \* @param name

&nbsp;&nbsp; \* @return the cookie value or null if not there

&nbsp;&nbsp; \*/

&nbsp; public static String cookieValue(HttpServletRequest httpServletRequest, String name) {

&nbsp;&nbsp;&nbsp; Cookie cookie = findCookie(httpServletRequest, name);

&nbsp;&nbsp;&nbsp; return cookie == null ? null : cookie.getValue();

&nbsp; }

&nbsp; /*\*

&nbsp;&nbsp; \* find a cookie or null if cant find

&nbsp;&nbsp; \* @param httpServletRequest

&nbsp;&nbsp; \* @param name

&nbsp;&nbsp; \* @return the cookie or null if not found

&nbsp;&nbsp; \*/

&nbsp; public static Cookie findCookie(HttpServletRequest httpServletRequest, String name) {

&nbsp;&nbsp;&nbsp; //no nulls

&nbsp;&nbsp;&nbsp; if (name \!= null) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cookie\[\] cookies = httpServletRequest.getCookies();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //go through all cookies and find the cookie by name

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int cookiesLength = cookies == null ? 0 : cookies.length;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i=0;i<cookiesLength;i++) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (name.equals(cookies\[i\].getName())) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return cookies\[i\];

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp; return null;

&nbsp; }

&nbsp;

&nbsp; /*\*

&nbsp;&nbsp; \* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)

&nbsp;&nbsp; \*/

&nbsp; public void init(FilterConfig arg0) throws ServletException {

&nbsp; }

}