Sunday, 15 January, 2012

Fun with OAM 11g SDK

OAM 11g was a step backward from 10g, but slowly - as of .5 - it's becoming usable again. Bringing back custom authentication plugins and schemes was vital, and then there's the topic of this post, the indispensable; AccessServerSDK.

I am still not pleased - Certain things are still missing, like the "originally requested URL" - making it impossible to do something like, allow a user to self-register and return to their original destination. I read that Oracle are working on the request, but who knows how long it will take with all the legitimate bugs out there.

OFM OAM SDK 11g


The 11g SDK is a simple Java API. Download and extract "ofm_oam_sdk_generic_11.1.1.5.0_disk1_1of1.zip" - no installer necessary. Sort of.

To get started, create a 10g gate in the OAM console, and copy the generated ObAccessClient.xml to your "AccessServerSDK" home folder within the subfolders: oblix\lib. For example, on my workstation, I created "C:\Oracle\asdk\oblix\lib".

From that point, you can copy the AccessServerSDK directory ("C:\Oracle\asdk") to setup multiple clients on developer or test machines, no worries, as long as the path is the same for everyone.

Add your oam-sdk jar to the project classpath - that's it. See Oracle API for a basic example or keep reading.

The fun part is using the SDK to authenticate a user for a valid SSO session they can take with them. I will get to that in a second, let's show the basic concept first.

Basic OAM SDK client class


Let's start with logging in with a basic OAM SDK client. To run this test, update: configLocation, login, password, resource for your environment.

You can use any http resource already protected in OAM, or create a new one with any FORM type Authentication Scheme.

The resource format is : "//HostnameFromHostIdentifier:80/ResourcePath"

In my environment, I need the code to work on Windows or Unix, so there are two configLocation values - use is determined by the java System Property for OS.

import java.util.Hashtable;

import oracle.security.am.asdk.AccessClient;
import oracle.security.am.asdk.AccessException;
import oracle.security.am.asdk.AuthenticationScheme;
import oracle.security.am.asdk.ResourceRequest;
import oracle.security.am.asdk.UserSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * To generate the Access SDK log, you must provide a logging configuration file
 * when you start the application. e.g.
* java -Djava.util.logging.config.file=JRE_DIRECTORY/lib/logging.properties
 */
public class Simple10gClient {
  protected static final Logger log = LoggerFactory.getLogger(Simple10gClient.class);

  // assume all resources are http GETs
  public static final String res_type = "http";
  public static final String ms_method = "GET";

  // Using standard location for SDK; will choose based on OS.
  public static String configLocation = "/Oracle/Middleware/asdk";
  public static String winConfigLocation = "C:/Oracle/asdk";
  private static AccessClient ac;

  private Simple10gClient() {}

