Configuring Authentication and Authorization in BES5.2 with LDAP
Bruce Wallace
March, 2003

Purpose

This white paper will describe the steps needed to reproduce the
proof of concept project that demonstrated Web and EJB container
security using Borland's JBuilder8 Enterprise IDE and BES5.2 J2EE
app server with a test LDAP server.  There are some interim tests
that are produced via test programs that are not per-se part of
the final proof of concept system. However, they provide the reader
with the ability to verify the setup in increments.

Prerequisites

It will be assumed that the reader has already installed onto
Windows 2000 a copy of JBuilder8 (Enterprise edition) and BES5.2
server and management console. It will be further assumed that
the reader has already successfully built web and EJB projects
on JBuilder8 and has deployed and executed them via BES5.2 and
has managed them via the Borland Management Console (and has
therefore completed the myriad arcane configurations to get
them to work together).
Finally, it is assumed that the reader has access to a working
LDAP server as well as the information needed to access it (as
detailed later in this paper).

Components that will be Produced

The following components will be generated by the end of this paper:
•	a JBuilder8 project containing the development components
•	a simple stateless session EJB with a remote home and remote interface
•	a simple standalone Java client program that accesses the above EJB
•	an EJB deployment descriptor that specifies security roles and constraints
•	a simple servlet that accesses the above EJB
•	a simple HTML page with a login form
•	a simple HTML page with a login error message and "Try again" form
•	a web container deployment descriptor specifying security roles/constraints
•	a test user/group/password database using JDataStore (built into BES)
•	a set of configurations to be manually applied to BES via its console
•	a simple JNDI utility program that directly accesses the LDAP server
•	(TBD) a separate set of BES configurations that are specific to LDAP
There is a .zip file of an existing JBuilder project and all its components here.

Steps that will Described

The following steps will be described by the end of this paper and the
instructions must be followed in order:
Step 1: Create a JBuilder8 project and simple EJB
Step 2: Create a simple EJB test client

Step 3: Configure BES5.2 for security (using JDataStore database)
Step 4: Configure EJB for security
Step 5: Configure Test Client for security
Step 6: Test EJB security via Test Client

Step 7: Create a simple servlet that accesses EJB
Step 8: Configure BES5.2 for web container security
Step 9: Configure Servlet for BASIC security
Step 10: Create and Use simple login and login-error HTML pages

Step 11: Create a simple JNDI/LDAP test client
Step 12: Configure BES5.2 for security using LDAP server
Step 13: Test Servlet and EJB security using LDAP server

NOTE: Ignore the following message when running the server via JBuilder8:
 "MAPPING configuration error for request URI"

NOTE: Over the course of building this proof of concept, you may optionally
            set the following properties to increase the amount of BES debug
            information generated into the server and/or partition log files:
  (per http://info.borland.com/techpubs/books/vbj/vbj40/Java-reference/properties.html
   and http://info.borland.com/techpubs/visibroker/visibroker4/vbjrel.html
   and http://info.borland.com/techpubs/books/security/iss41/security-guide/secejb.html#48719
   and http://info.borland.com/devsupport/bes/faq/5.1/security/props.html )

  *) set the following in the partition VisiBroker properties (vbroker.properties):
      vbroker.orb.debug=true
      vbroker.orb.warn=2
      vbroker.orb.logLevel=7
      vbroker.security.logLevel=7
  *) set the following in the partition VM configuration (partition_server.config):
      vmprop EJBDebug

Step 1: Create a JBuilder8 project and simple EJB

Assuming that you have previously set your JBuilder default project properties
such that new projects can correctly build, deploy, and run via BES5.2,
do the following:
1) create a new project via File->New Project
   *) with name "LDAP_POC"
   *) use JVM "java version 1.4.1-b21"
   *) use lib "Borland Enterprise Server 5.0.2-5.1.x Client"
   *) use server "Borland Enterprise Server AppServer Edition 5.0.2-5.1.x"
   *) specify BES partition "POC"
2) create a new EJB module via File->New->Enterprise->EJB Module
   *) with name "EJB_POC"
   *) specify EJB 2.0 compliance
3) create an EJB pool via File->New->Enterprise->EJB2Designer
   *) use EJB_POC
4) create a session EJB via backClick->CreateEJB->SessionBean
   *) with name SessionEJB
   *) specify "remote" interfaces
   *) specify stateless
5) edit the source "SessionEJB.java" to look like:

package ldap_poc;

import javax.ejb.*;
import java.util.*;
import java.rmi.*;

public interface SessionEJB extends javax.ejb.EJBObject
{
  static final public long kRELEASE = 100L;	// Bean Release 1.00

  public long SafeVersion() throws RemoteException;
  public long UnsafeVersion() throws RemoteException;
}

6) edit the source "SessionEJBBean.java" to look like:

package ldap_poc;

import javax.ejb.*;

public class SessionEJBBean implements SessionBean
{
  SessionContext sessionContext;

  public void ejbCreate() throws CreateException {}
  public void ejbRemove(){}
  public void ejbActivate(){}
  public void ejbPassivate(){}

  public void setSessionContext(SessionContext sessionContext)
  { this.sessionContext = sessionContext;  }

  public long SafeVersion() { return SessionEJB.kRELEASE; }
  public long UnsafeVersion() { return SessionEJB.kRELEASE + 1; }
}

