Building a Single Sign-On Module for the BIRT Report Viewer – Part 3-2

This is the fourth (actually second part of the third) post of the BIRT SSO Series wherein I describe the implementation of a single sign-on module for the Eclipse BIRT Report Viewer. The introduction and server configuration are covered in  Part 1 and Part 2 respectively. The Drupal Module component follows in Part 3-1. It is recommended that you read them first in order to get acquainted with the background and the premises on which this solution is built. In this post, I describe the BIRT Module.

3.2: The BIRT Module

In Part 3-1, I described how the Drupal Module encrypts a string of information and sends it over to the BIRT component. Once the BIRT Module has received this encrypted data, it needs to decrypt and process the string to provide or revoke authentication for a particular user. There are 4 classes that are involved in orchestrating this process:

  1. AuthFilter: An instance of javax.servlet.Filter that intercepts all incoming requests to BIRT’s servlets and allows or rejects them based on session authentication.
  2. AuthManagerServlet: A subclass of javax.servlet.http.HttpServlet that receives and processes incoming authentication requests from the Drupal Module.
  3. SessionLifecycleListener: An instance of javax.servlet.http.HttpSessionListener that listens for session events generated by client-connects and session timeouts, and does some voodoo.
  4. Transcoder: A simple utility class that provides methods for decryption and checksums.

Before I get into the nitty-gritties of the individual classes, a little explanation of the overall flow is needed. Briefly, here’s what transpires:

  1. AuthManagerServlet receives an encrypted request. It attempts to decrypt it using Transcoder‘s utility methods. If successful, the following parameters now become available to it:
    1. A universally unique session id (session created by Drupal).
    2. A flag denoting whether this is a login or a logout operation.
    3. timeout which can be set for a newly created session (in case of a login).
  2. In case this is a login operation, this session id is stored in a map (as the key, with NULLfor value).
    1. Every request coming in from the client’s browser will contain a cookie with this session id.
    2. This request has come in directly from the Drupal server, so the session created on the Java side for this request does not map to the actual client browser.
  3. When the client sends its first request to the reporting component, a new Java session is created for it, and the session map is updated with the session id of this session. So now we have a Drupal session mapped to a Java session, cookies for both being stored in the client browser. The timeout value is also set, at this point, for the newly created Java session.
  4. All further requests coming in from the client are validated for the correct Drupal and Java session ids.
  5. If the original encrypted request was a logout operation, then the appropriate entry is removed from the session map.
  6. A session map entry can also be removed if triggered by a session timeout.

Now that we have the flow in mind, let’s dive right into the code. With the background knowledge given above, the logic should be fairly easy to follow.

public class AuthFilter implements Filter
{

  private static final boolean debug = false;
  // The filter configuration object we are associated with.  If
  // this value is null, this filter instance is not currently
  // configured.
  private FilterConfig filterConfig = null;

  public AuthFilter ()
  {
  }

  private boolean validateSession (HttpServletRequest request, Map<String, String> authorizedSessions)
  {
    Cookie[] cookies = request.getCookies ();
    if (cookies != null)
    {
      for (int i = 0; i < cookies.length; ++i)
      {
        String remoteSession = cookies[i].getValue ();
        if (authorizedSessions.containsKey (remoteSession))
        {
          String localSession = authorizedSessions.get (remoteSession);
          String jSessionId = request.getSession ().getId ();
          if (localSession == null)
          {
            authorizedSessions.put (remoteSession, jSessionId);
            return true;
          }
          else if (localSession.equals (jSessionId) )
          {
            return true;
          }
          break;
        }
      }
    }
    return false;
  }

