import java.io.*;
import java.util.*;

import javax.servlet.http.*;

import jet.server.api.UserSession;
import jet.server.api.http.HttpExternalAuthorized;
import jet.server.api.http.HttpUtil;



/** 
 * CustomHttpExternalAuthorized.java is a class that
 * implements the HttpExternalAuthorized interface.
 * This provides a way for an existing application
 * to co-operate with JReport Server to manage
 * a Single Sign On system to handle log in to
 * the HTTP Session.
 */

/** 
 * This example implementation of HttpExternalAuthorized is used within 
 * this folder to show how to use this class so that an existing 
 * application can co-operate with the JReport Server 
 * login/security framework used by JSP pages and other servlets.
 * This file enables a Single Sign On function for JReport Server.
 *
 * This class's method getExternalAuthorizedUser() gets called by 
 * both the JReport HttpUtil.checkLogin() and 
 * HttpUserSessionManager.checkLogin() methods as a first step at 
 * identifying if an Authenticated user is logged into this web session.  
 * These checkLogin() methods are used by
 * all JReport JSP pages before they run, to verify that a JReport 
 * user is logged-in to the session.
 *
 * This method is how a user logged into the appliciation can be 
 * seen as logged into JReport Server without the user needing to 
 * do an another login dialog.
 *
 * See the javadoc for HttpUtil.checkLogin() and 
 *        HttpUserSessionManager.checkLogin()
 * for full details on what each checkLogin() method does within 
 * the login framework.
 * 
 * This class's method, handleUnAuthenticatedRequest(), will be 
 * called from HttpUserSessionManager.sendUnauthorizedResponse() 
 * before it sends the "HTTP 401 - Unauthorized" response.
 * (This HTTP 401 response initiates an HTTP Authentication dialog
 * with the browser, which prompts the user for login credentials.)
 * This call out to handlUnAuthenticatedRequest() allows the 
 * method here to handle the case by crafting a custom response 
 * that matches the application's need and then to notify
 * sendUnauthorizedResponse() to not send the HTTP 401 response.
 *
 * This is how to enforce a policy of using a single point of
 * log in using an application's log in system, rather than 
 * using HTTP Authentication for doing a log in.
 * With this method, the default login process and dialog used by 
 * JReport Server can be replaced with the login dialog used 
 * by the application.
 *
 * Note that HttpUtil.checkLogin() calls 
 * HttpUserSessionManager.sendUnAuthorizedResponse() when 
 * there is no user logged-in to the session and there 
 * is a failure to log in a user from credentials in the  
 * HTTP Request header or from credentials in the URL query parameters.  
 *
 * This sample code outputs activity messages to the console, 
 * rather than to a log file.  This is a demo, meant to show such
 * messages as part of the demo, so it is easy to see it is
 * running.  
 *
 * This class needs to be loaded at runtime so that JReport Server 
 * can call it from checkLogin().
 *
 * How to load this class: 
 * To install this demonstration program into JReport Server:
 *
 * 1. Compile it into CustomHttpExternalAuthorized.class 
 *    and put this into a jar file.  Then add a reference
 *    to this jar file into the CLASSPATH used for launching 
 *    JReport Server. 
 *
 *    If the class is put into external.jar in the lib folder, 
 *    add this entry to the CLASSPATH value in JReport.bat:
 *      CLASSPATH=...jar;%REPORTHOME%\lib\external.jar;%REPORTHOME%\lib.....
 *
 *
 * 2. Edit file <SERVER_PATH>/bin/server.bat and 
 *    <SERVER_PATH>/bin/server.sh add a system property to 
 *    the command line when starting the JVM, using the -D parameter
 *    using a key of jrs.httpExternalAuthorized,
 *    and a value of CustomHttpExternalAuthorized
 *
 *    Add:
 *      -Djrs.httpExternalAuthorized=CustomHttpExternalAuthorized
 *
 * 3. Restart JReport Server and this customized user authentication 
 *    class will be loaded and available.
 *    This  demo code will be run as part of JReport login/security framework.
 */
public class CustomHttpExternalAuthorized implements HttpExternalAuthorized
{
    // Hold a list of known JReport users.
    Hashtable <String, String> jUsers = null;

