Friday, March 3, 2017

A Work-Around for Security in ADF Essentials 12.2.1

In my previous post, I talked about ADF Essentials and configuring it for authentication and authorization.  And I reported a problem with the new 12.2.1 versions that raises an exception trying to initialize a UserProfile object.  I surmise that Oracle's newer versions call a facility that is only in Oracle WebLogic server, and NOT in the typical application server running ADF Essentials, notably Glassfish.  But, until Oracle fixes this (PLEASE Oracle) I have tested a workaround.  This was suggested by a comment on this post by Andrejus Baranovski (and if you are using ADF, you NEED to read Andrejus' blog - it is GREAT!)  http://andrejusb.blogspot.com/2012/10/adf-essentials-security-implementation.html.

The comment is by Ivan Melluso, who said:
I'm using JDeveloper 12.2.1, Glassfish 4.1.1, ADF Essentials 12.2.1;
I obtained
NoClassDefFoundError: Could not initialize class oracle.adf.share.security.identitymanagement.UserProfile
Then I discover the UserProfile class initialize a MDS session, that can't exists in GlassFish;

My solution:
- Decompile the class
- Rewrite it preserving package name and all public methods
- Create a new jar
- Place it in /lib directory

I restart the server 5 times to check the class conflict (same class exists in adf-share-ca.jar and my jar) and all 5 times I don't obtain the error
So the work-around is to write our own version of UserProfile and get the application to call ours instead of Oracle's original.  Here is my version:

package oracle.adf.share.security.identitymanagement;

import java.io.Serializable;

import java.security.Principal;

import java.util.HashMap;

import javax.faces.context.FacesContext;

public class UserProfile implements Serializable {
    @SuppressWarnings("compatibility:-6264967054558894560")
    private static final long serialVersionUID = 1L;

    public UserProfile() {
        super();
    }

    java.util.HashMap properties = new HashMap();


    public Object getProperty(String propName) {
        return properties.get(propName);
    }

    public void setProperty(String name, Object value) {
        properties.put(name, value);
    }

    public void setBusinessCity(String businessCity) {
        setProperty("businessCity", businessCity);
    }

    public String getBusinessCity() {
        return (String)getProperty("businessCity");
    }

    public void setBusinessCountry(String businessCountry) {
        setProperty("businessCountry", businessCountry);
    }

    public String getBusinessCountry() {
        return (String)getProperty("businessCountry");
    }

    public void setBusinessEmail(String businessEmail) {
        setProperty("businessEmail", businessEmail);
    }

    public String getBusinessEmail() {
        return (String)getProperty("businessEmail");
    }

    public void setBusinessFax(String businessFax) {
        setProperty("businessFax", businessFax);
    }

    public String getBusinessFax() {
        return (String)getProperty("businessFax");
    }

    public void setBusinessMobile(String businessMobile) {
        setProperty("businessMobile", businessMobile);
    }

    public String getBusinessMobile() {
        return (String)getProperty("businessMobile");
    }

    public void setBusinessPager(String businessPager) {
        setProperty("businessPager", businessPager);
    }

    public String getBusinessPager() {
        return (String)getProperty("businessPager");
    }

    public void setBusinessPhone(String businessPhone) {
        setProperty("businessPhone", businessPhone);
    }

    public String getBusinessPhone() {
        return (String)getProperty("businessPhone");
    }

    public void setBusinessPOBox(String businessPOBox) {
        setProperty("businessPOBox", businessPOBox);
    }

    public String getBusinessPOBox() {
        return (String)getProperty("businessPOBox");
    }

    public void setBusinessPostalAddr(String businessPostalAddr) {
        setProperty("businessPostalAddr", businessPostalAddr);
    }

    public String getBusinessPostalAddr() {
        return (String)getProperty("businessPostalAddr");
    }

    public void setBusinessPostalCode(String businessPostalCode) {
        setProperty("businessPostalCode", businessPostalCode);
    }

    public String getBusinessPostalCode() {
        return (String)getProperty("businessPostalCode");
    }

    public void setBusinessState(String businessState) {
        setProperty("businessState", businessState);
    }

    public String getBusinessState() {
        return (String)getProperty("businessState");
    }

    public void setBusinessStreet(String businessStreet) {
        setProperty("businessStreet", businessStreet);
    }

    public String getBusinessStreet() {
        return (String)getProperty("businessStreet");
    }