  // simple test 
  public static void main(String argv[]) {
    String anonResource = "//AccessServerSDK_HostID:443/ResourceProtectedWithNoPasswordScheme";
    String login = "testuser1";
    String password = null;

    try {
      String sId = Simple10gClient.authenticate(anonResource, login, password);

      Simple10gClient.isLoggedIn(sId);

      Simple10gClient.shutdown();
      
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @SuppressWarnings("rawtypes")
  private static void setupAccessClient() {
    try {
      String os = System.getProperty("os.name").toLowerCase();
      if (os.indexOf("win") > -1) {
        configLocation = winConfigLocation;
      }
      // "C:/Oracle/asdk/oblix/lib/ObAccessClient.xml" must exist/be valid:
      log.info("getAccessClient() config:" + configLocation );
      ac = AccessClient.createDefaultInstance(configLocation , AccessClient.CompatibilityMode.OAM_10G);
      log.info("Nap:" + AccessClient.getNAPVersion() + " SDK:" + AccessClient.getSDKVersion());
      Hashtable diagnostic = ac.getServerDiagnosticInfo();
      for (Object key : diagnostic.keySet()) {
        log.info("Server Diagnostic " + key + "==" + diagnostic.get(key));
      }
      diagnostic = ac.getClientDiagnosticInfo();
      for (Object key : diagnostic.keySet()) {
        log.info("Client Diagnostic " + key + "==" + diagnostic.get(key));
      }

    } catch (AccessException ae) {
      if (ae.getMessage().indexOf("OAMAGENT-02055") > -1) {
        log.error("AccessServerSDK - is this agent webgate configured? " + ae);
      } else {
        log.error("Access Exception: " + ae.getMessage(), ae);
      }
    }
  }

  public static String authenticate(String resource, String login, String pw) {
    log.debug("authenticate() user=" + login + " res:" + res_type + " " + resource);
    try {
      if (ac == null || !ac.isInitialized()) {
        setupAccessClient();
      }
      ResourceRequest rrq = new ResourceRequest(res_type, resource, ms_method);
      if (rrq.isProtected()) {
        log.info("Resource is protected: " + resource);
        AuthenticationScheme authnScheme = new AuthenticationScheme(rrq);
        if (authnScheme.isForm()) {
          log.info("Form Authentication Scheme: " + authnScheme.getName());
          Hashtable creds = new Hashtable();
          // to know credential key names, either you write the AuthN scheme yourself
          // or go with the Oracle example.
          creds.put("userid", login);
          if (pw != null && pw.length() > 0)
            creds.put("password", pw);
          UserSession session = new UserSession(rrq, creds);
          if (session.getStatus() == UserSession.LOGGEDIN) {
            if (session.isAuthorized(rrq)) {
              log.info("User is logged in and authorized, level " + session.getLevel());
            } else {
              log.info("User is logged in but NOT authorized");
            }
            log.info("User sessionToken:" + session.getSessionToken());
            return session.getSessionToken();
          } else {
            log.warn("User is NOT logged in");
            return null;
          }
        } else {
          log.warn("non-Form Authentication Scheme.");
        }
      } else {
        log.warn("Resource is NOT protected.");
      }
    } catch (AccessException ae) {
      log.error("authenticate() Access Exception: " + ae.getMessage());
    }
    return null;
  }

  public static void logout(String sessionId) {
    try {
      if (ac == null || !ac.isInitialized()) {
        setupAccessClient();
      }
      oracle.security.am.asdk.UserSession session = new UserSession(ac, sessionId);
      log.info("is logout required? " + session);
      if (session.getStatus() == 1)
        session.logoff();
    } catch (Exception ae) {
      log.error("logout() Access Exception: " + ae.getMessage(), ae);
    }
  }

  /**
   * @param sessionId
   */
  public static boolean isLoggedIn(String sessionId) {
    // 0 for AWAITINGLOGIN
    // 1 for LOGGEDIN
    // 2 for LOGGEDOUT
    // 3 for LOGINFAILED
    // 4 for EXPIRED
    try {
      if (ac == null || !ac.isInitialized()) {
        setupAccessClient();
      }
      oracle.security.am.asdk.UserSession session = new UserSession(ac, sessionId);
      log.info("isLoggedIn  startTime:" + session.getStartTime() + " status:" + session.getStatus());
      java.util.Date since = new java.util.Date(session.getStartTime());
      log.debug("since? " + since);
      return session.getStatus() == 1;
    } catch (Exception ae) {
      log.error("isLoggedIn() Access Exception: " + ae.getMessage(), ae);
    }
    return false;
  }

  public static void shutdown() {
    try {
      if (ac != null)
        ac.shutdown();
    } catch (Exception e) {
      log.warn("AccessServerSDK shutdown error " + e);
    }
  }
}

Right click and Run in eclipse, and you should see something like:

- authenticate() user=testuser1 resource:http //AccessServerSDK_HostID:443/ResourceProtectedWithNoPasswordScheme
- getAccessClient() config:C:/Oracle/asdk
- Nap:3 SDK:OAMAccessSDK_Ver_11.1.1.5
- Server Diagnostic oamserverhostname5575=={}
- Client Diagnostic oamserverhostname5575=={host=oamserverhostname, port=5575, priority=1, createtime=1326647958849}
- Resource is protected.
- Form Authentication Scheme.
- User is logged in and authorized for the request at level 3
- User sessionToken:LEHZ+zmSIZiamCTmHNQh9S+4JH0R8AvJ0eVwLPkUwmZArWb9qOg
KHYtEKrNTYHmEjt/UcqfFQ3Bq+mWud7APd0lqZlf31IigqrUAKJn7wRbRHanIpIQ1ygj64s
FjCoP6CwSTvSGeLY6gv49okJCM/2QivVqWb2Ce1Xwru89Vs/ZaE7YVXfjS8DnWwPBqYR8kQ
ONN348NI2cRMSMbryhG4neWah7rSFtJDH/gwAU55xwpE4SCcfTsXor8Qm7Ag==

Using the OAM SDK to authenticate a web client


My next step is wildly insecure, but a good shortcut for this example. Using the session ID generated with the SDK login, construct a valid SSO cookie and send the user on their merry authenticated way.

First, in the OAM console: Create a resource for your AccessServerSDK host, path "ResourceProtectedWithNoPasswordScheme" and add a "No Password Authentication Scheme" to it. I created my own AuthN scheme, setting the level to 3 - because I needed to match the authentication level of the forwarding online resource.

Create a LogMeIn servlet class taking a request parameter for login ID, and log the user in with a request parameter (see what I mean about being wildly insecure!):

String login = request.getParameter("login");
  String anonResource = "//AccessServerSDK_HostID:443/ResourceProtectedWithNoPasswordScheme";

      try {
        String sessId = Simple10gClient.authenticate(anonResource, login, null);
        log.info("Anon authenticate sessId:" + sessId);

        // using the Session ID, create a ObSSOCookie and give it to the user
        if (sessId != null) {
          Cookie obCookie = new Cookie("ObSSOCookie", sessId);
          obCookie.setDomain(".domain.com");
          // obCookie.setHttpOnly(true); -- not sure why Weblogic 10.3.5 wasn't able to find this method
          obCookie.setPath("/");
          response.addCookie(obCookie);

          // now send a response.redirect to a level 3 protected resource, user is logged in

        }
      } catch (Exception e) {
        response.getOutputStream().print("Exception " + e);
        log.error("logmein failed", e);
      }


Deloying on the OIM/OAM WLS Domain


There was a bit of a gotcha using this sdk jar on the same domain as OIM/OAM - class/jar conflicts. Easily solved by adding a weblogic.xml to the war WEB-INF, requesting that weblogic look at the web libraries only.

  <wls:container-descriptor>
    <wls:prefer-application-packages>
 <!-- add package names from the Oracle Access Server SDK -->
 <wls:package-name>com.oblix.*</wls:package-name>
 <wls:package-name>oracle.security.am.*</wls:package-name>
    </wls:prefer-application-packages>
  </wls:container-descriptor>

The end result of this is that you now can authenticate with standard username / password, check if the value of an ObSSOCookie is a valid session token, and generate for the end-user a valid SSO token to continue their with session on other OAM protected resources.



Friday, 30 December, 2011

Spring Security with SSO Headers - integrating with OAM WebGate

How to integrate java web applications with SSO? Usually a simple J2EE filter looking for the correct header name can suffice, however, if you want to take advantage of Spring Security you need to set it up with a "PreAuthentication" filter. This states that spring is not performing authentication, it's already done.

This solution may be overkill, but it provides more control/log info, and the ability to test outside of the SSO environment without implementing alternate authentication providers (although it would also be possible to have a form/database authentication fallback as well).

My environment: J2ee (any app server) fronted by a Oracle Access WebGate protected web server.

Spring Security 2.5+ (tested with 3.0.5)

What I need: Convert Request Headers OAM_REMOTE_USER and ROLES to map to an authenticated principal.

Making it usable: Allow a fallback security configuration for local testing.

Step 1. PreAuthentication Config


There are two custom beans in this file "HeaderAuthenticationFilter" and "HeaderAuthenticationDetails" and three configuration properties:

security.principal.header.name=OAM_REMOTE_USER
security.roles.header.name=ROLES
security.test.principal=no_header_in_test_mode

applicationContext-security.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
  default-lazy-init="true">

  <bean
    class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" />

  <!-- this bean name must match the filter name defined in security.xml below -->
  <bean id="ssoHeaderFilter"
    class="com.sharnibey.sample.security.HeaderAuthenticationFilter">
    <property name="principalRequestHeader" value="${security.principal.header.name}" />
    
    <!-- fall back to other authentication providers is OAM SSO is not there -->
    <property name="exceptionIfHeaderMissing" value="false" />
    <!-- hard code a testUserId for local tests -->
    <property name="testUserId" value="${security.test.principal}" />

    <property name="authenticationManager" ref="authenticationManager" />

    <property name="authenticationDetailsSource">
      <bean class="com.sharnibey.sample.security.HeaderAuthenticationDetails">
        <!-- look for the request header set by the webgate and map to local 
          roles -->
        <property name="roleHeaderName" value="${security.roles.header.name}" />
        <property name="userRoles2GrantedAuthoritiesMapper">
          <bean
            class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
            <property name="convertAttributeToUpperCase" value="true" />
          </bean>
        </property>
        <!-- setup a testing role if not deployed with a webgate - this only 
          applies if ENV_NAME != uat/prod -->
        <property name="testingRoles">
          <set>
            <value>USER</value>
          </set>
        </property>        
        <!-- all available roles for this application -->
        <property name="allRoles">
          <set>
            <value>USER</value>
            <value>ADMIN</value>
          </set>
        </property>
      </bean>
    </property>
  </bean>
  <bean id="preauthAuthProvider"
    class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
    <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService" />
  </bean>
  <!-- magically map the user header to a valid user object -->
  <bean id="preAuthenticatedUserDetailsService"
    class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService" />

  <bean id="securityContextHolderAwareRequestFilter"
    class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" />
</beans>



PreAuthentication Code



public class HeaderAuthenticationFilter 
    extends AbstractPreAuthenticatedProcessingFilter {
  protected final Logger log = LoggerFactory.getLogger(HeaderAuthenticationFilter.class);
  private String principalRequestHeader = "OAM_REMOTE_USER";
  /**
   * Configure a value in the applicationContext-security for local tests.
   */
  private String testUserId = null;
  /**
   * Configure whether a missing SSO header is an exception.
   */
  private boolean exceptionIfHeaderMissing = false;

  /**
   * Read and return header named by principalRequestHeader from Request
   * 
   * @throws PreAuthenticatedCredentialsNotFoundException
   *             if the header is missing and
   *             exceptionIfHeaderMissing is set to true.
   */
  protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
    String principal = request.getHeader(principalRequestHeader);

    if (principal == null) {
      if (exceptionIfHeaderMissing) {
        throw new PreAuthenticatedCredentialsNotFoundException(principalRequestHeader
            + " header not found in request.");
      } if (StringUtils.isNotBlank(testUserId)) {
          log.warn("spring configuration has a test user id " + testUserId);
          principal = testUserId;
      } else if (request.getSession().getAttribute("session_user") != null) {
// A bit of a hack for testers - allow the principal to be 
// obtained by session. Must be set by a page with no security filters enabled.
// should remove for production.
        principal = (String) request.getSession().getAttribute("session_user");
      }
    }
    // also set it into the session, sometimes that's easier for jsp/faces
    // to get at..
    request.getSession().setAttribute("session_user", principal);
    return principal;
  }

  /**
   * Credentials aren't applicable here for OAM WebGate SSO.
   */
  protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
    return "password_not_applicable";
  }