7) verify by rebuilding project via Project->Rebuild Project
8) when project correctly builds, create a run configuration via Run->Run Project
  *) create new server run configuration named "POC Server"
9) Insure that BES52 is NOT already running then run project from JBuilder8
  *) JBuilder should auto launch server, create partition, and deploy project EJBs

Step 2: Create a simple EJB test client

1) create a test client via File->New->Enterprise->EJB Test Client Wizard
  *) use "application" client type
  *) use "SessionEJB"
  *) specify class name "SessionEJBTestClient1"
  *) use package "ldap_poc"
  *) specify creation of runtime config "SessionEJBTestClient1"
2) edit the source "SessionEJBTestClient1.java" as follows:
  *) add the following lines to the bottom of main():

client.create();
boolean safe = Boolean.valueOf(args[0]).booleanValue();
System.out.println("Arg=["+args[0]+"]");
System.out.println("Version("+(safe?"":"un")+"safe)="
  +((safe) ? client.SafeVersion() : client.UnsafeVersion())
                  );
System.exit(0);

3) rebuild the JBuilder project to verify changes
4) customize runtime configs for this program via Run->Configurations...
  *) edit "SessionEJBTestClient1" configuration
  *) add "(safe)" to configuration name
  *) specify a single application parameter "true"
  *) copy "SessionEJBTestClient1(safe)" configuration
  *) change new config name to "SessionEJBTestClient1(unsafe)"
  *) change "true" to "false" in new config app parameter
5) execute test client via Run->Run Project
  *) select unsafe version and expect results as follows:

-- Initializing bean access.
-- Succeeded initializing local bean access through Local Home interface.
-- Execution time: 2515 ms.
-- Calling create()
-- Succeeded: create()
-- Execution time: 31 ms.
-- Return value from create(): Stub[repository_id=RMI:ldap_poc.SessionEJB:0000000000000000,key=ServiceI ...
Arg=[false]
-- Calling UnsafeVersion()
-- Succeeded: UnsafeVersion()
-- Execution time: 125 ms.
-- Return value from UnsafeVersion(): 101.
Version(unsafe)=101

  *) select safe version and expect results as follows:

-- Initializing bean access.
-- Succeeded initializing local bean access through Local Home interface.
-- Execution time: 2360 ms.
-- Calling create()
-- Succeeded: create()
-- Execution time: 15 ms.
-- Return value from create(): Stub[repository_id=RMI:ldap_poc.SessionEJB:0000000000000000,key=ServiceI ...
Arg=[true]
-- Calling SafeVersion()
-- Succeeded: SafeVersion()
-- Execution time: 0 ms.
-- Return value from SafeVersion(): 100.
Version(safe)=100

Step 3: Configure BES5.2 for security (using JDataStore database)

1) launch a DOS window and run the Borland "userdbadmin" tool to create user/password database
  *) cd to <BES install dir>/var/servers/<your-server-name>
  *) run program via following (all on same line):
    userdbadmin -create -db jdbc:borland:dslocal:adm/security/pocDB.jds
     -driver com.borland.datastore.jdbc.DataStoreDriver
     -user admin -password admin -interactive
  *) create database contents via following interactive commands to userdbadmin:
       > addgroups poc_super
       > addgroups poc_normal
       > adduser super superpwd poc_super
       > adduser normal normalpwd poc_normal
       > quit
 2) enable security on the POC partition via the Borland Management Console
  *) launch the Borland Management Console and access your server
  *) backClick the POC partition in your server and select "Configure" from the menu
  *) select the Security tab and enable security checkbox and SSL checkbox
  *) select the Authentication button and add the following to config.jaas:

POCrealm {
  com.borland.security.provider.authn.BasicLoginModule required
	DRIVER=com.borland.datastore.jdbc.DataStoreDriver
	URL="jdbc:borland:dslocal:../../adm/security/pocDB.jds" 
	TYPE=BASIC
	LOGINUSERID=admin 
	LOGINPASSWORD=admin;
};

  *) select the Authorization button and create a new security domain named "pocSecureDomain"
  *) add the following to pocSecureDomain.rolemap, verify via verify button, and save:

poc_super_role {
  *GROUP=poc_super
}

poc_normal_role {
  *GROUP=poc_normal
}

  *) make sure the following lines exist in vbroker.properties via the visiBroker tab->advanced button:

vbroker.security.authDomains=default,pocSecureDomain
vbroker.security.domain.pocSecureDomain.defaultAccessRule=grant
vbroker.security.domain.pocSecureDomain.rolemap_path=security/pocSecureDomain.rolemap

  *) exit the configuration editor and allow the partition to be restarted.

Step 4: Configure EJB for security