    public void setDateofBirth(String dateofBirth) {
        setProperty("dateofBirth", dateofBirth);
    }

    public String getDateofBirth() {
        return (String)getProperty("dateofBirth");
    }

    public void setDateofHire(String dateofHire) {
        setProperty("dateofHire", dateofHire);
    }

    public String getDateofHire() {
        return (String)getProperty("dateofHire");
    }

    public void setDefaultGroup(String defaultGroup) {
        setProperty("defaultGroup", defaultGroup);
    }

    public String getDefaultGroup() {
        return (String)getProperty("defaultGroup");
    }

    public void setDepartment(String department) {
        setProperty("department", department);
    }

    public String getDepartment() {
        return (String)getProperty("department");
    }

    public void setDepartmentNumber(String departmentNumber) {
        setProperty("departmentNumber", departmentNumber);
    }

    public String getDepartmentNumber() {
        return (String)getProperty("departmentNumber");
    }

    public void setDescription(String description) {
        setProperty("description", description);
    }

    public String getDescription() {
        return (String)getProperty("description");
    }

    public void setDisplayName(String displayName) {
        setProperty("displayName", displayName);
    }

    public String getDisplayName() {
        return (String)getProperty("displayName");
    }

    public void setEmployeeNumber(String employeeNumber) {
        setProperty("employeeNumber", employeeNumber);
    }

    public String getEmployeeNumber() {
        return (String)getProperty("employeeNumber");
    }

    public void setEmployeeType(String employeeType) {
        setProperty("employeeType", employeeType);
    }

    public String getEmployeeType() {
        return (String)getProperty("employeeType");
    }

    public void setFirstName(String firstName) {
        setProperty("firstName", firstName);
    }

    public String getFirstName() {
        return (String)getProperty("firstName");
    }

    public void setGivenName(String givenName) {
        setProperty("givenName", givenName);
    }

    public String getGivenName() {
        return (String)getProperty("givenName");
    }

    public void setGUID(String GUID) {
        setProperty("GUID", GUID);
    }

    public String getGUID() {
        return (String)getProperty("GUID");
    }

    public void setHomeAddress(String homeAddress) {
        setProperty("homeAddress", homeAddress);
    }

    public String getHomeAddress() {
        return (String)getProperty("homeAddress");
    }

    public void setHomePhone(String homePhone) {
        setProperty("homePhone", homePhone);
    }

    public String getHomePhone() {
        return (String)getProperty("homePhone");
    }

    public void setInitials(String initials) {
        setProperty("initials", initials);
    }

    public String getInitials() {
        return (String)getProperty("initials");
    }

    public void setJpegPhoto(byte[] jpegPhoto) {
        setProperty("jpegPhoto", jpegPhoto);
    }

    public byte[] getJpegPhoto() {
        return (byte[])getProperty("jpegPhoto");
    }

    public void setLastName(String lastName) {
        setProperty("lastName", lastName);
    }

    public String getLastName() {
        return (String)getProperty("lastName");
    }

    public void setMaidenName(String maidenName) {
        setProperty("maidenName", maidenName);
    }

    public String getMaidenName() {
        return (String)getProperty("maidenName");
    }

    public void setManager(String manager) {
        setProperty("manager", manager);
    }

    public String getManager() {
        return (String)getProperty("manager");
    }

    public void setMiddleName(String middleName) {
        setProperty("middleName", middleName);
    }

    public String getMiddleName() {
        return (String)getProperty("middleName");
    }

    public void setName(String name) {
        setProperty("name", name);
    }

    public String getName() {
        return (String)getProperty("name");
    }

    public void setNameSuffix(String nameSuffix) {
        setProperty("nameSuffix", nameSuffix);
    }

    public String getNameSuffix() {
        return (String)getProperty("nameSuffix");
    }

    public void setOrganization(String organization) {
        setProperty("organization", organization);
    }

    public String getOrganization() {
        return (String)getProperty("organization");
    }

    public void setOrganizationalUnit(String organizationalUnit) {
        setProperty("organizationalUnit", organizationalUnit);
    }

    public String getOrganizationalUnit() {
        return (String)getProperty("organizationalUnit");
    }

    public void setPreferredLanguage(String preferredLanguage) {
        setProperty("preferredLanguage", preferredLanguage);
    }

    public String getPreferredLanguage() {
        return (String)getProperty("preferredLanguage");
    }

    public Principal getPrincipal() {
        Principal principal =
            FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
        return principal;
    }