  public void setPrincipalRequestHeader(String principalRequestHeader) {
    Assert.hasText(principalRequestHeader, "principalRequestHeader must not be empty or null");
    this.principalRequestHeader = principalRequestHeader;
  }

  public void setTestUserId(String testId) {
    if (StringUtils.isNotBlank(testId)) {
      this.testUserId = testId;
    }
  }

  /**
   * Exception if the principal header is missing. Default false
   * @param exceptionIfHeaderMissing
   */
  public void setExceptionIfHeaderMissing(boolean exceptionIfHeaderMissing) {
    this.exceptionIfHeaderMissing = exceptionIfHeaderMissing;
  }

  public void setAuthenticationDetailsSource(AuthenticationDetailsSource source) {
    log.info("testing authenticationDetailsSource set " + source);
    super.setAuthenticationDetailsSource(source);
  }
}

public class HeaderAuthenticationDetails extends AuthenticationDetailsSourceImpl {
  protected final Logger log = LoggerFactory.getLogger(HeaderAuthenticationDetails.class);

  /**
   * Can be setup in applicationContext-security if the ROLES header value is
   * not found.
   */
  private Set testingRoles = new HashSet();

  /**
   * Security principal will only contain roles from "allRoles" - letting us
   * cut down the irrelevant values setup by the webgate SSO header.
   */
  protected Set allRoles = new HashSet();

  /**
   * setup in applicationContext-security
   */
  private String roleHeaderName = "ROLES";

  protected Attributes2GrantedAuthoritiesMapper grantedAuthoritiesMapper 
    = new SimpleAttributes2GrantedAuthoritiesMapper();

  public HeaderAuthenticationDetails() {
    super.setClazz(PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.class);
  }

  /**
   * Build the authentication details object. If the specified authentication
   * details class implements {@link MutableGrantedAuthoritiesContainer}, a
   * list of pre-authenticated Granted Authorities will be set based on the
   * roles for the current user.
   */
  public Object buildDetails(Object context) {
    Object result = super.buildDetails(context);
    List userGas = new ArrayList();
    if (result instanceof MutableGrantedAuthoritiesContainer) {
      Collection userRoles = getUserRoles(context, allRoles);
      userGas = grantedAuthoritiesMapper.getGrantedAuthorities(userRoles);
      ((MutableGrantedAuthoritiesContainer) result).setGrantedAuthorities(userGas);
    }
    return result;
  }

  /**
   * Allows the roles of the current user to be determined from the context
   * object
   * 
   * @param context
   *            the context object (HttpRequest, PortletRequest etc)
   * @param mappableRoles
   *            the possible roles determined by the
   *            MappableAttributesRetriever
   * @return Collection subset of mappable roles current user has.
   */
  protected Collection getUserRoles(Object context, Set mappableRoles) {
    ArrayList requestRoles = new ArrayList();
    if (((HttpServletRequest) context).getHeader(roleHeaderName) != null) {
      String[] roles = ((HttpServletRequest) context).getHeader(roleHeaderName).split(",");
      for (int i = 0; i < roles.length; i++) {
        if (mappableRoles.contains(roles[i])) {
          requestRoles.add(roles[i]);
        }
      }
    } else if ( testingRoles != null) {
      log.warn("Failed to retrieve Roles from Header, for debug purposes set to testingRole");
      requestRoles.addAll(testingRoles);
    } else {
      log.warn("Failed to retrieve Roles from Header, setup as 'user' role.");
      requestRoles.add("USER");
    }
    // add them to the session for convenience
    ((HttpServletRequest) context).getSession().setAttribute(RequestUtil.SESSION_ROLE_KEY, requestRoles);
    return requestRoles;
  }