1) add EJB security specifications via deployment descriptor files
  *) edit the project EJB module's ejb-jar.xml adding the following after the <assembly-descriptor> tag
        <security-role>
            <role-name>poc_super_role</role-name>
        </security-role>
        <security-role>
            <role-name>poc_normal_role</role-name>
        </security-role>
        <method-permission>
            <unchecked />
            <method>
                <description />
                <ejb-name>SessionEJB</ejb-name>
                <method-intf>Home</method-intf>
                <method-name>*</method-name>
            </method>
        </method-permission>
        <method-permission>
            <unchecked />
            <method>
                <description />
                <ejb-name>SessionEJB</ejb-name>
                <method-intf>Remote</method-intf>
                <method-name>remove</method-name>
            </method>
        </method-permission>
        <method-permission>
            <unchecked />
            <method>
                <description />
                <ejb-name>SessionEJB</ejb-name>
                <method-intf>Remote</method-intf>
                <method-name>UnsafeVersion</method-name>
            </method>
        </method-permission>
        <method-permission>
            <role-name>poc_super_role</role-name>
            <method>
                <description />
                <ejb-name>SessionEJB</ejb-name>
                <method-intf>Remote</method-intf>
                <method-name>SafeVersion</method-name>
            </method>
        </method-permission>

  *) edit the project EJB module's ejb-borland.xml replacing the <assembly-descriptor /> tag with:

    <assembly-descriptor>
        <security-role>
            <role-name>poc_super_role</role-name>
        </security-role>
        <security-role>
            <role-name>poc_normal_role</role-name>
        </security-role>
    </assembly-descriptor>

2) rebuild the JBuilder project to verify changes
3) deploy the EJB jar file via Run->Run Project->POC Server

Step 5: Configure Test Client for security

1) Verify that Test clients fail (now that server is secure but no client security configured yet)
  *) run the test clients that ran successfully in step 2.5 expecting the following failure in each case:
    java.rmi.NoSuchObjectException: CORBA INV_OBJREF 0[]

2) Create "client.properties" in root level of JBuilder project via File->New File with contents:

#Enable Security
vbroker.security.disable=false
#Automatically Login Client
vbroker.security.login=true
#which realm is client using? (new for BES5.2)
vbroker.security.login.realms=POCrealm
#Which callback handler to use?
vbroker.security.authentication.callbackHandler=com.borland.security.provider.authn.CmdLineCallbackHandler

3) add the following to the JVM parameters of all the application run configurations:

-DORBpropStorage=./client.properties

Step 6: Test EJB security via Test Client

1) Verify that "unsafe" Test client disallows access until legal login
  *) run the "unsafe" test client (as in step 2.5)
  *) when prompted for a username, enter "foo"
  *) when prompted for a password, enter "bar"
  *) verify that you are denied access with message:
 Error:Authentication failed for realm POCrealm retry (Yes/No)?[Yes] 
  *) accept retry offer and this time enter "normal" and "normalpwd" for name and password
  *) verify that program completes with message:
  ...Succeeded: UnsafeVersion() ...
  Return value from UnsafeVersion(): 101. Version(unsafe)=101

2) Verify that "safe" Test client disallows access until legal login AND proper role
  *) run the "safe" test client (as in step 2.5)
  *) when prompted for a username, enter "foo"
  *) when prompted for a password, enter "bar"
  *) verify that you are denied access with message:
   Error:Authentication failed for realm POCrealm retry (Yes/No) ? [Yes] 
  *) accept retry offer and this time enter "normal" and "normalpwd" for name and password
  *) verify that program fails with message:
   java.rmi.AccessException: CORBA NO_PERMISSION 0[]

3) Verify that "safe" Test client allows access with proper role
  *) run the "safe" test client (as in step 2.5)
  *) when prompted for a username, enter "foo"
  *) when prompted for a password, enter "bar"
  *) verify that you are denied access with message:
   Error:Authentication failed for realm POCrealm retry (Yes/No) ? [Yes] 
  *) accept retry offer and this time enter "super" and "superpwd" for name and password
  *) verify that program completes with message:
  ...Succeeded: SafeVersion() ...
  Return value from SafeVersion(): 100. Version(safe)=100

4) If there are problems, try shutting down all servers, consoles, JBuilder, and reboot Windows
  *) launch BES directly
  *) launch Borland Management Console directly
  *) make sure server and partition are running (check log files)
  *) launch JBuilder and run test configs (but not the server config since server is already running)

5) If the username/password are recognized but not the roles associated with them, try the following:
  *) backClick the POC partition in your server and select "Configure" from the menu
  *) select the Security tab and select "pocSecureDomain" as the default domain

Step 7: Create a simple servlet that accesses EJB

1) create a new WebApp module via File->New->Web->Web Application
   *) with name "WEB_POC"
2) create a servlet via File->New->Web->Servlet
  *) use name “ServletPOC”
  *) use package "ldap_poc"
  *) use "standard" servlet type
  *) no need for SHTML generation
  *) specify URL "/servletpoc"
  *) no need for runtime config generation
3) edit the source "ServletPOC.java" to be follows:

package ldap_poc;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;

public class ServletPOC extends HttpServlet
{
  public void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException
  { doIt( request, response ); }

  public void doPut(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException
  { doIt( request, response ); }

  void doIt(HttpServletRequest request, HttpServletResponse response)
  throws IOException
  {
    boolean safe = true;
    String safeParm = request.getParameter("safe");
    if (safeParm!=null) safe = Boolean.valueOf( safeParm ).booleanValue();

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<html>");
    out.println("<head><title>ServletPOC</title></head>");
    out.println("<body bgcolor=\"#ffffff\">");
    out.println("<p>The servlet has received a "+request.getMethod()+". This is the reply.</p>");

    long answer = -42;
    try
    {
      Context        ctx   = new InitialContext();
      Object         ref   = ctx.lookup("SessionEJB");
      SessionEJBHome xHome = (SessionEJBHome) PortableRemoteObject.narrow(ref, SessionEJBHome.class);
      SessionEJB     xTS   = xHome.create();
                     answer= ( safe ? xTS.SafeVersion() : xTS.UnsafeVersion() );
    }
    catch(Exception e)
    { out.println("<p><pre>Exception:<br/>"+e+"</pre></p>"); }

    out.println("The "+(safe?"":"un")+"safe answer to Life, the Universe, and Everything is "+ answer);
    out.println("</body></html>");
  }
}

4) rebuild the JBuilder project to verify changes
5) verify that the servlet can not yet access the protected EJB
  *) deploy the web application by backClicking on WEB_POC and using the deploy menu item
  *) launch a web browser and load URL: http://yourServerName:8080/WEB_POC/servletpoc
  *) verify that you are denied access with message:
   Exception:java.rmi.AccessException: CORBA NO_PERMISSION
  *) verify that the EJB was in fact not accessed via message:
   The safe answer to Life, the Universe, and Everything is -42

Step 8: Configure BES5.2 for web container security

1) enable security on the POC partition’s web container via the Borland Management Console
  *) launch the Borland Management Console and access your server
  *) backClick the Web Container in the POC partition in your server and select "Configure" from the menu
  *) select the General tab and edit the following in server.xml
  *) find the tag   <Service name="HTTP">
  *) replace the pre-existing tag   <Realm className=...> with tag:

     <Realm className="com.borland.catalina.realm.BSSRealm" debug="0"
	name="POCrealm" />

  *) exit the configuration editor and allow the partition to be restarted

Step 9: Configure Servlet for BASIC security

1) add WebApp security specifications via deployment descriptor files
  *) edit the project WebApp module's web.xml adding the following after the </servlet-mapping> tag:

     <security-constraint>
        <display-name>POCSecurityConstraint</display-name>
        <web-resource-collection>
            <web-resource-name>POCSecureColl</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>POST</http-method>
            <http-method>GET</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>poc_normal_role</role-name>
            <role-name>poc_super_role</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>POCrealm</realm-name>
    </login-config>

    <security-role>
        <role-name>poc_super_role</role-name>
    </security-role>
    <security-role>
        <role-name>poc_normal_role</role-name>
    </security-role>

  *) edit the project WebApp module's web-borland.xml adding the following after the </context-root> tag:

    <authorization-domain>pocSecureDomain</authorization-domain>
    <security-role>
        <role-name>poc_super_role</role-name>
    </security-role>
    <security-role>
        <role-name>poc_normal_role</role-name>
    </security-role>

2) rebuild the JBuilder project to verify changes
3) verify that the servlet and EJB can be accessed iff user is properly logged in
  *) redeploy the web application by backClicking on WEB_POC and using the redeploy menu item
  *) launch a web browser and load URL: http://yourServerName:8080/WEB_POC/servletpoc?safe=true
  *) when prompted for a username and password, enter “foo” and “bar”
  *) verify that you are re-prompted for a name and password
  *) when prompted for a username and password, enter “normal” and “normalpwd”
  *) verify that you are denied access with message:
   Exception:java.rmi.AccessException: CORBA NO_PERMISSION
  *) kill the browser, relaunch browser; retry the URL again and this time enter “super” and “superpwd”
  *) verify that the EJB was in fact not accessed via message:
   The safe answer to Life, the Universe, and Everything is 100 
  *) kill the browser, relaunch browser; try the URL: http://yourServerName:8080/WEB_POC/servletpoc?safe=false
  *) when prompted for a username and password, enter “normal” and “normalpwd”
  *) verify that the EJB was in fact not accessed via message:
   The unsafe answer to Life, the Universe, and Everything is 101

Step 10: Create and Use simple login and login-error HTML pages

1) create an HTML file in the JBuilder project via File->New File
  *) filename is “login.html”
  *) set path to <JBuilder project path>/LDAP_POC/WEB_POC/
  *) edit the file to contain as follows:

<html>
<head><title>Login Page for LDAP_POC</title></head>
<body bgcolor="#ffc800">
<h1>Login Page for LDAP_POC</h1>
<form method="POST" action="j_security_check">
  <table border="0" cellspacing="5">
    <tr>
      <th align="right">Username:</th>
      <td align="left"><input type="text" name="j_username"></td>
    </tr>
    <tr>
      <th align="right">Password:</th>
      <td align="left"><input type="password" name="j_password"></td>
    </tr>
    <tr>
      <td align="right"><input type="submit" value="Log In"></td>
      <td align="left"><input type="reset"></td>
    </tr>
  </table>
</form>
</body>
</html>

2) create an HTML file in the JBuilder project via File->New File
  *) filename is “login_error.html”
  *) set path to <JBuilder project path>/LDAP_POC/WEB_POC/
  *) edit the file to contain as follows:

<html>
<head><title>(Re)Login Page for LDAP_POC</title></head>
<body bgcolor="#FF8888">
<h1>Login Failed: Try Again</h1>
<form method="POST" action="j_security_check">
  <table border="0" cellspacing="5">
    <tr>
      <th align="right">Username:</th>
      <td align="left"><input type="text" name="j_username"></td>
    </tr>
    <tr>
      <th align="right">Password:</th>
      <td align="left"><input type="password" name="j_password"></td>
    </tr>
    <tr>
      <td align="right"><input type="submit" value="Log In"></td>
      <td align="left"><input type="reset"></td>
    </tr>
  </table>
</form>
</body>
</html>

3) change to FORM based authentication via deployment descriptor files
  *) edit the project WebApp module's web.xml replacing the following:

    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>POCrealm</realm-name>
    </login-config>

 with the following:

  <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>POCrealm</realm-name>
    <form-login-config>
      <form-login-page>/login.html</form-login-page>
      <form-error-page>/login_error.html</form-error-page>
    </form-login-config>
  </login-config>

4) rebuild the JBuilder project to verify changes
5) redeploy the web application by backClicking on WEB_POC and using the redeploy menu item
6) kill the browser, relaunch browser; try the URL: http://yourServerName:8080/WEB_POC/servletpoc
7) verify that you are now sent to the login page and that the servlet works as before using this login form
8) verify that if you enter an incorrect login, you are sent to the login error page 

Step 11: Create a simple JNDI/LDAP test client

0) NOTE: the highlighted data in this Step is LDAP server specific. The data shown is for the Lab’s server.
  *) the data in the Lab server is as follows in this LDIF dump:

#-------------------------------------------------------------------------------
# This file has been generated on 03.19.2003 at 12:58 from gallery-dc01.choicepoint.net:389
# by Softerra LDAP Browser 2.0 (www.ldapbrowser.com)
#-------------------------------------------------------------------------------
version: 1
dn: OU=Development,DC=gallery,DC=ins
dSCorePropagationData: 20030313165854.0Z
dSCorePropagationData: 20030313165558.0Z
dSCorePropagationData: 16010101000417.0Z
instanceType: 4
distinguishedName: OU=Development,DC=gallery,DC=ins
objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=gallery,DC=ins
objectClass: top
objectClass: organizationalUnit
objectGUID:: Nu/xQoPDzEiDtjTvJVCHMQ==
ou: Development
name: Development
uSNChanged: 334799
uSNCreated: 334799
whenChanged: 20030313122920.0Z
whenCreated: 20030313122920.0Z

dn: CN=John Doe,OU=Development,DC=gallery,DC=ins
memberOf: CN=Auth1,OU=Development,DC=gallery,DC=ins
accountExpires: 9223372036854775807
badPasswordTime: 126921452158125000
badPwdCount: 0
codePage: 0
cn: John Doe
countryCode: 0
displayName: John Doe
dSCorePropagationData: 20030313165854.0Z
dSCorePropagationData: 20030313165558.0Z
dSCorePropagationData: 16010101000417.0Z
givenName: John
instanceType: 4
lastLogoff: 0
lastLogon: 126921455029062500
logonCount: 0
distinguishedName: CN=John Doe,OU=Development,DC=gallery,DC=ins
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=gallery,DC=ins
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
objectGUID:: EsTu59HIp0awlLxHeKQUHg==
objectSid:: AQUAAAAAAAUVAAAAL9XsbQ/4YB0H5TsreQQAAA==
primaryGroupID: 513
pwdLastSet: 126920321979687500
name: John Doe
sAMAccountName: johndoe
sAMAccountType: 805306368
sn: Doe
userAccountControl: 512
userPrincipalName: johndoe@gallery.ins
uSNChanged: 334813
uSNCreated: 334801
whenChanged: 20030313123111.0Z
whenCreated: 20030313122956.0Z

dn: CN=Jane Doe,OU=Development,DC=gallery,DC=ins
memberOf: CN=Auth2,OU=Development,DC=gallery,DC=ins
accountExpires: 9223372036854775807
badPasswordTime: 0
badPwdCount: 0
codePage: 0
cn: Jane Doe
countryCode: 0
displayName: Jane Doe
dSCorePropagationData: 20030313165854.0Z
dSCorePropagationData: 20030313165558.0Z
dSCorePropagationData: 16010101000417.0Z
givenName: Jane
instanceType: 4
lastLogoff: 0
lastLogon: 0
logonCount: 0
distinguishedName: CN=Jane Doe,OU=Development,DC=gallery,DC=ins
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=gallery,DC=ins
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
objectGUID:: m0f3HWxQ00aXKpAPXbB2/A==
objectSid:: AQUAAAAAAAUVAAAAL9XsbQ/4YB0H5TsregQAAA==
primaryGroupID: 513
pwdLastSet: 126920322496406250
name: Jane Doe
sAMAccountName: janedoe
sAMAccountType: 805306368
sn: Doe
userAccountControl: 512
userPrincipalName: janedoe@gallery.ins
uSNChanged: 334811
uSNCreated: 334807
whenChanged: 20030313123049.0Z
whenCreated: 20030313123049.0Z

dn: CN=Auth1,OU=Development,DC=gallery,DC=ins
member: CN=John Doe,OU=Development,DC=gallery,DC=ins
cn: Auth1
dSCorePropagationData: 20030313165854.0Z
dSCorePropagationData: 20030313165558.0Z
dSCorePropagationData: 16010101000417.0Z
groupType: -2147483646
instanceType: 4
distinguishedName: CN=Auth1,OU=Development,DC=gallery,DC=ins
objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=gallery,DC=ins
objectClass: top
objectClass: group
objectGUID:: Jp2DCw8UEUGmoZ0Y3UWtWg==
objectSid:: AQUAAAAAAAUVAAAAL9XsbQ/4YB0H5TsrewQAAA==
name: Auth1
sAMAccountName: Auth1
sAMAccountType: 268435456
uSNChanged: 334822
uSNCreated: 334815
whenChanged: 20030313123214.0Z
whenCreated: 20030313123153.0Z

dn: CN=Auth2,OU=Development,DC=gallery,DC=ins
member: CN=Jane Doe,OU=Development,DC=gallery,DC=ins
cn: Auth2
dSCorePropagationData: 20030313165854.0Z
dSCorePropagationData: 20030313165558.0Z
dSCorePropagationData: 16010101000417.0Z
groupType: -2147483646
instanceType: 4
distinguishedName: CN=Auth2,OU=Development,DC=gallery,DC=ins
objectCategory: CN=Group,CN=Schema,CN=Configuration,DC=gallery,DC=ins
objectClass: top
objectClass: group
objectGUID:: 7P3eL59kdkqiHBw7/HVPow==
objectSid:: AQUAAAAAAAUVAAAAL9XsbQ/4YB0H5TsrfAQAAA==
name: Auth2
sAMAccountName: Auth2
sAMAccountType: 268435456
uSNChanged: 334824
uSNCreated: 334819
whenChanged: 20030313123223.0Z
whenCreated: 20030313123202.0Z

1) Create a new simple program via File->New Class
  *) use name “SimpleLDAP”
  *) edit the source “SimpleLDAP.java” to look like the following:

package ldap_poc;

import javax.naming.*;
import javax.naming.directory.*;
import java.util.*;

/**
 * Simplest program that verifies name/password against Lab LDAP Server
 * and looks up groups associated with user.
 * usage: java SimpleLDAP <name> <password>
 */
class SimpleLDAP
{
    static String kCONTEXT = "OU=Development,DC=Gallery,DC=ins";
    static String kLDAPURL = "ldap://gallery-dc01.choicepoint.net:389";

    static DirContext _ctx = null;

    public static boolean Authenticate( String name, String password )
    {
        Hashtable env = new Hashtable();
        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
        env.put( Context.PROVIDER_URL, kLDAPURL );
//	 env.put( Context.SECURITY_AUTHENTICATION, "simple" );
        env.put( Context.SECURITY_PRINCIPAL, "CN="+name+","+kCONTEXT );
        env.put( Context.SECURITY_CREDENTIALS, password );

        try { _ctx = new InitialDirContext(env); }
        catch (Exception e){ _ctx = null; e.printStackTrace(); return false; }
        return true;
    }

    public static List GetGroups( String username )
    throws NamingException
    {
      List groups = new ArrayList();
      String[] attrIDs = {"memberOf"};
      Attributes attrs = _ctx.getAttributes("cn="+username+","+kCONTEXT, attrIDs);
      if (attrs != null)
        for (NamingEnumeration ae=attrs.getAll(); ae.hasMore(); )
          for (NamingEnumeration e=((Attribute)ae.next()).getAll(); e.hasMore(); )
            groups.add( e.next().toString() );

      return groups;
    }

    public static void main( String[] args )
    throws NamingException
    {
        String name = args[0];
        String pass = args[1];
        if (args.length>2){ kLDAPURL = args[2]; kCONTEXT = args[3]; }
        System.out.println( "URL=["+kLDAPURL+"]; Context=["+kCONTEXT+"]" );
        System.out.println( "user=["+name+"]; password=["+pass+"]" );

        System.out.println( "Authenticated="+Authenticate(name,pass) );
        if (_ctx==null) return;
        List groups = GetGroups(name);
        for (Iterator i=groups.iterator(); i.hasNext();)
          System.out.println( "Group="+ i.next() );
        _ctx.close();
    }
}

2) rebuild the JBuilder project to verify changes
3) create a run configuration via Run->Configurations...->New...
  *) specify configuration name “SimpleLDAP (John Doe)”
  *) specify application parameters (including quotes): "John Doe" "password"
  *) use config type: “application”
  *) specify main class as “ldap_poc.SimpleLDAP” (hint: use search facility)
4) execute test client via Run->Run Project->SimpleLDAP (John Doe)
  *) expect results similar to the following (taking into account highlighted LDAP specific data):

URL=[ldap://gallery-dc01.choicepoint.net:389]; Context=[OU=Development,DC=Gallery,DC=ins]
user=[John Doe]; password=[password]
Authenticated=true
Group=CN=Auth1,OU=Development,DC=gallery,DC=ins

Step 12: Configure BES5.2 for security using LDAP server
 (DRAFT-NOT FINISHED!)

1) Add an LDAP security realm on the POC partition via the Borland Management Console
  *) launch the Borland Management Console and access your server
  *) backClick the POC partition in your server and select "Configure" from the menu
  *) select the Authentication button on the Security tab and edit config.jaas
  *) rename the existing “POCrealm” to “dbPOCrealm”, and add the following:

POCrealm {
   com.borland.security.provider.authn.LDAPLoginModule required
   INITIALCONTEXTFACTORY="com.sun.jndi.ldap.LdapCtxFactory"
   PROVIDERURL="ldap://gallery-dc01.choicepoint.net:389"
   SEARCHBASE="OU=Development,DC=Gallery,DC=ins"
   SEARCHSCOPE="true"
   USERNAMEATTRIBUTE="sAMAccountName"
   USERNAME="CN=John Doe,OU=Development,DC=Gallery,DC=ins"
   query.1="GROUP=(&(objectClass=group)(member={0}))"
   PASSWORD="password";
};

  *) NOTE that the highlighted data above is LDAP server specific. This is for the Lab’s server.

ADDENDUM TO Step 12 (a custom JAAS login module using JNDI to talk to LDAP)

here is a custom login module that
will integrate with BES5.2 and perform authentication and
authorization based on data in the Lab's LDAP server.

In addition to the single source code file...

* here is the realm definition to be put in the desired
partition's config.jaas file:

POCcustomRealm {
 POCLoginModule required;
};

* here is an example domain role mapping to be put
into the desired partition's Authentication config
(e.g. posSecureDomain.rolemap). This example
maps the J2EE role "poc_super_role" to any Subject
associated with a group called "poc_super" or a group
called "CN=Auth2,OU=Development,DC=gallery,DC=ins".
It also maps the role "poc_normal_role" to any Subject
associated with a group called "poc_normal".

poc_super_role {
  *GROUP=poc_super
  *GROUP="CN=Auth2,OU=Development,DC=gallery,DC=ins"
}

poc_normal_role {
  *GROUP=poc_normal
}

* here is the single source file POCLoginModule.java ...

import java.util.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.LoginModule;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.AccessController;

import com.borland.security.provider.PasswordCredential;
import com.borland.security.provider.UserPrincipal;
import com.borland.security.provider.GSSUPTarget;
import com.borland.security.spi.Privileges;

import javax.naming.*;
import javax.naming.directory.*;

public class POCLoginModule implements LoginModule
{
  public POCLoginModule() {}

  /** JAAS initialize will be called by JAAS **/
  public void initialize
  (
      Subject         subject,
      CallbackHandler cbHandler,
      Map             sharedState,
      Map             options
  )
  {
    /* Store only relevant information.
     * At this time sharedState and options are not used
     */
    fSubject         = subject;
    fCallbackHandler = cbHandler;

    /**
      * For local logins a target(realm) for local authentication is passed
      * to the login module from the Security Service . We look for it
      * and stash it away if found. Note that for remote authentication
      * the target information is obtained from the PasswordCredential.
      * Note also that the target is a GSSUPTarget . This means that the
      * underlying mechanism is a username / password based one unlike, say
      * Certificate based authentication.
      */
    Set      s = fSubject.getPublicCredentials( GSSUPTarget.class );
    Iterator i = s.iterator();
    if ( i.hasNext() ) fRealm = (GSSUPTarget) i.next();
  }

  static DirContext Authenticate( String dname, String password )
  {
    final String kLDAPURL = "ldap://gallery-dc01.choicepoint.net:389";
    DirContext ctx = null;

      Hashtable env = new Hashtable();
      env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
      env.put( Context.PROVIDER_URL, kLDAPURL );
      env.put( Context.SECURITY_PRINCIPAL, dname );
      env.put( Context.SECURITY_CREDENTIALS, password );

      try { ctx = new InitialDirContext(env); }
      catch (Exception e){ ctx = null; e.printStackTrace(); }
      return ctx;
  }

  static List GetGroups( DirContext ctx, String attrName, String dname )
  {
    List groups = new ArrayList();
    String[] attrIDs = { attrName };
    try
    {
      Attributes attrs = ctx.getAttributes( dname, attrIDs );
      /* DEBUG: print each attribute too */
      if (attrs != null)
        for (NamingEnumeration ae=attrs.getAll(); ae.hasMore(); )
        {
          Attribute attr   = (Attribute) ae.next();
          String    attrId = attr.getID();
          System.out.println( attrId + "..." );
          for (NamingEnumeration e = attr.getAll(); e.hasMore(); )
          {
            String g = e.next().toString();
            System.out.println( attrId + "=" + g );
            groups.add(g);
          }
        }
    }
    catch (NamingException e){ e.printStackTrace(); }
    return groups;
  }

  /**
   *  login : authentication happens here
   */
  public boolean login() throws LoginException
  {
    boolean credsFromSubject = true;

    /*  Try to extract the credential from the subject. If this is the
     *  login for a remote/local client ( remote authentication )
     *  then the security service passes to the login module a
     *  PasswordCredential containing the username, password and a target.
     *  We have to check for this case first
     */
    fCred = extractCredential( fSubject );

    if ( fCred == null ) // This means local login
    {
       credsFromSubject = false;

       /* Get user information. */
       fCred = getCredentialFromUser( fCallbackHandler );
    }

    /* validate */
    {
      //hardwiired for Lab server for now
      final String kCONTEXT = ",OU=Development,DC=Gallery,DC=ins";

       String name  = fCred.username();       //user name
       String dname = "CN="+name+kCONTEXT;    //full DN for user
       String gname = "memberOf";               //name of group attribute

       DirContext ctx = Authenticate( dname, new String(fCred.password()) );
       if ( ctx != null )
       {
          fPrinc  = new UserPrincipal( name, fCred.target() );
          fGroups = GetGroups( ctx, gname, dname );
       }
       else // login failed
       {
         /* Remove the credentials from subject if it was already present */
         if ( credsFromSubject )
         {
            Set privCreds  = getPrivateCreds( fSubject );
            privCreds.remove( fCred );
            privCreds = null;
         }

         /* Destroy credentials */
         fCred.destroy();
         fCred = null;

         throw new LoginException(" Login failed ");
      }
    }

    return fPrinc != null;
  }