    public void setProperties(HashMap properties) {
        setProperty("properties", properties);
    }

    public HashMap getProperties() {
        return (HashMap)getProperty("properties");
    }

    public void setTimeZone(String timeZone) {
        setProperty("timeZone", timeZone);
    }

    public String getTimeZone() {
        return (String)getProperty("timeZone");
    }

    public void setTitle(String title) {
        setProperty("title", title);
    }

    public String getTitle() {
        return (String)getProperty("title");
    }

    public void setUIAccessMode(String UIAccessMode) {
        setProperty("UIAccessMode", UIAccessMode);
    }

    public String getUIAccessMode() {
        return (String)getProperty("UIAccessMode");
    }

    public void setUniqueName(String uniqueName) {
        setProperty("uniqueName", uniqueName);
    }

    public String getUniqueName() {
        return (String)getProperty("uniqueName");
    }

    public void setUserID(String userID) {
        setProperty("userID", userID);
    }

    public String getUserID() {
        return (String)getProperty("userID");
    }

    public void setUserName(String userName) {
        setProperty("userName", userName);
    }

    public String getUserName() {
        return (String)getProperty("userName");
    }

    public void setWirelessAcctNumber(String wirelessAcctNumber) {
        setProperty("wirelessAcctNumber", wirelessAcctNumber);
    }

    public String getWirelessAcctNumber() {
        return (String)getProperty("wirelessAcctNumber");
    }
   
    public void saveProfile () {
        // do nothing
    }

}

Now, how to package it, and where to put it.  We need to make sure that ours is used instead of Oracle's.  We could do what Ivan did, and package it in our own jar and add the jar to the lib folder.  Well, I'm not confident that it will always use mine instead of Oracle's - depends on what the class loader does, and I'm not really that experienced with how java and Glassfish really work.  So I got out my trusty 7-Zip and replaced UserProfile in Oracle's jar, adf-share-ca.jar with my version of the class.  This works, no more Exception.

The real question - is this legal? If I started using this in production, could Oracle come after me.  Honestly, I doubt that they would bother, but could they?  On the other hand, if Oracle would fix this, I wouldn't do it.  After all, to deploy my application, I have to deploy MY version of adf-share-ca.jar, and I have to make a new version of the jar anytime Oracle's changes.