  /**
   *
   * @param request The servlet request we are processing
   * @param response The servlet response we are creating
   * @param chain The filter chain we are processing
   *
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a servlet error occurs
   */
  @Override
  public void doFilter (ServletRequest request, ServletResponse response,
                        FilterChain chain)
      throws IOException, ServletException
  {

    if (debug)
    {
      log ("AuthFilter:doFilter()");
    }

    // Create wrappers for the request and response objects.
    // Using these, you can extend the capabilities of the
    // request and response, for example, allow setting parameters
    // on the request before sending the request to the rest of the filter chain,
    // or keep track of the cookies that are set on the response.
    //
    // Caveat: some servers do not handle wrappers very well for forward or
    // include requests.
    RequestWrapper wrappedRequest = new RequestWrapper ((HttpServletRequest) request);
    ResponseWrapper wrappedResponse = new ResponseWrapper ((HttpServletResponse) response);

    Map<String, String> authorizedSessions = (Map<String, String>) wrappedRequest.getServletContext ().getAttribute (AuthManagerServlet.class.getPackage ().getName () + "." + AuthManagerServlet.class.getName () + ".authorizedSessions");
    if (authorizedSessions == null)
    {
      wrappedResponse.sendError (HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
      return;
    }

    if (!validateSession (wrappedRequest, authorizedSessions))
    {
      wrappedResponse.sendError (HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
      return;
    }

    Throwable problem = null;

    try
    {
      chain.doFilter (wrappedRequest, wrappedResponse);
    }
    catch (IOException | ServletException t)
    {
      // If an exception is thrown somewhere down the filter chain,
      // we still want to execute our after processing, and then
      // rethrow the problem after that.
      problem = t;
    }

    // If there was a problem, we want to rethrow it if it is
    // a known type, otherwise log it.
    if (problem != null)
    {
      if (problem instanceof ServletException)
      {
        throw (ServletException) problem;
      }
      if (problem instanceof IOException)
      {
        throw (IOException) problem;
      }
      sendProcessingError (problem, response);
    }
  }

  /**
   * Return the filter configuration object for this filter.
   */
  public FilterConfig getFilterConfig ()
  {
    return (this.filterConfig);
  }

  /**
   * Set the filter configuration object for this filter.
   *
   * @param filterConfig The filter configuration object
   */
  public void setFilterConfig (FilterConfig filterConfig)
  {
    this.filterConfig = filterConfig;
  }

  /**
   * Destroy method for this filter
   */
  @Override
  public void destroy ()
  {
  }

  /**
   * Init method for this filter
   */
  @Override
  public void init (FilterConfig filterConfig)
  {
    this.filterConfig = filterConfig;
    if (filterConfig != null)
    {
      if (debug)
      {
        log ("AuthFilter: Initializing filter");
      }
    }
  }

  /**
   * Return a String representation of this object.
   */
  @Override
  public String toString ()
  {
    if (filterConfig == null)
    {
      return ("AuthFilter()");
    }
    StringBuilder sb = new StringBuilder ("AuthFilter(");
    sb.append (filterConfig);
    sb.append (")");
    return (sb.toString ());

  }

  private void sendProcessingError (Throwable t, ServletResponse response)
  {
    String stackTrace = getStackTrace (t);

    if (stackTrace != null && !stackTrace.equals (""))
    {
      try
      {
        response.setContentType ("text/html");
        try (PrintStream ps = new PrintStream (response.getOutputStream ()); PrintWriter pw = new PrintWriter (ps))
        {
pw.print ("\n\nError\n\n\n"); //NOI18N

          // PENDING! Localize this for next official release
          pw.print ("</pre>
<h1>The resource did not process correctly</h1>
<pre>

\n
\n");
          pw.print (stackTrace);
          pw.print ("

\n"); //NOI18N
 }
 response.getOutputStream ().close ();
 }
 catch (Exception ex)
 {
 }
 }
 else
 {
 try
 {
 try (PrintStream ps = new PrintStream (response.getOutputStream ()))
 {
 t.printStackTrace (ps);
 }
 response.getOutputStream ().close ();
 }
 catch (Exception ex)
 {
 }
 }
 }

 public static String getStackTrace (Throwable t)
 {
 String stackTrace = null;
 try
 {
 StringWriter sw = new StringWriter ();
 PrintWriter pw = new PrintWriter (sw);
 t.printStackTrace (pw);
 pw.close ();
 sw.close ();
 stackTrace = sw.getBuffer ().toString ();
 }
 catch (Exception ex)
 {
 }
 return stackTrace;
 }

 public void log (String msg)
 {
 filterConfig.getServletContext ().log (msg);
 }

 /**
 * This request wrapper class extends the support class HttpServletRequestWrapper, which implements all the methods in the
 * HttpServletRequest interface, as delegations to the wrapped request. You only need to override the methods that you need to change. You
 * can get access to the wrapped request using the method getRequest()
 */
 class RequestWrapper extends HttpServletRequestWrapper
 {

 public RequestWrapper (HttpServletRequest request)
 {
 super (request);
 }
 // You might, for example, wish to add a setParameter() method. To do this
 // you must also override the getParameter, getParameterValues, getParameterMap,
 // and getParameterNames methods.
 protected HashMap localParams = null;

 public void setParameter (String name, String[] values)
 {
 if (debug)
 {
 System.out.println ("AuthFilter::setParameter(" + name + "=" + values + ")" + " localParams = " + localParams);
 }

 if (localParams == null)
 {
 localParams = new HashMap ();
 // Copy the parameters from the underlying request.
 Map wrappedParams = getRequest ().getParameterMap ();
 Set keySet = wrappedParams.keySet ();
 for (Iterator it = keySet.iterator (); it.hasNext ();)
 {
 Object key = it.next ();
 Object value = wrappedParams.get (key);
 localParams.put (key, value);
 }
 }
 localParams.put (name, values);
 }

 @Override
 public String getParameter (String name)
 {
 if (debug)
 {
 System.out.println ("AuthFilter::getParameter(" + name + ") localParams = " + localParams);
 }
 if (localParams == null)
 {
 return getRequest ().getParameter (name);
 }
 Object val = localParams.get (name);
 if (val instanceof String)
 {
 return (String) val;
 }
 if (val instanceof String[])
 {
 String[] values = (String[]) val;
 return values[0];
 }
 return (val == null ? null : val.toString ());
 }

 @Override
 public String[] getParameterValues (String name)
 {
 if (debug)
 {
 System.out.println ("AuthFilter::getParameterValues(" + name + ") localParams = " + localParams);
 }
 if (localParams == null)
 {
 return getRequest ().getParameterValues (name);
 }
 return (String[]) localParams.get (name);
 }

 @Override
 public Enumeration getParameterNames ()
 {
 if (debug)
 {
 System.out.println ("AuthFilter::getParameterNames() localParams = " + localParams);
 }
 if (localParams == null)
 {
 return getRequest ().getParameterNames ();
 }
 return Collections.enumeration (localParams.keySet ());
 }

 @Override
 public Map getParameterMap ()
 {
 if (debug)
 {
 System.out.println ("AuthFilter::getParameterMap() localParams = " + localParams);
 }
 if (localParams == null)
 {
 return getRequest ().getParameterMap ();
 }
 return localParams;
 }
 }

 /**
 * This response wrapper class extends the support class HttpServletResponseWrapper, which implements all the methods in the
 * HttpServletResponse interface, as delegations to the wrapped response. You only need to override the methods that you need to change.
 * You can get access to the wrapped response using the method getResponse()
 */
 class ResponseWrapper extends HttpServletResponseWrapper
 {

 public ResponseWrapper (HttpServletResponse response)
 {
 super (response);
 }
 // You might, for example, wish to know what cookies were set on the response
 // as it went throught the filter chain. Since HttpServletRequest doesn't
 // have a get cookies method, we will need to store them locally as they
 // are being set.
 /*
 * protected Vector cookies = null;
 *
 * // Create a new method that doesn't exist in HttpServletResponse public Enumeration getCookies() { if (cookies == null) cookies =
 * new Vector(); return cookies.elements(); }
 *
 * // Override this method from HttpServletResponse to keep track // of cookies locally as well as in the wrapped response. public void
 * addCookie (Cookie cookie) { if (cookies == null) cookies = new Vector(); cookies.add(cookie);
 * ((HttpServletResponse)getResponse()).addCookie(cookie); }
 */
 }
}
public class AuthManagerServlet extends HttpServlet
{

  public static final String ENCRYPTION_KEY = "ENCRYPTION_KEY";
  private Map<String, String> authorizedSessions = new HashMap<> ();
  private String encryptionKey, initialVector = null;
  private Timer timer = new Timer (true);

  private enum Operations
  {

    LOGIN, LOGOUT
  }

  /**
   * Processes requests for both HTTP
   * <code>GET</code> and
   * <code>POST</code> methods.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  protected void processRequest (HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException
  {
    response.setContentType ("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter ();
    String message = null;
    try
    {
      String encData = request.getParameter ("data");
      if (encData == null)
      {
        message = "Operation failed. Data cannot be null.";
        response.sendError (HttpServletResponse.SC_UNAUTHORIZED, message);
      }
      else
      {
        String params = Transcoder.decrypt (encData, initialVector, encryptionKey);
        StringTokenizer st = new StringTokenizer (params, "&=");
        final Map<String, String> paramMap = new HashMap<> ();
        while (st.hasMoreTokens ())
        {
          String name = st.nextToken ();
          String value = st.nextToken ();
          paramMap.put (name, value);
        }

        Operations ops;
        try
        {
          ops = Operations.valueOf (paramMap.get ("op").toUpperCase ());
          switch (ops)
          {
            case LOGIN:
              authorizedSessions.put (paramMap.get ("session_id"), null);
              int timeout = Integer.parseInt (paramMap.get ("timeout"));

              //Timeout is received with each auth request, but is set globally for all future sessions.
              request.getServletContext ().setAttribute (getClass ().getPackage ().getName () + "." + getClass ().getName () + ".sessionTimeout", timeout);
              message = "Login successful.";
              break;
            case LOGOUT:
              authorizedSessions.remove (paramMap.get ("session_id"));
              message = "Logout successful.";
              break;
          }
        }
        catch (IllegalArgumentException e)
        {
          message = "Operation failed. Invalid op.";
          response.sendError (HttpServletResponse.SC_UNAUTHORIZED, message);
        }
      }
    }
    finally
    {
      out.println (message);
      out.close ();
    }
  }

  //
  /**
   * Handles the HTTP
   * <code>GET</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doGet (HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException
  {
    processRequest (request, response);
  }

  /**
   * Handles the HTTP
   * <code>POST</code> method.
   *
   * @param request servlet request
   * @param response servlet response
   * @throws ServletException if a servlet-specific error occurs
   * @throws IOException if an I/O error occurs
   */
  @Override
  protected void doPost (HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException
  {
    processRequest (request, response);
  }

  /**
   * Returns a short description of the servlet.
   *
   * @return a String containing servlet description
   */
  @Override
  public String getServletInfo ()
  {
    return "Simple Authentication Manager Servlet, based on background info recieved from authorized servers.";
  }//

  @Override
  public void init (ServletConfig config) throws ServletException
  {
    super.init (config);

    ServletContext context = config.getServletContext ();
    context.setAttribute (getClass ().getPackage ().getName () + "." + getClass ().getName () + ".authorizedSessions", authorizedSessions);

    encryptionKey = context.getInitParameter (ENCRYPTION_KEY);
    if (encryptionKey == null)
    {
      Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, "Error!! Encryption key not found.");
    }
    try
    {
      initialVector = Transcoder.md5 (Transcoder.md5 (encryptionKey)).substring (0, 16);
    }
    catch (NoSuchAlgorithmException ex)
    {
      Logger.getLogger (getClass ().getName ()).log (Level.SEVERE, null, ex);
    }
  }
}
@WebListener ()
public class SessionLifecycleListener implements HttpSessionListener
{

  @Override
  public void sessionCreated (HttpSessionEvent hse)
  {
    int sessionTimeout = (int) hse.getSession ().getServletContext ().getAttribute (AuthManagerServlet.class.getPackage ().getName () + "." + AuthManagerServlet.class.getName () + ".sessionTimeout");
    if (sessionTimeout <= 0)
    {
      sessionTimeout = 600;
    }
    hse.getSession ().setMaxInactiveInterval (sessionTimeout);
  }

  @Override
  public void sessionDestroyed (HttpSessionEvent hse)
  {
    Map<String, String> authorizedSessions = (Map<String, String>) hse.getSession ().getServletContext ().getAttribute (AuthManagerServlet.class.getPackage ().getName () + "." + AuthManagerServlet.class.getName () + ".authorizedSessions");
    String jSessionId = hse.getSession ().getId ();
    Collection values = authorizedSessions.values ();
    for (Iterator i = values.iterator (); i.hasNext ();)
    {
      String value = i.next ();
      if (jSessionId.equals (value))
      {
        authorizedSessions.remove (value);
        break;
      }
    }
  }
}
public class Transcoder
{

  private Transcoder ()
  {
  }

  public static String md5 (String input) throws NoSuchAlgorithmException
  {
    MessageDigest md = MessageDigest.getInstance ("MD5");
    byte[] messageDigest = md.digest (input.getBytes ());
    BigInteger number = new BigInteger (1, messageDigest);
    return number.toString (16);
  }

  public static String decrypt (String encryptedData, String initialVectorString, String secretKey)
  {
    String decryptedData = null;
    try
    {
      SecretKeySpec skeySpec = new SecretKeySpec (md5 (secretKey).getBytes (), "AES");
      IvParameterSpec initialVector = new IvParameterSpec (initialVectorString.getBytes ());
      Cipher cipher = Cipher.getInstance ("AES/CFB8/NoPadding");
      cipher.init (Cipher.DECRYPT_MODE, skeySpec, initialVector);
      byte[] encryptedByteArray = (new org.apache.commons.codec.binary.Base64 ()).decode (encryptedData.getBytes ());
      byte[] decryptedByteArray = cipher.doFinal (encryptedByteArray);
      decryptedData = new String (decryptedByteArray, "UTF-8");
    }
    catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e)
    {
      Logger.getLogger (Transcoder.class.getName ()).log (Level.SEVERE, "Problem decrypting the data", e);
    }
    return decryptedData;
  }
}

Finally a few entries go into web.xml to tie it all together:

<!--
Report resources directory for preview. Defaults to ${birt home}
-->
<context-param>
<param-name>BIRT_VIEWER_WORKING_FOLDER</param-name>
<param-value>report</param-value>
</context-param>

<context-param>
    <description>The secret key used to encrypt/decrypt informations between servers and browsers. Must never be transmitted!</description>
    <param-name>ENCRYPTION_KEY</param-name>
    <param-value>{your encryption key}</param-value>
</context-param>

<filter>
    <filter-name>AuthFilter</filter-name>
    <filter-class>{package.path}.AuthFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <servlet-name>EngineServlet</servlet-name>
</filter-mapping>
<filter-mapping>
    <filter-name>AuthFilter</filter-name>
    <servlet-name>ViewerServlet</servlet-name>
</filter-mapping>

<servlet>
    <servlet-name>AuthManagerServlet</servlet-name>
    <servlet-class>{package.path}.AuthManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>AuthManagerServlet</servlet-name>
    <url-pattern>/smanage</url-pattern>
</servlet-mapping>

<session-config>
    <session-timeout>10</session-timeout>
</session-config>

<listener>
    <listener-class>{package.path}.SessionLifecycleListener</listener-class>
</listener>

That concludes this series on implementing an SSO for the Birt Report Viewer. Hope it helps you build your own custom implementation. If you have any questions, observations or suggestions, just leave a comment and I’ll answer as best as I can.

Advertisements

, , , , ,

  1. #1 by Draco on February 13, 2013 - 8:36 am

    Initially the authorizedSessions map is null. Say, when I send my first request, irrespective of ‘/smanage’ or ‘/frameset’, the filter(AuthFilter) blocks the request, and since the map is null, it becomes unauthorized. My questions are 1.) how to make an entry to the map for the first time i.e., how to reach AuthManagerServlet to put an entry to the map? 2.) In web.xml, while adding the above entries, should the default filter entry be removed? 3.) Line 73 of AuthFilter Map authorizedSessions = (Map) wrappedRequest.getServletContext ().getAttribute (AuthManagerServlet.class.getPackage ().getName () + “.” + AuthManagerServlet.class.getName () + “.authorizedSessions”); is missing getSession(); i.e, it should be ‘wrappedRequest.getSession().getServletContext()….’ Also, does AuthManagerServlet.class.getName () initialize the Servlet by getting the class object?

    • #2 by Aditya Mukhopadhyay on February 13, 2013 - 8:10 pm

      Hi Draco,
      1. It seems the authorizedSessions map is initially null because the server is lazy in initializing the servlet instance. So the first time a request hits the server, the servlet is not yet instantiated and the filter (which comes first in the filter chain) finds no hash map in the session. You can try moving/duplicating the relevant code from the servlet’s init method to the filter’s init method and see if that solves your problem.

      2. The ViewerFilter and its mappings should be left in web.xml as is. No change is required.

      3. getServletContext() is a method available in javax.servlet.ServletRequestWrapper, and should therefore by inheritance be available in wrappedRequest as well.

      Hope that helps. Thanks for pointing out the issues with the null hash map.

      • #3 by Draco on February 14, 2013 - 10:18 am

        Thanks for the reply man! Code works fine as it is. No change needed. For the first time, the URL pattern should be ‘/smanage’ so the AuthFilter which has the line ‘AuthManagerServlet.class.getName ()’ tries to get the servlet class object thereby initializing the servlet and creates the map, decrypts the encrypted text from the request and an entry is made to the map. The URL pattern following the initial request from the same session would be ‘/frameset’ to view live reports.

  2. #4 by Aditya Mukhopadhyay on February 14, 2013 - 10:25 am

    @Draco Cool! Thanks for testing the code out!

  3. #5 by Wagner on October 3, 2013 - 2:26 pm

    Hi!
    Nice post! Thanks for sharing your experiences 😉
    It has been very enlightening for me.

  4. #6 by Wagner on October 3, 2013 - 2:42 pm

    Hi!

    Perhaps a dumb questions, but I have to ask:
    In which directory in tomcat are you deploying the java classes?
    Which format are you packaging them(jar, war)?
    Thanks!

    • #7 by Aditya Mukhopadhyay on October 3, 2013 - 2:47 pm

      The BIRT war is deployed like a standard tomcat webapp, in the webapps folder. Just drop the war file in there and tomcat should automatically deploy it.

  5. #8 by noise on October 18, 2013 - 2:29 am

    Hi i’m a noob at this and your tutorial is great but i tried this on a drupal 6 environment translated the code from drupal 7 to drupal 6 and i cant seem to get the authfilter to accept the post request im sending the drupal_http_request result returns an error Code: -13 Error: Permission denied. can you please help?

    • #9 by Aditya Mukhopadhyay on October 18, 2013 - 6:03 pm

      @noise

      I suggest testing the authfilter by using curl to send a POST request with the appropriate headers and options. That should give a hint on whether this issue is on the authfilter side or the drupal side.

      • #10 by noise on October 19, 2013 - 2:37 am

        I figured out what went wrong and finally got the post request through but i encountered another error at this line:

        cipher.init (Cipher.DECRYPT_MODE, skeySpec, initialVector);

        The error message being:
        javax.security.InvalidKeyException: Invalid AES Key Length: 31 bytes.

        this is the code in the module for sending the post data (nothing much has changed from the tutorial):

        $iv = substr(md5(md5($key)) , 0, 16);
        $data = _birt_reports_encrypt($params, $iv, $key);

        $options[‘data’] = ‘data=’ . urlencode($data);
        $result = drupal_http_request($url, $options[‘headers’], $options[‘method’], $options[‘data’]);

        the function for the data(also the same as the tutorial):
        function _birt_reports_encrypt($message, $initialVector, $secretKey)
        {
        return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, md5($secretKey) , $message, MCRYPT_MODE_CFB, $initialVector));
        }

        any ideas would be very much helpful thank you.

        looking at the $iv drupal side and comparing it to the initialvector string at the transcoder decrpyt function side shows different values. Could that be the problem?

  6. #11 by Aditya Mukhopadhyay on October 20, 2013 - 8:36 am

    Did you install the ‘ Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files’ as explained in part 2 ( https://notesfromheck.wordpress.com/2012/10/28/building-a-single-sign-on-module-for-the-birt-report-viewer-part-2/ )?

    • #12 by noise on October 21, 2013 - 7:57 pm

      yup, tested if its installed via this function:

      System.out.println(Cipher.getMaxAllowedKeyLength(“AES”));

      it retuns this value: 2147483647

      • #13 by Aditya Mukhopadhyay on October 22, 2013 - 7:39 pm

        The different initial vectors would result in different ciphers, breaking the decryption routine. Are the encryption keys the same on both sides? On Drupal 7, this is set at the field instance level. If you are using CCK for Drupal 6, then you would have the equivalent field settings form to do the same.

  7. #14 by noise on October 28, 2013 - 11:30 pm

    yes the encryption keys are the same. So i would need to install the cck for drupal 6 module to fix that?

    • #15 by Aditya Mukhopadhyay on October 29, 2013 - 8:31 am

      Not absolutely necessary. If you’re implementing the same field structure as given in the example, CCK would be the logical D6 counterpart to the Fields API. You can, however, simply things greatly by just setting and retrieving the encryption key in D6 at a global level, maybe by storing it in the variables table. The code to set and access the key would have to be modified accordingly.

  8. #16 by noise on October 29, 2013 - 12:15 am

    I bypassed the encryption code for now since im working on my localhost. I came across a problem with request.getCookies (); it returns null. I have no idea what went wrong there? my apache server and my tomcat server are on the same linux server so why does i return null?

    • #17 by noise on October 29, 2013 - 1:38 am

      nevermind the last post i got it to work and it works perfectly my only problem now is the encryption and why it doesn’t decrypt right.

      • #18 by Aditya Mukhopadhyay on October 29, 2013 - 8:36 am

        The encryption key is processed by AuthManagerServlet, while the cookies are looked up in the AuthFilter. The auth filter is not supposed to intercept requests coming in from the Drupal server (which are processed by the servlet). You probably have an error in your filter mappings in web.xml.

  9. #19 by noise on November 6, 2013 - 3:15 am

    Hey i got the encryption working ang got was able to authenticate a user upon log in by user on the drupal 6 site and also checking the tomcat manager app i see the session id for it there for that cookie but my problem now is that i can’t view the report. It doesn’t give a 401 right i see the birt report viewer load first but when i try to display the report it gives me a 401. Could the cause of this be the birt report viewer version? mine is Viewer Version : 3.7.2 Engine Version: 3.7.2 JRE version: 1.7.0_25. thnx this tutorial has been a lot of help.

    • #20 by Aditya Mukhopadhyay on November 6, 2013 - 1:56 pm

      Have you checked the source of the 401? Is it coming from the AuthFilter, or from BIRT directly?

      • #21 by noise on November 6, 2013 - 9:00 pm

        I believe it is from BIRT. I tried using Birt viewer version 4.3.1 and birt viewer 3.7.2 for this and it worked fine in 4.3 but not in 3.7.2…

  10. #22 by navdeeds on January 23, 2014 - 5:27 pm

    Hey Aditya,

    I am working on BIRT report and do not have access to the php and codebase other than MYSQL database and BIRT Designer. Report is very simple and already done with that, the only concern and issue is i don’t know how to get logged in user session to BIRT report to filter out logged in user data. I need some way directly into BIRT so that i can get user id… here is the flow,

    1) Main drupal site showing different report links (example: http://mydrupalwebsite.org:8081/frameset?__report=testReports1.rptdesign)

    2) the links don’t have any additional parameter…like session key or user session

    3) I need the session object OR user name so that before showing report result i can filter out result set to show only that user specific result

    • #23 by Aditya Mukhopadhyay on January 24, 2014 - 10:16 am

      The user info has to be somehow passed to the BIRT system for the filtering to take place. If not through a URL parameter, then it must have already been set in the servlet session before the report is called. If you manage to deploy this tutorial at your setup, it shouldn’t be too difficult to pass on some additional info to identify the Drupal user, during authentication.

  11. #24 by josh on March 28, 2014 - 1:57 am

    Hi Aditya,
    I feel I am very lucky to find your excellent writing about how to add authentication mechanism to BIRT. Would you please let me ask one basic question about the filter? I deployed a servlet for the example code above with web.xml where AuthFilter mappings are defined for both EngineServlet and ViewerServlet, but doFilter() won’t get called. Is it a correct practice to specify a filter for the servlet in another war file, in this case BIRT’s? I see AuthFilter gets initialized when debug=true. I am new to Servlet filter, so your insight will be highly appreciated.
    Thanks!
    – Josh

    • #25 by Aditya Mukhopadhyay on March 28, 2014 - 7:14 am

      Hi Josh,
      It wouldn’t work if you define the filter for a servlet in a different war. See my reply below on how to merge the custom classes into the existing war.

  12. #26 by josh on March 28, 2014 - 2:28 am

    It’s me josh, I just realized that I need to build everything above as jar and deploy it in BIRT’s webapps. Hopefully that should be it.

    • #27 by Aditya Mukhopadhyay on March 28, 2014 - 7:12 am

      Hi Josh,
      You can deploy the OOTB BIRT war file first in tomcat, which will explode it into its own folder in the webapps directory. You can then edit the web.xml in place, and also add the filter, servlet classes in the WEB-INF/classes directory.

      Once all files are in place, you can of course repackage it into a war to deploy elsewhere.

      Not sure, but it looks like this is what you were asking?

      • #28 by josh on March 28, 2014 - 7:42 pm

        Thanks for deciphering what I wrote as one line comment. I followed your instruction by creating a jar with four java files and placed it in lib of where BIRT war was expanded, then it worked! I’ve got HTTP Status 401 – Unauthorized just as expected when browsing frameset directly without Drupal layer.

        Now I moved on to Drupal/Servlet connectivity. I’m unable to get my Drupal’s login session auth’d by AuthManagerServlet. SessionId get’s created in birt_reports_sessions so the process is stuck somewhere between drupal_http_request($url, $options[sess_id,op,timeout]) and the servlet taking that request as it always returns with 401. Would $url=http://localhost/birt/frameset and $options=array(‘data’=>BASE64_ENCODED_PARAMS) with POST sound correct?

  13. #29 by Aditya Mukhopadhyay on March 28, 2014 - 8:01 pm

    Hmm.. when you created the jar, I’m assuming you just bundled the 4 classes into it? Did you also merge the web.xml snippet (last file in article) into the BIRT webapp’s web.xml?

    • #30 by josh on March 28, 2014 - 8:08 pm

      Your reply is blazingly fast! Yes I did package four java files. Actually, I located the problem at failing validation (validateSession (wrappedRequest, authorizedSessions)) in AuthFilter.java. I will look for the reason why its failing. Will post my finding hopefully while you are still awake tonight.

      • #31 by josh on March 28, 2014 - 9:13 pm

        What I am seeing is alwaysNull condition for authorizedSessions.keySet(), maybe the same problem as the first post by Draco on this timeline? Need to be out for a while, will continue research later.

    • #32 by josh on March 29, 2014 - 7:23 am

      Now everything works. I am not an expert of message digest thing but the last line of md5() in Transcoder.java should return zero-padded 32-byte AES key, that means the code would read like String.format(“%032x”,number). My secretKey had an issue with this and the modified code worked fine.

      • #33 by Aditya Mukhopadhyay on March 29, 2014 - 7:36 am

        Interesting observation. Strange why I didn’t run into this padding issue myself. Could the message digest be architecture/platform dependent, I wonder.

        Thanks for the heads up!

  1. Building a Single Sign-On Module for the BIRT Report Viewer – Part 3-1 « Notes from Heck

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: