Thursday, 2 November 2023

E-Business Suite and APEX Extending or Integrating

 Following implemented on APEX 22, ORDS 23 and EBS 12.2.11:


General Approach:

Requirements:
  • Users cannot log directly into APEX for EBS integrating applications
  • Users MUST log into Oracle EBS first
  • APEX applications must be assigned to Responsibilities for inclusion in Menus for those responsibilities not to individual users or a custom APEX Responsibility.  For example, an HR APEX application should be accessible from HR specific responsibilities only.  
  • APEX access must work in a mix of environments SSL and non-SSL.  For instance, EBS uses SSL but APEX (being a non-externally facing application) is non-SSL
  • EBS to APEX must not pass anything user identifiable in the URL, including responsibility IDs
  • Each Responsibility group must not be able to access the other responsibilities applications. In other words, HR should not be able to access Sales applications in APEX nor see any menu items for Sales APEX applications 

Caveats:
  • Our code is minimal in both commenting and error handling. This is done on purpose to make it easier to understand and for quick adaptability into your environment (should you choose to use it).
  • Yes we use a customized jsp based on the GWY.jsp.  In order to accomplish the requirement for working in mixed SSL and non-SSL environments, we had to make a non-secure copy of the EBS session cookie that could be read in non-SSL APEX pages. We know, we know... we really should be full SSL everywhere, even if it's non-externally facing.
  • We also realize that this solution is not supported by Oracle and any function call we make today may not work in the future.  But how is this different from any other customization we have to make in house?  Every organization is different and has different needs.  We make do with what we have.
  • We host several APEX instances on a single Glassfish server so urls contexts may seem a little strange.  I will post how we accomplished this at a later date.
  • The LAST and MOST IMPORTANT thing we have to mention.  Using cookies means both servers MUST BE ON THE SAME DOMAIN.  This is a strict cookie and session management requirement set in stone many, many years ago. So if your EBS server is at abc.def.com and APEX is at ghi.jkl.com, this method will not work.  Both servers must be on the .def.com or both on the .jkl.com domain. However, that doesn't mean all is lost.

We didn't alter any of the original GWY.jsp code. Instead we added a piece to capture the value of the current EBS session cookie and rewrite it to another cookie that APEX will expect. By using the GWY.jsp, in the case where someone has bookmarked the urls within EBS to launch APEX, we can lean on the session checking already included. Anyone without a valid session will be sent to the Oracle login page.

Where does LaunchApex.jsp go on the server?

$OA_HTML should point to the running file system.
For example: 
<some path>/fs1/FMW_Home/Oracle_EBS-app1/applications/oacore/html

For production you would only place in the non-running or Patch file system, then compile and wait for a patch cycle to commit to the running file system.  But in our test environment we place in both file systems
For example:
<some path>/fs1/FMW_Home/Oracle_EBS-app1/applications/oacore/html
<some path>/fs2/FMW_Home/Oracle_EBS-app1/applications/oacore/html

How is LaunchApex.jsp used?

LaunchApex.jsp will be set in the function call within EBS and we will see this in another post.

But first we have to compile the jsp and restart Weblogic so the new jsps are ready for use.

For Example:
cd <some path>/fs1/FMW_Home/Oracle_EBS-app1/applications/oacore/html

$FND_TOP/patch/115/bin/ojspCompile.pl --compile -s 'ZEUS_LaunchApex.jsp' –flush

cd <some path>/fs2/FMW_Home/Oracle_EBS-app1/applications/oacore/html

$FND_TOP/patch/115/bin/ojspCompile.pl --compile -s 'ZEUS_LaunchApex.jsp' --flush

Restarting Weblogic Server
admanagedsrvctl.sh stop oacore_server1
admanagedsrvctl.sh start oacore_server1


The Cookie Variables explained:

        String ebsCookieName = "VIS"; - this is the name of the EBS cookie, there are several articles on the internet that show you how to locate this value either in your browser or in your database.

        String apexCookieName = "VISAPEX";   - this is the name of the cookie for which APEX will be looking in the browser. In the APEX application this is a substitution string setting (don't forget to set it)

        String apexDomain = ".YOURDOMAIN.com"; - the SHARED domain between apex and ebs. Not setting this could cause an issue if you have any custom code that uses a default domain that isn't correct.  Maybe a bad clone from another instance kept an older value or something to that effect.The leading "." isn't strictly required but some browsers may forget to add it, so explicitly setting is a good idea.

        String apexPath = "/"; - another parameter that isn't required but could be set to an undesirable value by your webserver due to any number of ways defaults/non set values are handled, so explicitly setting is a good idea.

        boolean isCookieSecure = false; - If both servers are using SSL, change this to true. Otherwise leave alone.


Full listing for our LaunchApex.jsp 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%--
/*===========================================================================+
 |      Copyright (c) 2009 Oracle Corporation, Redwood Shores, CA, USA       |
 |                         All rights reserved.                              |
 +===========================================================================+
 |  FILENAME                                                                 |
 |    GWY.jsp                                                                |
 |                                                                           |
 |  DESCRIPTION                                                              |
 |    GWY.jsp handles external application URL embedding within              |
 |    E-Business Suite. GWY expects to be invoked only from RF as            |
 |    standard function invocation.                                          |
 |                                                                           |
 |  DEPENDENCIES                                                             |
 |                                                                           |
 |  HISTORY                                                                  |
 |    01-AUG-2009   raghosh     created                                      |
 +===========================================================================*/
--%>
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Enumeration"%>
<%@ page import="java.util.Iterator"%>
<%@ page import="oracle.apps.fnd.common.VersionInfo"%>
<%@ page import="oracle.apps.fnd.services.gwy.ExternalAppManager"%>

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Oracle Applications External Gateway - Custom Launch Apex 20140801 - 5</title>
  </head>
  <body>
  
    <%! public static final String RCS_ID =  "$Header: GWY.jsp 120.3.12020000.1 2012/06/30 05:28:11 appldev ship $"; %>
    <%! public static final boolean RCS_ID_RECORDED =  VersionInfo.recordClassVersion(RCS_ID,"oa_html"); %>
    
    <%
        
        Enumeration<String> paramNames = request.getParameterNames();

        Map<String, String> params = new HashMap<String, String>();

        while(paramNames.hasMoreElements()) {
            String param = paramNames.nextElement();
            String paramVal = request.getParameter(param);
            if (!(paramVal == null || "".equals(paramVal)))
                paramVal = paramVal.trim();
            params.put(param, paramVal);
        }

        //boolean debugMode = "Y".equalsIgnoreCase(params.get("debug")) ? true : false;
        //if (debugMode) {
        //      Iterator iter = params.entrySet().iterator();
        //      while (iter.hasNext()) {
        //              Map.Entry aPair = (Map.Entry) iter.next();
        //              out.println(String.valueOf(aPair.getKey()) + "=" + String.valueOf(aPair.getValue()) + "<br>");
        //      }
        //}

        String targetType = params.get(ExternalAppManager.EXTERNAL_APP_TYPE_PARAM);
        if (targetType == null || "".equals(targetType))
            targetType = (String) request.getAttribute(ExternalAppManager.EXTERNAL_APP_TYPE_PARAM);
            
        String handlerClass = params.get(ExternalAppManager.EXTERNAL_APP_HANDLER_PARAM);
        if (handlerClass == null || "".equals(handlerClass))
            handlerClass = (String) request.getAttribute(ExternalAppManager.EXTERNAL_APP_HANDLER_PARAM);
        //String authFunction = params.get(ExternalAppManager.EXTERNAL_APP_AUTH_FUNCTION);
        ExternalAppManager manager = new ExternalAppManager(request, response, targetType, handlerClass);
        manager.logParams(params);
        

        /** COOKIE REWRITE BEGIN **/

        // COOKIE VARIABLES 
        String ebsCookieName = "VIS";
        String apexCookieName = "VISAPEX";   
        String apexDomain = ".YOURDOMAIN.com";
        String apexPath = "/";
        boolean isCookieSecure = false;

        // RETRIEVE EBS COOKIE
        Cookie[] cookies = request.getCookies();
        Cookie ebsCookie = null;
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                if (cookies [i].getName().equals (ebsCookieName)) {
                    ebsCookie = cookies[i];
                    break;
                }
            }
        }
        
        // CREATE APEX COOKIE USING EBS COOKIE VALUE        
        Cookie apexCookie = new Cookie(apexCookieName, ebsCookie.getValue());
        apexCookie.setDomain(apexDomain);
        apexCookie.setPath(apexPath);
        apexCookie.setSecure(isCookieSecure);
        
        // ADD COOKIE INTO RESPONSE
        response.addCookie(apexCookie);    
        
        /** COOKIE REWRITE END **/
        
        manager.doForward(params, false);
        manager.releaseResources();

    %>
  </body>
</html>


The following steps were done while logged into EBS with the System Administrator responsibility.

note: The System Administrator menus are used as references.  The same techniques can be used to create menu entries under other responsibilities as well.

Set APEX Function path

System Administrator -> Profile -> System
In the “Find System Profile Values” -> Profile field enter FND: APEX URL and press the Find button


Change the Site value to your appropriate path and save.
For example: http://yourserver.yourdomain.com:8080/ords

Create Form Function

Application -> Function
Description Tab:
  • Function Name
  • User Function Name
  • Description

Properties Tab - Type: SSWA jsp function

Web HTML Tab - HTML Call: LaunchApex.jsp?targetAppType=APEX&p=900:1
(using application id 900 and page 1, make sure the application ID matches what you are using for your APEX app. )
Notice we are not sending anything else in the call.  Therefore, no other information will be sent in the calling URL!


Adding Menu Prompt

Application -> Menu
Create a new entry(see bottom entry in image below):
  • Prompt - whatever you wish the user to see
  • Submenu - depends on your setup
  • Function - the function we created above
  • Description - optional

Notes:

  • You can set up multiple Form Functions. We use one for each application. For example, HR and Sales each have different Form Functions and menu entries.  
  • On the APEX side we have completely separate applications for each group.  Therefore we can completely separate responsibilities and groups that are affected with changes in any applications.

Before setting up your APEX application, you will need to know 3 things about your EBS to APEX env

  1. The name of the cookie for APEX in the custom LaunchApex.jsp (apexCookieName) from Part 2
  2. The Responsibility IDs of the responsibilities that are allowed to access this application
  3. The URL of the EBS application Navigator Page (after login). 
For our example:
  1. VISAPEX
  2. 20420 (System Administrator)
  3. http://YOURSERVER.YOURDOMAIN.com/OA_HTML/OA.jsp?OAFunc=OAHOMEPAGE

Quick query to get a list of responsibility ids and names:

select 
  responsibility_id
  ,responsibility_name 
from
  fnd_responsibility_tl
order by 
  responsibility_id
;

Create Application

Create your application with whatever basic steps you take.

Create Application Items for Global Usage

Shared Components -> Application Items

  • G_FND_GLOBAL_USER_ID
  • G_FND_GLOBAL_RESP_APPL_ID
  • G_FND_GLOBAL_RESP_ID
  • G_FND_GLOBAL_SEC_GROUP_ID



Create Custom Authentication Scheme


For the Authentication Scheme, we don't need any specific code.  Just setting the authentication scheme, we force all users to Page 101 (Login) where we will handle all the true authentication steps. For the Post-Logout URL we are going to add CLOSE to the request to trigger a redirect back to EBS.
Shared Components -> Authentication Schemes
  • Name: EBS2APEX
  • Scheme Type: Custom
  • Post-Logout URL: f?p=&APP_ID.:101::CLOSE





Create Custom Authorization Scheme
"BASIC USER"


For the Authorization Scheme we are going to do something very simple.  No need to over-complicate this piece. We are going to look in the Substitution String we will create in the next section and see if the responsibility ids listed in the ALLOWED_RESPONSIBILITIES contain the responsibility id associated with the EBS session identified by the session cookie.

Shared Components -> Authorization Schemes
  • Name: Basic User
  • Scheme Type:: PL/SQL Function Returning Boolean
  • PL/SQL Function Body:
    RETURN instr(:ALLOWED_RESPONSIBILITIES,:G_FND_GLOBAL_RESP_ID) > 0;
  • Identify error message… : You are not authorized to view this page.




Create Substitution Strings 

These three substitution stings will contain the information required at the top of this post.

Application # ->Edit Application Properties
  • COOKIE_NAME - VISAPEX
  • ALLOWED_RESPONSIBILITIES - 20420
  • EBS_LINK - http://YOURSERVER.YOURDOMAIN.com/OA_HTML/OA.jsp?OAFunc=OAHOMEPAGE

note: for multiple allowed responsibility ids use a comma sorted list. For example 20420, 20421, 33333, etc. In future releases of our apps we will use an intermediate table to add/remove responsibilities to each separate application.




Enable Authorization Scheme 

“BASIC USER” 

Application # -> Edit Application Properties -> Security Tab

Set Authorization Schema to Basic User


Edit Page 101 (Login Page)

Create Before Header Process