21 comments:

  1. Thanks man, this post can save so many guys of being fired! I implemented another solution but this is great as well.

    ReplyDelete
    Replies
    1. Daniel, It will be interesting if u share your solution with us! thx!

      Delete
    2. Hi Daniel !

      I am currently stuck with the same problem now. I would appreciate if you can share your solution.

      Delete
  2. hi John, thanks for your post.. this was really helpful.. i have tried your solution but when i tried to login i encounter this error.

    ADFc: No exception handler was found for an application exception.
    java.lang.NoSuchMethodError: oracle.adf.share.security.identitymanagement.UserProfile.(Ljava/lang/String;)V
    at oracle.adf.share.security.SecurityContextImpl.getUserProfile(SecurityContextImpl.java:147)
    at oracle.adfinternal.share.util.AdfShareInternalUtils.getCurrentUserProfile(AdfShareInternalUtils.java:94)
    at oracle.adfinternal.share.util.AdfShareInternalUtils.getSecurityContextUserIdentifier(AdfShareInternalUtils.java:47)
    at oracle.adf.share.config.ADFContextMDSConfigHelperImpl.createBaseSessionOptions(ADFContextMDSConfigHelperImpl.java:148)
    at oracle.adf.share.config.ADFContextMDSConfigHelperImpl.createSessionOptions(ADFContextMDSConfigHelperImpl.java:168)
    at oracle.adf.share.config.ADFContextMDSConfigHelperImpl.createMDSSession(ADFContextMDSConfigHelperImpl.java:69)
    at oracle.adf.share.ADFContext.getMDSSessionAsObject(ADFContext.java:2356)


    i use glassfish 4.1 + adf essentials 12.2.1.2 + apache shiro.
    i think i missed some method in the UserProfile Class.

    ReplyDelete
    Replies
    1. My version was based on an earlier version of UserProfile than 12.2.1.2, so maybe the later version has a new method that I don't implement. You may have to decompile the class in THAT version and compare it with my version.
      You say you are using Apache Shiro. I've tried this with plain JEE security (configured in web.xml) and with Spring Security, but I've never used Shiro - so I wonder if this is part of the problem. If you figure this out, I hope you'll share what you learn.

      Delete
    2. add below code.
      public UserProfile(String userName){
      super();
      }

      Delete
    3. Thank you very much FYAZ AHMAD, cumibulat and John Flack. It work for us on GlassFish 4.1.2 + ADF Essentials 12.2.1.3 + Apache Shiro.

      Delete
    4. Hi ! Please can you share your patch ? that the source code for the modified UserProfile.java class. Alternatively share your adf-share-ca.jar file. I am using the same adf version as use on payara 4.1. Would really appreciate your help. This issue is still open at my end.

      Delete
    5. For clarity sake. I am using ADF Essentials 12.2.1.3. I used the adf-share-ca.jar file for that version. I followed the approach shared by John and decompiled it and applied the fix. I would appreciate it if you can share your own jar file. Thanks !

      Delete
    6. It's working now.

      I used your code John then added FYAZ patch to it.

      I also did one more thing.

      I noticed that adf_share_ca.jar is always added to the war file. This jar is also in the glassfish lib folder. (where the other adf jars are). So I traced where this jar is located on my pc and patched it there.
      So that during war deployment the patched version is added to the war file each time a war deployment is done. (This was the issue all the while)

      I then replaced the one in the glassfolder lib.

      John and Fyaz thanks !!

      Delete
  3. Hi John !

    I am currently stuck with the same problem now. I have encountered the same issue. I am using ADF 12.2.1.3.0. Please can you share the patched jar file you used ?

    ReplyDelete
    Replies
    1. So, I'm currently on shaky ground, since I'm re-writing ORACLE's jar. I did publish my version of the class in question above, and you are welcome to cut and paste and compile it yourself. Then you need to either get it into your classpath ahead of Oracle's version, or replace Oracle's with this one in adf-share-ca.jar. Use a zipping tool like WinZip or 7Zip for that. If you are using a different version of ART 12c than I am, you may need to make changes to my code to make it work. Or you may want to wait for Oracle to release ADF 19 which I understand will be coming out soon. Maybe they have fixed it.

      Delete
    2. By the way - I typed this answer on my Kindle Fire - darn spell checker changed my ADF to ART. :)

      Delete
  4. I was hoping you could share your own patched adf-share-ca.jar. But going through your post again. I can see that you used a lower version than me.

    If I am to proceed with creating my own jar or modifying the exist adf-share-ca.jar of my adf version. My first question will be How do I set up a project to compile this UserProfile class ? Do I create a standard java se project in jdev ? What java ee jar dependencies do I need to add so that I can compile my own UserProfile class.

    I can see that you used packages such as javax.faces.context.FacesContext, java.security.Principal in the import section of the code.

    ReplyDelete
    Replies
    1. It has been a while, but I think I just used JDeveloper and included it in an ADF project. That would have the referenced classes.

      Delete
  5. I have just tried your approach. I decompiled the UserProfile class in my adf 12.2.1.3.0. Adjusted it, compiled it and used 7 zip to replace the existing UserProfile class. I am getting the same error.

    java.lang.NoClassDefFoundError: Could not initialize class oracle.adf.share.security.identitymanagement.UserProfile

    I am now getting the impression that the class is not even being invoked at all. Could be a class loader problem.

    ReplyDelete
    Replies
    1. Have a look at what FYAZ AHMED says he did in his comment above. He says that he made a change in my code that got it to work. Or Oracle says a new version is around the corner - hope they have fixed it. But I am no longer actively working with ADF.

      Delete
  6. Thanks John, it works for adf essentials 12.2.1.2.0 and tomcat 7.
    But I did some modifications:

    1.added

    public UserProfile(String userName){
    super();
    }

    2. and commented out following
    public Principal getPrincipal() {
    Principal principal = null;//FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
    return principal;
    }

    Installation:
    1. Remove original UserProfile.class from adf-share-ca.jar
    2. Add new jar with modified UserProfile

    ReplyDelete
  7. Thanks for this workaround - worked for me in 12.2.1.4 on Glassfish 5.1.0

    ReplyDelete
  8. Looks like the same issue still exists in 12.2.1.4. I tried this approach, but now am getting a null pointer.

    ReplyDelete
    Replies
    1. I have tested 12.2.1.4, it worked well with authcBasic. I don't commeented out
      public Principal getPrincipal() {
      Principal principal = null; //FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
      return principal;
      }

      Delete