    public CustomHttpExternalAuthorized()
    {
        //******  Write message to console to show that class is loaded.  ****************
        System.out.println("======================CustomHttpExternalAuthorized class is loaded");
    }

    private void updateJRServerUserList()
    {
        System.out.println("===========updateJRServerUserList");
        jUsers.clear();
       
        //****** Get all usernames defined in JReport Server.****************
        //  Use "admin" as the caller of getAllUsers() so the call returns 
        //  the full set of known users.
        //  Note that this list is created once for the class, at load time.
        //  Changes to the set of users will not be noted until the next 
        //  start up of the server.
        for (Enumeration e = HttpUtil.getHttpRptServer().getResourceManager().getAllUsers("admin").elements();
                e.hasMoreElements();) {
            String user = (String) e.nextElement();
            jUsers.put(user, "isUser");
            System.out.println("========adding JReport user="+user);
        }
      System.out.println("======================CustomHttpExternalAuthorized.jUsers: "+jUsers);
        
    }


    /** 
     * Returns a JReport User ID associated with the currently logged in application user. 
     *
     * @param activeRealm the active realm. 
     * @param req the HttpServletRequest object. 
     *
     * @return the JReport User ID associated with the logged-in application user. 
     * Return null when unable to find a JReport User ID, and want to signal
     * the caller to continue it's processing, which leads to sending an UnauthorizedResponse.
     * The returned user ID should be a valid user ID of the report server.
     */
    /** 
     * getExternalAuthorizedUser(String activeRealm, HttpServletRequest req)
     *
     * This signature of the getExternalAuthorizedUser() is called by checkLogin().
     *
     * Returns a JReport User ID associated with the currently logged in application user. 
     *
     * Implement this method by defining a protocol for the mapping from a 
     * logged-in application user to a valid JReport User ID.
     *
     * The HTTP Request holds a session object, which holds state across HTTP Requests.
     * Use this to identify the application's logged in user.
     *
     * This method can identify which applicaton user is logged in and return 
     * the corresponding JReport User ID associated with the logged in application user.
     *
     * The specification for getExternalAuthorizedUser() 
     * is that it will return a valid JReport User ID to its caller, checkLogin.
     * If it returns a User ID that is not a known JReport User ID, then the checkLogin() 
     * processing will treat it as if a null were returned, indicating no application user 
     * is logged into the session, and signalling checkLogin() to try to login in a user
     * using its ways, that are based on the current HTTP request.
     *
     * This example will do a validity check on the value it finds before passing it back
     * to show how to that would be done.
     *
     * When the application user is logged into this session, finding the 
     * corresponding JReport User ID can be done in whatever way the application wannts.
     *
     * Here are some examples of ways to map an applicaton user 
     *  (who is logged in to the session) to the name to use 
     *  as the user's JReport User ID.
     *  - application user name is the JReport User ID
     *  - application user has an associated attribute that is the name to use 
     *                  as the user's JReport User ID
     *  - application user belongs to a group, which has an associated attribute 
     *                  that is the name to use as the user's JReport User ID.
     *  - every application user name always maps to the same JReport User ID.
     *
     * This example will use a simmple mapping from logged-in application user to known 
     * JReport User ID.  The mapping will be based on the imagined simple protocol that 
     * when a user logs into the application, the application's login activity will 
     * create a session variable called "authorized_user" that holds the 
     * JReport User ID for the application user.
     *
     * When this variable exists in the session, it indicates that an application user 
     * is logged in, and that this is the JReport User ID to return for this user.
     * There is nothing in this example code's protocol that cares how the mapping 
     * was done by the application.
     *
     * If there is no application user logged in yet, this code could implement 
     * a policy that allowed an HTTP Request to carry login credentials.
     *
     * The policy could be any of these:
     *   - to not support such a policy, and ignore such credentials.
     *   - to accept identifying information that might exist in the HTTP Request using a
     *          protocl defined by the application (only useful in a secure network).
     *   - to accept login credentials that might exist in the URL query parameters defined 
     *          by JReport Server (only useful in a secure network).
     *   - to engage in an HTTP Authentication dialog with the client 
     *          (only useful in a secure network).
     *
     * (These last two policies are the default policy of checkLogin(), so returning null 
     *  from this method is how to implement these pair of policies.)
     *
     * This example will implement code for the second choice - consider identifying
     * information passed in through the current HTTP Request.
     * In this case, two cases are defined.  
     * 1.  The HTTP Request header can contain a field named "authorized_user" holding
     *     the JReport Server ID to use
     * 2.  The HTTP Request query paramaters can hold a parameter named "authorized_user" 
     *     whose value will be the JReport Server ID to use.
     *
     * This policy is only appropriate for a secure network, where the
     * HTTP Request is not visible to the public.
     *
     * This example code implementing this policy is to show a way
     * to identify a JReport User ID within an HTTP environment when 
     * there is no active user logged in to the session.   
     * This approach would not be used when using a strict Single Sign On system.
     *
     */
    public String getExternalAuthorizedUser(String activeRealm, HttpServletRequest req)
    {
        //  This example will discover if an application user is logged in by looking
        //  for the session variable "authorized_user".  
        //  (The imagined protocol for the application is that the application login 
        //  code will set this variable when a user logs into the application.)
        //
        //  This example will check the value against the list of known JReport Users
        //  and if the value is not a known user, will treat it as an unathorized request.

        // hold the potential JReport User ID
        String jReportUser = null;

        // get the HTTP session object
        HttpSession session = req.getSession(true);
        
        if (jUsers == null) {
            jUsers = new Hashtable<String, String>();
        //****** get all usernames defined in jreport server.****************
            updateJRServerUserList();
        }


        // look for the field in the session that was set by the application's login code.
        if (session.getAttribute("authorized_user") != null){
            jReportUser = session.getAttribute("authorized_user").toString();
            System.out.println("======================getExternalAuthorizedUser.found.user=" + jReportUser);
        }

        // if no user is logged in, look for information in Request Header 
        // following the special application protocol for getting the User ID.
        if (jReportUser == null) {
            // Try to get authorized user info from the customized HTTP request header.
            // Look for the header field "authorized-user".
            jReportUser = req.getHeader("authorized_user");
            System.out.println("======================getExternalAuthorizedUser.header="+jReportUser);
        }

        // if no user ID is idenfied yet, look for information in the query parameters
        // following the special application protocol for getting the User ID.
        if (jReportUser == null) {
            // Try to get authorized user info from the HTTP request parameters.
            // Look for the parameter name "authorized-user".
            jReportUser = req.getParameter("authorized_user");
            System.out.println("======================getExternalAuthorizedUser.param="+jReportUser);
        }
        
        if (jReportUser != null) {
            // validate that the found User ID is a known one
            if (jUsers.get(jReportUser) != null) {

                // found User ID is known.
                System.out.println("======================getExternalAuthorizedUse:authorized user="+jReportUser);
                // authorized
                return jReportUser;
            }
        }

        System.out.println("======================getExternalAuthorizedUser:unauthorized request");
        //unauthorized
        return null;
    }