 /** commit == > we can populate principals **/
  public boolean commit() throws LoginException
  {
    fCommitted = false;

    if ( fPrinc != null )  // login succeeded
    {
       if ( fCred == null || fCred.isDestroyed() )
         throw new LoginException("commit failed");

       try
       {
         /** Add principal and private credential only on commit
          */
         fSubject.getPrincipals().add( fPrinc );
         Set privCreds  = getPrivateCreds( fSubject );
         privCreds.add( fCred );

         Set groupSet = new HashSet();
         //groupSet.add("poc_normal");

       final String QUOTE = "\"";
         for (Iterator i=fGroups.iterator(); i.hasNext(); )
         {
           String group = i.next().toString();
           System.out.println( "Group=" + group );      // DEBUG
           groupSet.add( QUOTE+group+QUOTE );
         }

         fPrivileges = new Privileges( fPrinc.getName(), fSubject );
         fPrivileges.getAttributes().put( "group", groupSet );

         final Privileges privileges = fPrivileges;
         AccessController.doPrivileged( new PrivilegedAction()
         { public Object run()
           { privileges.getSubject().getPublicCredentials().add( privileges );
             return null;
         } } );
       }
       catch( Exception e ){ throw new LoginException("commit failed"); }

      fCommitted = true;
    }

    return true;
  }

  /** abort the operation and clean up */
  public boolean abort() throws LoginException
  {
    if ( fCommitted ) logout(); // log out should also cleanup credentials
    return true;
  }

  /** logout the principal */
  public boolean logout() throws LoginException
  {
     try
     {
         if ( fCred != null )
         {
           if ( fSubject != null )
           {
              fSubject.getPrincipals().remove( fPrinc );
              Set privCreds = getPrivateCreds( fSubject );
              privCreds.remove( fCred );
              privCreds = null;
           }

           /* clean up credential internally */
           fCred.destroy();
           fCred  = null;
           fPrinc = null;

           /* Set to unauthenticated */
           fCommitted = false;
         }

     }
     catch( Exception e ){ throw new LoginException("logout failed"); }

     return true;
  }

  private Set getPrivateCreds( final Subject subject )
  {
     return (Set)AccessController.doPrivileged(new PrivilegedAction() {
           public Object run() { return subject.getPrivateCredentials(); }
         });
  }

  private Set getPrivateCreds( final Subject subject, final Class cls )
  {
     return (Set)AccessController.doPrivileged(new PrivilegedAction() {
           public Object run() { return subject.getPrivateCredentials(cls); }
         });
  }

 /**
  * Extracts credential from the subject
  * Currently understands only UP based PasswordCredential
  */
  protected PasswordCredential extractCredential( Subject sub )
  {
    Set s = (Set) getPrivateCreds( sub, PasswordCredential.class );
    Iterator it = s.iterator();

    while ( it.hasNext() )
    {
       PasswordCredential cred = (PasswordCredential) it.next();
       if ( fRealm == null || cred.target().equals( fRealm ) )
         return cred;
    }

    return null;
  }

  protected PasswordCredential getCredentialFromUser( CallbackHandler cbHandler )
  throws LoginException
  {
     if (cbHandler == null)
        throw new LoginException("Callback Handler is not set");

     /* Default implementation returns a UP based credential */
     try
     {
         if ( fRealm == null )
           throw new LoginException("No Realm given.");

         ArrayList callbackList = new ArrayList();
         callbackList.add( new TextOutputCallback(TextOutputCallback.INFORMATION,
                               "RBW Login Version 1.0" ) );
         callbackList.add( new TextOutputCallback(TextOutputCallback.INFORMATION,
                               "Logging into realm... " + fRealm ) );
         callbackList.add( new     NameCallback("Enter rbw username") );
         callbackList.add( new PasswordCallback("Enter rbw password",false) );
         Callback[]  callbacks =
        (Callback[]) callbackList.toArray( new Callback[callbackList.size()] );

         try { cbHandler.handle(callbacks); }
         catch (UnsupportedCallbackException ex)
         {
           throw new LoginException( "Callback Handler does not support callback " + ex.getCallback() );
         }
         catch (IOException ex)
         {
           throw new LoginException( "Callback Handler raised IOException:" + ex.toString() );
         }

         String username = null;
         char[] password = null;
         for (int i=0; i<callbacks.length; ++i)
         {
           if ( callbacks[i] instanceof     NameCallback )
           {
             username = (    (NameCallback) callbacks[i]).getName();
           }
           else
           if ( callbacks[i] instanceof PasswordCallback )
           {
             password = ((PasswordCallback) callbacks[i]).getPassword();
           }
         }

         PasswordCredential credential = new
         PasswordCredential( username, password, fRealm );

         /** clear the password **/
         if ( password != null ) Arrays.fill( password, '\0' );
         return credential;
     }
     catch(Throwable e)
     {
       e.printStackTrace();
       throw new LoginException( e.getMessage() );
     }
  }

  // initial state
  protected Subject         fSubject;
  protected CallbackHandler fCallbackHandler;
  protected Privileges      fPrivileges;

  // target for which local login is being performed
  protected GSSUPTarget fRealm = null;

  // the authentication status
  protected boolean fCommitted = false;

  // credential
  private   PasswordCredential fCred   = null;
  protected UserPrincipal      fPrinc  = null;
  protected List               fGroups = null;
}

Step 13: Test Servlet and EJB security using LDAP server

1) TBD
  *) TBD