  /**
   * @param mapper
   *            The Attributes2GrantedAuthoritiesMapper to use
   */
  public void setUserRoles2GrantedAuthoritiesMapper(Attributes2GrantedAuthoritiesMapper mapper) {
    grantedAuthoritiesMapper = mapper;
  }

  /**
   * All available roles for this application
   * 
   * @param allRoles
   */
  public void setAllRoles(Set allRoles) {
    this.allRoles = allRoles;
  }
  /**
   * @param roleHeaderName
   */
  public void setRoleHeaderName(String roleHeaderName) {
    this.roleHeaderName = roleHeaderName;
  }
  /**
   * @param testingRole
   */
  public void setTestingRoles(Set testingRole) {
    this.testingRoles = testingRole;
  }
}

web.xml updates


These snippets will look familiar if you've ever used spring security; define the filter, map to all resources. Spring contextConfiguration locations should have your resource properties loaded first, then security config, and everything else.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value> 
 classpath:/ctx/applicationContext-resources.xml 
 classpath:/ctx/applicationContext-security.xml 
 /WEB-INF/security.xml
 /WEB-INF/applicationContext*.xml
    </param-value>
  </context-param>

  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

Final Step - Setup Spring Security


security.xml is application specific, but my sample application is based on appfuse - JSF version - so it should cover most example uses.

The intercept-url element defines the roles or authentication states that are required to access a URL path. Roles should be comma-delimited.

To restrict pages by user type instead of user role the following values can be used:
IS_AUTHENTICATED_ANONYMOUSLY - Allow access to any user.
IS_AUTHENTICATED_REMEMBERED - Allow access to logged-in users or users with a "remember me" cookie.
IS_AUTHENTICATED_FULLY - Allow access to logged-in users.

To remove all Spring Security processing from a page use the filters="none" attribute.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
              http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <http auto-config="true" lowercase-comparisons="false">
  <intercept-url pattern="/images/**" filters="none" />
  <intercept-url pattern="/styles/**" filters="none" />
  <intercept-url pattern="/scripts/**" filters="none" />
  <intercept-url pattern="/javax.faces.resource/**"
   filters="none" />

  <!-- direct xhtml access disallowed -->
  <intercept-url pattern="/**/*.xhtml" access="ROLE_NOBODY" />

  <!-- local authentication is unused, but this is how it's configured -->
  <intercept-url pattern="/j_security*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
  <intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />

  <intercept-url pattern="/a4j.res/**"
   access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER" />
  <intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
  <intercept-url pattern="/user/**" access="ROLE_ADMIN,ROLE_USER" />

  <!-- show request headers and session variables for any user -->
  <intercept-url pattern="/env.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />

  <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />

  <!-- matches the bean name for HeaderAuthenticationFilter class above -->
  <custom-filter position="PRE_AUTH_FILTER" ref="ssoHeaderFilter" />

  <form-login login-page="/login" authentication-failure-url="/login?error=true"
   login-processing-url="/j_security_check" always-use-default-target="true"
   default-target-url="/" />
 </http>

 <authentication-manager alias="authenticationManager">
  <authentication-provider ref="preauthAuthProvider" />

  <!-- this is an example of alternate user authentication providers, although 
   we only have the PRE_AUTH_FILTER defined above, so it isn't used. -->
  <authentication-provider>
   <user-service>
    <user authorities="ROLE_USER" name="guest" password="guest" />
   </user-service>
  </authentication-provider>
 </authentication-manager>
</beans:beans>


If you read this far, and you want a Ready-To-Go example of how all this fits together, leave a comment and I will upload a full-source war to a temporary share site. Please don't put your email in the comments.

OIM Recon LDAP - adding fields


First, grab your metadata from MDS: MW_HOME/Oracle_IDM[1,2]/server/bin/

Setup weblogic.properties


wls_servername=oim_server1
application_name=oim
metadata_from_loca=/data/temp
metadata_to_loca=/data/temp
metadata_files=/db/LDAPUser,/db/RA_LDAPUSER.xml,/metadata/iam-features-ldap-sync/LDAPUser.xml