    /** 
     *
     * getExternalAuthorizedUser(String activeRealm, Object userInfo)
     *
     * This signature of the getExternalAuthorizedUser() is not a known use case.
     * It exists in the Java interface definition for potential future use.
     *
     * The implementation here uses it as a wrapper for the signature that is commonly used.
     *
     * @param activeRealm the active realm. 
     * @param userInfo the object contains user info to get the external authorized user ID. 
     *
     * @return the external authorized user ID. Return null if can not get authorized user info 
     * from the userInfo object. The user ID should be a valid user ID of the report server.
     */
    public String getExternalAuthorizedUser(String activeRealm, Object userInfo) {
        if (userInfo instanceof HttpServletRequest) {
            // Only one userInfo type is supported - the one used by the other signature.
            return getExternalAuthorizedUser(activeRealm, (HttpServletRequest)userInfo);
        } else {
            System.out.println("Unknown userInfo object! " + userInfo);
            return null;
        }
    }

    /** 
     * Ask to invalidate an external authorized user session(i.e. the session expired).
     * The report server calls this method before invalidating the session. 
     * Note: The server does not call this method when an user logs out. 
     * The method notifyLogout(...) will be called after logout.
     * @param userSession the user session. 
     *
     * @return true invalidate the session. false do not invalidate the session.
     */
    public boolean askInvalidate(UserSession userSession)
    {
        System.out.println("======================askInvalidate="+userSession.getUserID());
        //get the user ID
        //String user = userSession.getUserID();

        //return true to invalidate the user session, else return false.
        return true;
    }