CreateSessionFromCookie 

In this section we create the main authentication and login process. We will grab the value of the VISAPEX cookie and set the session state from the associated icx_session values. 

Security wise, exceptions happen with any of the following:
  • No Cookie
  • Cookie has an invalid session id
  • No Responsibility Id associated with the session id
Now we know this isn't best practice, but we used the "WHEN OTHERS" handling.  From a security standpoint we do not want the user to see any part of the error they are causing at login.  Yes, this can be a pain to troubleshoot, but the function below has very few parts and a specific set of conditions that have to be met for success.  Basically: fewer moving parts, fewer thing to troubleshoot. Keeping it simple! 

Create a Before Header Process

  • Name - CreateSessionFromCookie
  • Source - 
DECLARE
    l_cookie      OWA_COOKIE.COOKIE;    l_sessionID   varchar2(100);    l_session_id  NUMBER;    l_userID      NUMBER;    l_username    VARCHAR2(512);
    
l_respID      NUMBER;    l_respapplID  NUMBER;    l_secgroupID  NUMBER;

        l_cookieName varchar2(512) := :COOKIE_NAME;

    BEGIN
            l_cookie := owa_cookie.get(l_cookieName);    l_sessionID := l_cookie.vals(1);     
              --is EBS session valid, will throw exception if not valid    app_session.validate_icx_session(l_sessionID);
                  --has a valid EBS session, but has user chose a responsibility in EBS     select responsibility_id into l_respID from icx_sessions where XSID =  l_sessionID;   
                    IF l_respID <> -1
                      THEN
                           --user has a good session and has a responsibility id assigned in session       --set global variables and login to APEX
                           select user_id into l_userID from icx_sessions where XSID =  l_sessionID;       select user_name into l_username from fnd_user where user_id = l_userID;       select responsibility_application_id into l_respapplID from icx_sessions where XSID =  l_sessionID;       select security_group_id into l_secgroupID from icx_sessions where XSID =  l_sessionID;
                             apex_util.set_session_state('G_FND_GLOBAL_USER_ID',l_userID);       apex_util.set_session_state('G_FND_GLOBAL_RESP_ID',l_respID);        apex_util.set_session_state('G_FND_GLOBAL_RESP_APPL_ID',l_respapplID);           apex_util.set_session_state('G_FND_GLOBAL_SEC_GROUP_ID',l_secgroupID);              apex_custom_auth.post_login(
                                      p_uname         => l_username,              p_session_id    => nv('APP_SESSION'),               p_app_page      => apex_application.g_flow_id || ':1',               p_preserve_case => FALSE
                                 );
                                END IF;
                              EXCEPTION
                                   -- no cookie, session id, or chosen responsibility    WHEN OTHERS THEN NULL;
                                  END;
                                  • Condition Type - Request != Expression 1
                                  • Expression 1 - CLOSE




                                  Create Before Header Process

                                  RedirectBackToEBS

                                  In this process we will create the action when someone clicks the logout link. In our case we want the user redirected back to EBS using link in the Substitution Strings.

                                  Create a Before Header Process


                                  • Name - RedirectBackToEBS
                                  • Source - owa_util.redirect_url(:EBS_LINK, true);
                                  • Condition Type - Request = Expression 1
                                  • Expression 1 - CLOSE




                                  Create a new Login Button

                                  This section is partly optional. We set the default P101_USERNAME, P101_PASSWORD fields and the P101_LOGIN button Condition Type to "Never".  We change the username and password fields to never because we do not allow direct logins on the APEX login page. We don't really have a good reason to "Never" the login button. We could just change the action of login button to the actions we use when we create the new button, but we like a separate button for this. It's just how we roll!

                                  Change Condition Type to Never:

                                  • P101_USERNAME
                                  • P101_PASSWORD
                                  • P101_LOGIN




                                  Create Page Item Button in Login Region:

                                  • Name - P101_LOGIN_TO_ORACLE
                                  • Text Label/Alt - Login to Oracle
                                  • Action - Redirect to URL
                                  • URL Target - &EBS_LINK.




                                  Conclusion

                                  Now you should be able to login to EBS with System Administrator responsibility and click on your Auth_Test link to enter APEX. We have tested several different methods to make sure a user can not enter without logging into EBS and have found only a few that work. But they all involve APEX retaining its current session cookie.  So this is exactly as expected. 



                                  No comments:

                                  Post a Comment

                                  Number of Visitors