weblogicExportMetadata.sh/bat (connect as weblogic to the AdminServer e.g. t3://localhost:7001)

OIM Default/Existing Field Example


TO show you the fields that are required for update, here's a generic example to describe the LDAP/recon process for the OIM Role (user type) field to the LDAP attribute employeetype:

metadata/db/LDAPUser: Attribute appears twice, under "reconFields" and reconToOIMMappings:


reconFields

<reconAttr>
<oimFormDeescriptiveName>Role</oimFormDescriptiveName> (Attribute Name from OIM user attribute config)
<reconFieldName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">employeetype</reconFieldName>
<reconColName>RECON_USR_EMP_TYPE</reconColName>
<emDataType>string</emDataType>
<formFieldType/>
<targetattr keyfield="false" encrypted="false" required="false" type="String" name="usr_emp_type"/> (name of field in USR table)
</reconAttr>

reconToOIMMappings

<reconAttr>
<oimFormDescriptiveName>Role</oimFormDescriptiveName> (Attribute Name from OIM user attribute config)
<reconFieldName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">employeetype</reconFieldName> (LDAPUser.xml fieldname)
<reconColName>RECON_USR_EMP_TYPE</reconColName>
<emDataType>string</emDataType>
<formFieldType/>
<targetattr keyfield="false" encrypted="false" required="false" type="String" name="usr_emp_type"> (name of field in USR table)
<Transformation name="OneToOne">
<Parameter name="employeetype" fieldname="employeetype"/> (name of field in OVD)
</Transformation>
</targetattr>
</reconAttr>

/metadata/dbRA_LDAPUSer.xml: Attribute and recon field defined, under "entity-attributes" and "target-fields":


entity-attributes

<attribute name="Role"> (Attribute Name from OIM user attribute config)
<type>string</type>
<required>false</required>
<searchable>true</searchable>
<MLS>false</MLS>
<attribute-group>Basic</attribute-group>
<metadata-attachment/>
</attribute>
<attribute-map>
<entity-attribute>Role</entity-attribute>
<target-field>RECON_USR_EMP_TYPE</target-field>
</attribute-map>

target-fields

<field name="RECON_USR_EMP_TYPE">
<type>string</type>
<required>false</required>
</field>

/metadata/iam-features-ldap-sync/LDAPUser.xml: Mapping is defined, under "entity-attributes", "target-fields" and "attribute-maps"


<attribute name="Role">
<type>string</type>
<required>false</required>
<attribute-group>Basic</attribute-group>
<searchable>true</searchable>
</attribute>
<field name="employeeType">
<type>string</type>
<required>false</required>
</field>
<attribute-map>
<entity-attribute>Role</entity-attribute>
<target-field>employeeType</target-field>
</attribute-map>

Now a real example - Adding Lock Fields


The problem with a fully integrated OIM/OAM/Ldap Sync/OVD 11g is that it's broken.

Users lock themselves through OAM webgate login, correctly setting the oblockouttime - but OIM knows nothing about it. I have no idea how to get a unix-style epoch time (oblockouttime) through the reconcile process to OIM - if anyone knows how to convert to a date type, that would be awesome?

Oracle SR response not forthcoming (after several weeks), we looked for alternatives. Found an OVD field which was set during lockout: pwdaccountlockedtime.

First OVD must know about the target LDAP attribute. OAM schema (ob* attributes) should have been loaded to OVD, this is the relevant objectclass containing the OAM lockout info:

objectclasses: ( 1.3.6.1.4.1.3831.0.1.21 NAME 'oblixPersonPwdPolicy' DESC 'Oracle Access Manager defined objectclass' SUP top 
AUXILIARY MAY ( obpasswordcreationdate $ obpasswordhistory $ obpasswordchangeflag $ obpasswordexpmail $ oblockouttime $ oblogintrycount $ obfirstlogin $ obresponsetries $ oblastloginattemptdate $ oblastresponseattemptdate $ obresponsetimeout $ oblastsuccessfullogin $ oblastfailedlogin $ etc. etc. ) )

Next, to have two-way updates (LDAP back to OIM) you need a RECON field, I added these two to the RA_LDAPUSER table in the OIM database: lock date and number of failed login attempts.

describe RA_LDAPUSER 

 ... rest of the RA_LDAPUSER table above ...
RECON_ORCLUSERLOCKEDON DATE 
RECON_LOGINATTEMPTS    NUMBER(19) 

LDAPUser


<reconAttr>
<oimFormDescriptiveName>Locked On</oimFormDescriptiveName> (This field must match the OIM Attribute Name - see Configuration, User Attributes to confirm)
<reconFieldName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">pwdaccountlockedtime</reconFieldName>
<reconColName>RECON_ORCLUSERLOCKEDON</reconColName>
<emDataType>date</emDataType>
<formFieldType/>
<targetattr keyfield="false" encrypted="false" required="false" type="Date" name="pwdaccountlockedtime"/> (This field must match your OVD or LDAP)
</reconAttr>
<reconAttr>
<oimFormDescriptiveName>usr_login_attempts_ctr</oimFormDescriptiveName> (This field must match the OIM Attribute Name - see Configuration, User Attributes to confirm)
<reconFieldName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">oblogintrycount</reconFieldName>
<reconColName>RECON_LOGINATTEMPTS</reconColName>
<emDataType>number</emDataType>
<formFieldType/>
<targetattr keyfield="false" encrypted="false" required="false" type="String" name="oblogintrycount"/> (This field must match your OVD or LDAP)
</reconAttr>


<reconAttr>
<oimFormDescriptiveName>Locked On</oimFormDescriptiveName> (This field must match above - the OIM Attribute Name)
<reconFieldName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">pwdaccountlockedtime</reconFieldName>
<reconColName>RECON_ORCLUSERLOCKEDON</reconColName>
<emDataType>date</emDataType>
<formFieldType/>
<targetattr keyfield="false" encrypted="false" required="false" type="Date" name="usr_locked_on"> (This field must match the USR table column name)
<Transformation name="OneToOne">
<Parameter name="pwdaccountlockedtime" fieldname="pwdaccountlockedtime"/>
</Transformation> (This field must match your OVD or LDAP)
</targetattr>
</reconAttr>
<reconAttr>
<oimFormDescriptiveName>usr_login_attempts_ctr</oimFormDescriptiveName>
<reconFieldName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">oblogintrycount</reconFieldName>
<reconColName>RECON_LOGINATTEMPTS</reconColName>
<emDataType>number</emDataType>
<formFieldType/>
<targetattr keyfield="false" encrypted="false" required="false" type="String" name="usr_login_attempts_ctr">
<Transformation name="OneToOne">
<Parameter name="oblogintrycount" fieldname="oblogintrycount"/>
</Transformation>
</targetattr>
</reconAttr>

RA_LDAPUSER.xml


<attribute-map>
<entity-attribute>Locked On</entity-attribute>
<target-field>RECON_ORCLUSERLOCKEDON</target-field>
</attribute-map>
<attribute-map>
<entity-attribute>usr_login_attempts_ctr</entity-attribute>
<target-field>RECON_LOGINATTEMPTS</target-field>
</attribute-map>

<field name="RECON_ORCLUSERLOCKEDON">
<type>date</type>
<required>false</required>
</field>
<field name="RECON_LOGINATTEMPTS">
<type>number</type>
<required>false</required>
</field>

LDAPUser.xml


<attribute name="Locked On">
<type>date</type>
<required>false</required>
<attribute-group>Basic</attribute-group>
<searchable>true</searchable>
</attribute>
<attribute name="usr_login_attempts_ctr">
<type>number</type>
<required>false</required>
<attribute-group>Basic</attribute-group>
<searchable>true</searchable>
</attribute>

<field name="pwdaccountlockedtime">
<type>date</type>
<required>false</required>
</field>
<field name="oblogintrycount">
<type>number</type>
<required>false</required>
</field>

<attribute-map>
<entity-attribute>Locked On</entity-attribute>
<target-field>pwdaccountlockedtime</target-field>
</attribute-map>
<attribute-map>
<entity-attribute>usr_login_attempts_ctr</entity-attribute>
<target-field>oblogintrycount</target-field>
</attribute-map>

Re-import LDAP/Recon config


Back to the OIM_HOME server bin directory, run weblogicImportMetadata.sh/bat

Doesn't appear to need a restart, but I usually run "PurgeCache.sh All" at the same time.

Test reconciliation


Run the scheduled job; LDAP Full User Group reconcile - look to error details, fix typos and run again.

Thursday, 22 December, 2011

An open house at site3.ca

So here's this naff video i made talking to the folks at Site 3 coLaboratory - check it out: http://site3.ca/news/how-does-it-work/

Wednesday, 7 December, 2011

OIM 11g really delete deleted

I run through a *lot* of accounts testing in OIM/OAM 11.1.1.5. Things happen, accounts get borked.

Easiest way to keep your UAT system from getting out of hand is to delete sometimes, but the Reuse ID functionality in OIM is extremely dangerous (i.e. it doesn't work).

So I suggest using this SQL to clean your deleted test accounts (this is not something I would do in production!)

delete from oud where oiu_key in (select oiu_key from oiu where usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from osi where req_key in (select req_key from req where orc_key in (select orc_key from orc where orc.usr_key in (select USR_KEY from USR where usr_status='Deleted')));
delete from osi where osi_assigned_to_usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from osh where osh_assigned_to_usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from rcd where rce_key in (select rce_key from rce,orc where rce.orc_key = orc.orc_key and orc.usr_key  in (select USR_KEY from USR where usr_status='Deleted'));
delete from rch where rce_key in (select rce_key from rce,orc where rce.orc_key = orc.orc_key and orc.usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from rcu where rce_key in (select rce_key from rce,orc where rce.orc_key = orc.orc_key and orc.usr_key  in (select USR_KEY from USR where usr_status='Deleted'));
delete from rcb where rce_key in (select rce_key from rce,orc where rce.orc_key = orc.orc_key and orc.usr_key  in (select USR_KEY from USR where usr_status='Deleted'));
delete from rce where orc_key in (select orc_key from orc where orc.usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from oio where orc_key in (select orc_key from orc where orc.usr_key  in (select USR_KEY from USR where usr_status='Deleted'));
delete from oiu where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from oti where orc_key in (select orc_key from orc where orc.usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from osi where orc_key in (select orc_key from orc where orc.usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from orc where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from upd where upp_key in (select upp_key from upp where upp.usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from upp where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from usg where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from uhd where uph_key in (select uph_key from uph where uph.usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from uph where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from pcq where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from rcu where usr_key  in (select USR_KEY from USR where usr_status='Deleted');
delete from RECON_USER_MATCH where USR_KEY in (select USR_KEY from USR where usr_status='Deleted');
delete from RECON_ROLE_MATCH where RE_KEY in (select re_key from RECON_EVENTS where usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from RECON_ROLE_MEMBER_MATCH where RE_KEY in (select re_key from RECON_EVENTS where usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from RA_LDAPROLEMEMBERSHIP where RE_KEY in (select re_key from RECON_EVENTS where usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from RECON_HISTORY where RE_KEY in (select re_key from RECON_EVENTS where usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from RA_LDAPUSER where RE_KEY in (select re_key from RECON_EVENTS where usr_key in (select USR_KEY from USR where usr_status='Deleted'));
delete from RECON_EVENTS where usr_key in (select USR_KEY from USR where usr_status='Deleted');
delete from usr where usr_key in (select USR_KEY from USR where usr_status='Deleted');

OIM 11g SPML Modify Request

This post is a follow-on from my post about getting the SPML service to work security-wise.

Now we're using CXF to send a mod request - including User Defined Fields (UDF). My UDF is TermsAgreement, if you're not sure what to use, I'm not surprised.

In API code, if it's Thor API you need to use USR_UDF_ATTRIBNAME.. if it's Oracle API you need to use exactly as appears on User Configuration -> User Attributes tab; in the "Attribute Name" column.

Also verify with what's in your OIM metadata/iam-features-requestactions/model-data/ModifyUserDataset.xml - the value of what you set for AttributeReference name="YourAttributeName"

Also, it doesn't seem to love spaces.

The full source project: http://wikisend.com/download/608008/spmlclient-11.1.1.5.cxf.jar

private static final QName SERVICE_NAME = new QName("http://xmlns.oracle.com/idm/identity/webservice/SPMLService",
   "SPMLService");

 public static void main(String args[]) throws Exception {
 
  URL wsdlURL = new URL("http://localhost:14000/spml-xsd/SPMLService?wsdl");
  // URL wsdlURL = SPMLService.WSDL_LOCATION;

  SPMLService ss = new SPMLService(wsdlURL, SERVICE_NAME);
  SPMLRequestPortType port = ss.getSPMLServiceProviderSoap();

  Map ctx = ((BindingProvider) port).getRequestContext();
  ctx.put("ws-security.username", "xelsysadm");
  ctx.put("ws-security.password", "the password");

  // adding logging
  Client client = ClientProxy.getClient(port);
  client.getInInterceptors().add(new LoggingInInterceptor());
  client.getOutInterceptors().add(new LoggingOutInterceptor());

  ServiceHeaderType serviceHeader = new ServiceHeaderType();

  System.out.println("Invoking spmlModifyRequest...");
  oracle.iam.wsschema.model.spmlv2.core.ModifyRequestType modifyBody = new oracle.iam.wsschema.model.spmlv2.core.ModifyRequestType();
 
  java.util.List modCapData = new java.util.ArrayList();

  oracle.iam.wsschema.model.spmlv2.core.CapabilityDataType modCap = new oracle.iam.wsschema.model.spmlv2.core.CapabilityDataType();
  java.util.List modCapAny = new java.util.ArrayList();
  modCap.getAny().addAll(modCapAny);
  modCap.setMustUnderstand(Boolean.TRUE);
  modCap.setCapabilityURI("urn:oasis:names:tc:SPML:2:0:reference");
  modCapData.add(modCap);
  modifyBody.getCapabilityData().addAll(modCapData);
  modifyBody.setRequestID("RequestID-763892610");

  oracle.iam.wsschema.model.spmlv2.core.ExecutionModeType async = oracle.iam.wsschema.model.spmlv2.core.ExecutionModeType.ASYNCHRONOUS;
  modifyBody.setExecutionMode(async);
  modifyBody.setLocale("en");

  oracle.iam.wsschema.model.spmlv2.core.PSOIdentifierType modifyBodyPsoID = new oracle.iam.wsschema.model.spmlv2.core.PSOIdentifierType();

  java.util.List modifyBodyPsoIDAny = new java.util.ArrayList();
  java.lang.Object modifyBodyPsoIDAnyVal1 = null;
  modifyBodyPsoIDAny.add(modifyBodyPsoIDAnyVal1);
  modifyBodyPsoID.getAny().addAll(modifyBodyPsoIDAny);
  modifyBodyPsoID.setID("identity:6C9B96E99FC8DC32E040E50A3D5252F5");

  java.util.List mods = new java.util.ArrayList();
  oracle.iam.wsschema.model.spmlv2.core.ModificationType modType = new oracle.iam.wsschema.model.spmlv2.core.ModificationType();

  oracle.iam.wsschema.model.spmlv2.core.SelectionType component = new oracle.iam.wsschema.model.spmlv2.core.SelectionType();
  java.util.List componentAny = new java.util.ArrayList();
  component.getAny().addAll(componentAny);
  java.util.List componentNamespacePrefixMap = new java.util.ArrayList();
  component.getNamespacePrefixMap().addAll(componentNamespacePrefixMap);
  component.setPath("/identity");
  component.setNamespaceURI("http://www.w3.org/TR/xpath20");
  modType.setComponent(component);

  oracle.iam.wsschema.model.common.pso.ProvisioningObjectType modsData = new oracle.iam.wsschema.model.common.pso.ProvisioningObjectType();

  oracle.iam.wsschema.model.common.pso.Identity idAttribs = new oracle.iam.wsschema.model.common.pso.Identity();
  oracle.iam.wsschema.model.common.pso.UnboundedAttributes unboundedIdAttribs = new oracle.iam.wsschema.model.common.pso.UnboundedAttributes();

                // Must use these unbounded attributes for UDFs.
  java.util.List modsDataIdentityAttributesAttr = new java.util.ArrayList();
  DsmlAttr udfAttr = new DsmlAttr();
  udfAttr.setName("TermsAgreement");
  udfAttr.getValue().add("1.0");
  modsDataIdentityAttributesAttr.add(udfAttr);
  unboundedIdAttribs.getAttr().addAll(modsDataIdentityAttributesAttr);
  idAttribs.setAttributes(unboundedIdAttribs);


                // Standard attributes for SPML modifications follows.
  idAttribs.setActiveEndDate(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(
    "2011-06-14T16:07:20.498-04:00"));
  idAttribs.setActiveStartDate(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(
    "2011-06-14T16:07:20.514-04:00"));

  oracle.iam.wsschema.model.common.pso.MultiValuedString deptNum = new oracle.iam.wsschema.model.common.pso.MultiValuedString();
  java.util.List deptNumValue = new java.util.ArrayList();
  deptNumValue.add("12345");
  deptNum.getValue().addAll(deptNumValue);
  idAttribs.setDepartmentNumber(deptNum);

  oracle.iam.wsschema.model.common.pso.MultiValuedString firstName = new oracle.iam.wsschema.model.common.pso.MultiValuedString();
  java.util.List firstNameValue = new java.util.ArrayList();
  firstNameValue.add("Hello");
  firstName.getValue().addAll(firstNameValue);
  idAttribs.setGivenName(firstName);

  idAttribs.setHireDate(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(
    "2011-06-14T16:07:20.514-04:00"));

  oracle.iam.wsschema.model.common.pso.MultiValuedString mail = new oracle.iam.wsschema.model.common.pso.MultiValuedString();
  java.util.List mailValue = new java.util.ArrayList();
  mail.getValue().addAll(mailValue);
  idAttribs.setMail(mail);

  idAttribs.setMiddleName("MiddleName");

  oracle.iam.wsschema.model.common.pso.MultiValuedBinary passwd = new oracle.iam.wsschema.model.common.pso.MultiValuedBinary();
  java.util.List passwdValue = new java.util.ArrayList();
  passwdValue.add("Testing123!".getBytes());
  passwd.getValue().addAll(passwdValue);
  idAttribs.setPassword(passwd);

  idAttribs.setPreferredLanguage("fr");

  idAttribs.setUserType("NONW");

  modsData.setIdentity(idAttribs);

  oracle.iam.wsschema.model.common.pso.Member modsDataMember = new oracle.iam.wsschema.model.common.pso.Member();
  // Entity ID / USR_KEY from OIM
  modsDataMember.setIdentityPSOID("3");

  modsData.setMember(modsDataMember);
  modType.setData(modsData);

  oracle.iam.wsschema.model.spmlv2.core.ModificationModeType modTypeModificationMode = oracle.iam.wsschema.model.spmlv2.core.ModificationModeType.REPLACE;
  modType.setModificationMode(modTypeModificationMode);
  mods.add(modType);
  modifyBody.getModification().addAll(mods);

  oracle.iam.wsschema.model.spmlv2.core.ReturnDataType modifyBodyReturnData = oracle.iam.wsschema.model.spmlv2.core.ReturnDataType.DATA;
  modifyBody.setReturnData(modifyBodyReturnData);

  oracle.iam.wsschema.model.spmlv2.core.ModifyResponseType modReturn = port.spmlModifyRequest(modifyBody,
    serviceHeader);
  System.out.println("spmlModifyRequest.result=" + modReturn);

       }

And since some people are assembling the requests in other clients, here's an example of the request by the above code:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-1">
        <wsse:Username>xelsysadm</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">the password</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
    <ServiceHeader xmlns:ns9="http://xmlns.oracle.com/idm/identity/OperationData" xmlns:ns8="urn:oasis:names:tc:SPML:2:0:reference" xmlns:ns7="urn:oasis:names:tc:SPML:2:0:suspend" xmlns:ns6="http://xmlns.oracle.com/idm/identity/PSO" xmlns:ns5="http://xmlns.oracle.com/idm/identity/spmlv2custom/Username" xmlns:ns4="urn:oasis:names:tc:SPML:2:0:password" xmlns:ns3="urn:oasis:names:tc:SPML:2:0:async" xmlns:ns2="urn:oasis:names:tc:SPML:2:0" xmlns="urn:names:spml:ws:header"/>
  </soap:Header>
  <soap:Body>
    <ns2:modifyRequest xmlns="urn:names:spml:ws:header" xmlns:ns2="urn:oasis:names:tc:SPML:2:0" xmlns:ns3="urn:oasis:names:tc:SPML:2:0:async" xmlns:ns4="urn:oasis:names:tc:SPML:2:0:password" xmlns:ns5="http://xmlns.oracle.com/idm/identity/spmlv2custom/Username" xmlns:ns6="http://xmlns.oracle.com/idm/identity/PSO" xmlns:ns7="urn:oasis:names:tc:SPML:2:0:suspend" xmlns:ns8="urn:oasis:names:tc:SPML:2:0:reference" xmlns:ns9="http://xmlns.oracle.com/idm/identity/OperationData" returnData="data" requestID="RequestID-763892610" executionMode="asynchronous" locale="en">
      <ns2:capabilityData mustUnderstand="true" capabilityURI="urn:oasis:names:tc:SPML:2:0:reference"/>
      <ns2:modification modificationMode="replace">
        <ns2:component path="/identity" namespaceURI="http://www.w3.org/TR/xpath20"/>
        <ns2:data>
          <ns6:identity>
            <ns6:attributes>
              <ns6:attr name="TermsAgreement">
                <ns6:value>1.0</ns6:value>
              </ns6:attr>
            </ns6:attributes>
            <ns6:activeEndDate>2011-06-14T16:07:20.498-04:00</ns6:activeEndDate>
            <ns6:activeStartDate>2011-06-14T16:07:20.514-04:00</ns6:activeStartDate>
            <ns6:departmentNumber>
              <ns6:value>12345</ns6:value>
            </ns6:departmentNumber>
            <ns6:givenName>
              <ns6:value>Hello</ns6:value>
            </ns6:givenName>
            <ns6:hireDate>2011-06-14T16:07:20.514-04:00</ns6:hireDate>
            <ns6:mail/>
            <ns6:middleName>MiddleName</ns6:middleName>
            <ns6:password>
              <ns6:value>VGVzdGluZzEyMyE=</ns6:value>
            </ns6:password>
            <ns6:preferredLanguage>fr</ns6:preferredLanguage>
            <ns6:userType>NONW</ns6:userType>
          </ns6:identity>
          <ns6:member>
            <ns6:identityPSOID>3</ns6:identityPSOID>
          </ns6:member>
        </ns2:data>
      </ns2:modification>
    </ns2:modifyRequest>
  </soap:Body>
</soap:Envelope>

Tuesday, 15 November, 2011

The Logging Mess

Fantastic article (because we both came to the same conclusion) about logging options in JEE.

Also, oracle weblogic logging is the worst thing I have ever seen.

Wednesday, 29 June, 2011

OIM 11g SPML Client test

OIM (Oracle Identity Manager) 11g has a default security policy attached to the spml-xsd SOAP endpoint that is overly difficult to deal with.

The default endpoint policy is oracle/wss11_saml_or_username_token \
with_message_protection_service_policy

However, the "message protection" requirement is not configured with a signing certificate.

Detach this policy and attach: "oracle/wss_username_token_service_policy" and restart oim.

Creating a client with JAX-WS or CXF is super easy now! Using the Eclipse "new webservice client wizard" and the Apache CXF runtime, enter WSDL and generate.

Add the "ws-security.username" and credentials to the client class, and it starts to look much easier.

If you want the full CXF generated source, the jar is:
http://wikisend.com/download/608008/spmlclient-11.1.1.5.cxf.jar

public class TestClass {

private static final QName SERVICE_NAME = new QName(
   "http://xmlns.oracle.com/idm/identity/webservice/SPMLService",
   "SPMLService");

 public static void main(String args[]) throws Exception {
  URL wsdlURL = new URL("http://your-identity-host/spml-xsd/SPMLService?wsdl");

  SPMLService ss = new SPMLService(wsdlURL, SERVICE_NAME);
  SPMLRequestPortType port = ss.getSPMLServiceProviderSoap();

  Map ctx = ((BindingProvider) port).getRequestContext();
  ctx.put("ws-security.username", "xelsysadm");
  ctx.put("ws-security.password", "the password");

  ServiceHeaderType serviceHeader = new ServiceHeaderType();

  ValidateUsernameRequestType validateUser = new ValidateUsernameRequestType();
  validateUser.setUsername("sbeynon");
  ValidateUsernameResponseType retValUser = port
    .spmlValidateUsernameRequest(validateUser, serviceHeader);
  System.out.println("spml validate user =" + retValUser.isValid());
  }
}

There's a follow on to this post, showing an example SPML Modify Request

Monday, 6 June, 2011

2011 fencing, fire swords

DSC_0578DSC_0421DSC_0425DSC_0426DSC_0428DSC_0429
DSC_0430DSC_0431DSC_0432DSC_0436DSC_0438DSC_0443
DSC_0449DSC_0453DSC_0455DSC_0469DSC_0472DSC_0476
DSC_0481DSC_0489DSC_0492DSC_0500DSC_0503DSC_0533

2011 fencing, fire swords, a set on Flickr.

how cool is it when you turn 30 and throw a keg/fencing party? 30 straight rounds.

Sunday, 8 May, 2011

2011 May - Toronto Mini Maker Faire

On or way !first decent day of spring!i like the treesdown toward the don valleywander out of the ravine to the brickworksbrickworks exterior
bucket o' waffle batterOntario Science Centre - representFoulab - of Montrealblasting machine game from Foulab - of MontrealFoulab - of MontrealDYAD by ][ - in action
putt puttDYAD - dismountDYAD by ][the man behind the DYAD gamebrickworks kiln tunnelsbrian tao - adjusting something :)
DSC_0278ddimit - 3D printed designDSC_0281InteraccessDSC_0283russel zeit - chair

First Toronto Mini Maker Faire was a bunch of fun and awesome.