    /** 
     * Notify an external authorized user session logout.
     * The report server calls this method after logout. 
     * @param userSession the user session. 
     */

    public void notifyLogout(UserSession userSession)
    {
        System.out.println("======================notifyLogout="+userSession.getUserID());
        //get the user ID
        //String user = userSession.getUserID();

        //can logout from your application system or do nothing.
    }

    /** 
     * Handle the case where an Unauthorized Response should be sent to the client.
     * Called just prior to the caller sending the HTTP 401 message to the client.
     * Do something here and tell the caller to send or not send the HTTP 401 message. 
     * @param req the HttpServletRequest.
     * @param res the HttpServletResponse. 
     * @param authScheme the the authentication scheme. The available values are "Basic" or "Digest".
     * @param realm the realm.
     * @return whether or not the report server should send the HTTP unauthorized response(HTTP 401) to the client. 
     *         If return true, the report server will send the HTTP unauthorized response(HTTP 401) to the client. 
     * @exception  IOException  if a IOException occurs.
     */
    /** 
     *  handleUnAuthenticatedRequest()
     *
     * If there is no application user logged in, this code can implement a policy on what to do.
     *
     * The policy could be any of these:
     *   - to redirect to the application's standard login page.
     *   - to quietly deny service to anyone who gets to the URL without having already logged in.
     *   - to engage in an HTTP Authentication dialog with the client (only useful in a secure network).
     *
     * (This last policy is the default policy of HttpUtil.checkLogin(), so returning true 
     *  when called in that context is how to implement this policy.)
     *
     * This example will do a different thing than any of those policies.
     * It will generate HTML to return to the browser for display, to demonstrate that it was called
     * and to indicate it handled the unauthorized request.
     *
     * Comments in the example code has a code snippet that demonstrates how to redirect to 
     * an application's login page.
     *
     * When the requesting client is not a browser, but is a program written to use the ClientAPI,
     * then this method will not generate HTML for display, but will return to the caller
     * so it will send the HTTP Unauthorized response.
     *
     */
    public boolean handleUnAuthenticatedRequest(HttpServletRequest req,
                                                 HttpServletResponse res,
                                                 String authScheme,
                                                 String realm) throws java.io.IOException {
        // When this request comes from a program using the ClientAPI class, 
        // do not do generate HTML for display.
        if (HttpUtil.isJRClient(req, null)) {
            // Notify the caller to send the HTTP unauthorized response(HTTP 401) to the client.
            return true;
        }

        // -- code example showing redirect to app's login page.
        // What is the URL to the standard login page.
        // Imagine it is set in a system property "app.loginURL"
        //
        // String loginURL=System.getProperty("app.loginURL");
        // if ((loginURL == NULL) || loginURL.trim().equals("")) 
        //     loginURL="appLogin.jsp";
        // }
        // res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
        // res.setHeader("Location", loginURL);
        // res.setHeader("Content-Location", loginURL);

        // generate  HTML to display in browser 
        res.setStatus(HttpServletResponse.SC_OK);
        res.setContentType("text/html");
        PrintWriter writer = res.getWriter();
        writer.println("<html>");
        writer.println("<head>");
        writer.println("<title>Unauthorized Request Noticed</title>");
        writer.println("</head>");
        writer.println("<body>");
        writer.println("<table cellpadding=\"8\" width=\"600\" bgcolor=\"#ffffb6\">");
        writer.println("<tr><td>");
        writer.println("<p>&nbsp;</p>");
        writer.println("<p>&nbsp;</p>");
        writer.println("<H3>Unauthorized request is handled by<br>Single Sign On framework module:<br><br>&nbsp;&nbsp;&nbsp;  CustomHttpExternalAuthorized</H3>");
        writer.println("<p>&nbsp;</p>");
        writer.println("<p>&nbsp;</p>");
        writer.println("</td></tr>");
        writer.println("</table>");
        writer.println("</body>");
        writer.println("</html>");
    
        //notify the report server to not send the HTTP unauthorized response(HTTP 401) to the client.
        return false;
    }
}
