46 Configuring a Customized External Authentication Plug-in

You can store user security credentials in a repository other than Oracle Internet Directory—for example, a database or another LDAP directory—and use these credentials for user authentication to Oracle components. You do not need to store the credentials in Oracle Internet Directory and then worry about keeping them synchronized. Authenticating a user by way of credentials stored in an external repository is called external authentication.

This chapter contains these topics:

Note:

All references to Oracle Single Sign-On in this chapter refer to Oracle Single Sign-On 10g (10.1.4.3.0) or later.

46.1 Introduction to Configuring a Customized External Authentication Plug-in

Authentication that relies on security credentials stored in Oracle Internet Directory is called native authentication. When a user enters her security credentials, the directory server compares them with the credentials stored in Oracle Internet Directory. If the credentials match, then the directory server authenticates the user.

By contrast, authentication that relies on security credentials stored in a directory other than Oracle Internet Directory is called external authentication. When a user enters her security credentials, the directory server compares them with the credentials stored in the other directory. This is done by using:

  • A PL/SQL program that does the external authentication work

  • An external authentication plug-in that invokes this PL/SQL program

46.2 Installing, Configuring, and Enabling the External Authentication Plug-in

This example uses the PL/SQL program, oidexaup.sql. Section 46.4, "Creating the PL/SQL Package oidexaup.sql" describes this program. This package is used for installing the external authentication plug-in PL/SQL package. It contains:

  • Two plug-ins: namely, when_compare_replace and when_modify_replace

  • One utility function: namely, get_nickname

The integrated package is the plug-in package, OIDEXTAUTH. It can also serve as a template to modify according to the requirements of your deployment.

To install, configure, and enable the external authentication plug-in, follow these steps:

  1. Implement your standalone external authentication PL/SQL program. For example, if you want to authenticate users by using user names and passwords, then you should have a PL/SQL program which takes these two parameters.

    In our sample code, oidexaup.sql, auth_external is the program package name, and authenticate_user is the function that does the authentication. you must make sure that this standalone program is working properly before you move on to next steps.

  2. Integrate this standalone program into the plug-in modules.

  3. Load the plug-in package into database. In this example, we enter:

    sqlplus ods/odspwd @oidexaup.sql
    
  4. Register the plug-ins. Do this by creating and uploading an LDIF file that provides the directory server with the necessary information to invoke the plug-in.

  5. This example uses a file named oidexauth.ldif, which contains the following:

    dn: cn=whencompare,cn=plugin,cn=subconfigsubentry
    objectclass:orclPluginConfig
    objectclass:top
    orclpluginname:oidextauth
    orclplugintype:configuration
    orclplugintiming:when
    orclpluginldapoperation:ldapcompare
    orclpluginenable:1
    orclpluginversion:1.0.1
    orclPluginIsReplace:1
    cn:whencompare
    orclpluginsubscriberdnlist:dc=com;o=IMC,c=US
    orclpluginattributelist:userpassword
    orclpluginrequestgroup:$prgdn
    
    dn: cn=whenmodify,cn=plugin,cn=subconfigsubentry
    objectclass:orclPluginConfig
    objectclass:top
    orclpluginname:oidextauth
    orclplugintype:configuration
    orclplugintiming:when
    orclpluginldapoperation:ldapmodify
    orclpluginenable:1
    orclpluginversion:1.0.1
    orclPluginIsReplace:1
    cn:whenmodify
    orclpluginsubscriberdnlist:dc=com;o=IMC,c=US
    orclpluginattributelist:userpassword
    orclpluginrequestgroup:$prgdn
    

    In this file, we notify the directory server that, whenever there is an ldapcompare or ldapmodify request, there are two plug-ins to be invoked.

    We use orclpluginsubscriberdnlist:dc=com;o=IMC,c=US so that plug-ins are ONLY invoked if the target entry is under dc=com or o=IMC,c=US.

    Replace $prgdn with the plug-in request group DN. This is an optional, recommended security feature. For integrating with Oracle Single Sign-On, this value is a required field. Only members of the group entered can invoke the plug-ins. You may enter multiple groups. Use a semicolon to separate entries.

    The recommended defaults are: cn=OracleUserSecurityAdmins,cn=Groups,cn=OracleContext and cn=OracleDASAdminGroup,cn=Groups,cn=OracleContext,o=default_subscriber,dc=com. Note that the Oracle Single Sign-On server is a member of the first group. Also, be sure to replace o=default_subscriber with the correct value for your deployment environment.

    To add this file to the directory, enter the following:

    ldapadd -p portnum -h hostname -D cn=orcladmin -q -v \
            -f oidexauth.ldif
    

Now, everything should be ready. Use the ldapcompare command-line tool to verify that the plug-in and authentication program are working properly before you try to authenticate the user from Oracle Single Sign-On.

In our example, we also provide the plug-in code for externally modifying user password.

46.3 Debugging the External Authentication Plug-in

Turn on directory server plug-in to help you to examine the process and content of plug-ins.

To setup directory server plug-in debugging, execute the following command:

sqlplus ods  @$ORACLE_HOME/ldap/admin/oidspdsu.sql

To enable directory server plug-in debugging, execute the following command:

sqlplus ods  @$ORACLE_HOME/ldap/admin/oidspdon.sql

To disable directory server plug-in debugging, execute the following command:

sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdof.sql

To show directory server plug-in debugging messages, execute the following command:

sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdsh.sql

To delete directory server plug-in debugging messages, please execute the following command:

sqlplus ods @$ORACLE_HOME/ldap/admin/oidspdde.sql

46.4 Creating the PL/SQL Package oidexaup.sql

The script oidexaup.sql, as used in this example, contains the following:

CREATE OR REPLACE PACKAGE OIDEXTAUTH AS

  PROCEDURE when_compare_replace (ldapplugincontext IN  ODS.plugincontext,
                                  result            OUT INTEGER,
                                  dn                IN  VARCHAR2,
                                  attrname          IN  VARCHAR2,
                                  attrval           IN  VARCHAR2,
                                  rc                OUT INTEGER,
                                  errormsg          OUT VARCHAR2
                                  );
  
  PROCEDURE when_modify_replace (ldapplugincontext IN  ODS.plugincontext,
                                 dn                IN  VARCHAR2,
                                 mods              IN  ODS.modlist,
                                 rc                OUT INTEGER,
                                 errormsg          OUT VARCHAR2
                                 );
  
  FUNCTION get_nickname (dn         IN  VARCHAR2,
                         my_session IN  DBMS_LDAP.session)
    RETURN VARCHAR2;

END OIDEXTAUTH;
/

SHOW ERROR

CREATE OR REPLACE PACKAGE BODY OIDEXTAUTH AS

  -- We use this function to convert the dn to nickname.
  -- When OID server receives the ldapcompare request, it 
  -- only has the dn information. We need to use DBMS_LDAP_UTL
  -- package to find out the nickname attribute value of 
  -- the entry.

  FUNCTION get_nickname (dn         IN VARCHAR2,
                         my_session IN DBMS_LDAP.session)
    RETURN VARCHAR2
    IS
       my_pset_coll        DBMS_LDAP_UTL.PROPERTY_SET_COLLECTION;
       my_property_names   DBMS_LDAP.STRING_COLLECTION;
       my_property_values  DBMS_LDAP.STRING_COLLECTION;
       
       user_handle         DBMS_LDAP_UTL.HANDLE;
       user_id             VARCHAR2(2000);
       user_type           PLS_INTEGER;
       user_nickname       VARCHAR2(256) DEFAULT NULL;
       
       my_attrs            DBMS_LDAP.STRING_COLLECTION;
       retval              PLS_INTEGER;
       
  BEGIN
     plg_debug( '=== Beginning of get_nickname() === ');
     user_type     := DBMS_LDAP_UTL.TYPE_DN;
     user_id       := dn;
                
     retval := DBMS_LDAP_UTL.create_user_handle(user_handle, user_type, user_id);
                
     plg_debug('create_user_handle() Returns ' || To_char(retval));
     
     retval := DBMS_LDAP_UTL.get_user_properties(my_session,
                                                 user_handle,
                                                 my_attrs,
                                                 DBMS_LDAP_UTL.NICKNAME_PROPERTY,
                                                 my_pset_coll);
     
     plg_debug( 'get_user_properties() Returns ' || To_char(retval));
     
     IF my_pset_coll.COUNT > 0 THEN     
        FOR i IN my_pset_coll.first .. my_pset_coll.last LOOP      
           retval := DBMS_LDAP_UTL.get_property_names(my_pset_coll(i),
                                                      my_property_names);
           IF my_property_names.COUNT > 0 THEN      
              FOR j IN my_property_names.first .. my_property_names.last LOOP
                 retval := DBMS_LDAP_UTL.get_property_values(my_pset_coll(i),
                                                             my_property_names(j),
                                                             my_property_values);
                 IF my_property_values.COUNT > 0 THEN
                    FOR k IN my_property_values.FIRST..my_property_values.LAST LOOP
                       user_nickname := my_property_values(k);
                       plg_debug( 'user nickname = ' || user_nickname);
                    END LOOP;
                 END IF;
              END LOOP;
           END IF; -- IF my_property_names.count > 0
        END LOOP;  
     END IF; -- If my_pset_coll.count > 0
     
     plg_debug( 'got user_nickname: ' || user_nickname);

     -- Free my_properties
     IF my_pset_coll.count > 0 then
        DBMS_LDAP_UTL.free_propertyset_collection(my_pset_coll);
     END IF;
     
     DBMS_LDAP_UTL.free_handle(user_handle);
     
     RETURN user_nickname;
     
  EXCEPTION
     WHEN OTHERS THEN
        plg_debug('Exception in get_nickname. Error code is ' || to_char(sqlcode));
        plg_debug('   ' || Sqlerrm);
        RETURN NULL;
  END;


  PROCEDURE when_compare_replace (ldapplugincontext IN  ODS.plugincontext,
                                  result            OUT INTEGER,
                                  dn                IN  VARCHAR2,
                                  attrname          IN  VARCHAR2,
                                  attrval           IN  VARCHAR2,
                                  rc                OUT INTEGER,
                                  errormsg          OUT VARCHAR2
                                  )
    IS
       retval pls_integer;
       lresult BOOLEAN;
       
       my_session          DBMS_LDAP.session;
       my_property_names   DBMS_LDAP.STRING_COLLECTION;
       my_property_values  DBMS_LDAP.STRING_COLLECTION;
       my_attrs            DBMS_LDAP.STRING_COLLECTION;
       my_pset_coll        DBMS_LDAP_UTL.PROPERTY_SET_COLLECTION;       
       user_handle         DBMS_LDAP_UTL.HANDLE;
       
       user_id             VARCHAR2(2000);
       user_type           PLS_INTEGER;
       user_nickname       VARCHAR2(60);
       remote_dn           VARCHAR2(256);     
       
       i                   PLS_INTEGER;
       j                   PLS_INTEGER;
       k                   PLS_INTEGER;
       
  BEGIN   
     plg_debug( '=== Begin of WHEN-COMPARE-REPLACE plug-in');
     plg_debug( 'DN = ' || dn);
     plg_debug( 'Attr = ' || attrname);
     --plg_debug( 'Attrval = ' || attrval);
     
     DBMS_LDAP.USE_EXCEPTION := FALSE;
     errormsg := 'No error msg';
     rc := 0;
     
     -- converting dn to nickname
     my_session := LDAP_PLUGIN.init(ldapplugincontext);
     plg_debug( 'ldap_session =' || RAWTOHEX(SUBSTR(my_session,1,8)));
     
     retval := LDAP_PLUGIN.simple_bind_s(ldapplugincontext, my_session);
     plg_debug( 'simple_bind_res =' || TO_CHAR(retval));     
     
     user_nickname := get_nickname(dn, my_session);
     plg_debug( 'user_nickname =' || user_nickname);
     
     -- unbind from the directory  
     retval := DBMS_LDAP.unbind_s(my_session);
     plg_debug( 'unbind_res Returns ' || To_char(retval));

     IF (user_nickname IS NULL) THEN
        result := 32;
        errormsg := 'Can''t find the nickname';
        plg_debug( 'Can''t find the nickname');
        RETURN;
     END IF;   
     
     plg_debug( '=== Now go to extauth ');
     
     BEGIN                         
        retval := auth_external.authenticate_user(user_nickname, attrval);
        plg_debug( 'auth_external.authenticate_user() returns = ' || 'True');
        result := 6; -- compare result is TRUE
     EXCEPTION
        WHEN OTHERS THEN
           result := 5; -- compare result is FALSE
           plg_debug( 'auth_external.authenticate_user() returns = ' || 'False');
           RETURN;                 
     END;
     
     plg_debug( '=== End of WHEN-COMPARE-REPLACE plug-in');
  EXCEPTION
     WHEN OTHERS THEN
        rc := 1;
        errormsg := 'Exception: when_compare_replace plugin';
        plg_debug( 'EXCEPTION: ' || retval);
        plg_debug('Exception in when_compare. Error code is ' || to_char(sqlcode));
        plg_debug('   ' || Sqlerrm);
  END;
  

  PROCEDURE when_modify_replace (ldapplugincontext IN  ODS.plugincontext,
                                 dn                IN  VARCHAR2,
                                 mods              IN  ODS.modlist,
                                 rc                OUT INTEGER,
                                 errormsg          OUT VARCHAR2
                                 )
    IS
       retval pls_integer;
       lresult BOOLEAN;
       
       my_session          DBMS_LDAP.SESSION;
       my_property_names   DBMS_LDAP.STRING_COLLECTION;
       my_property_values  DBMS_LDAP.STRING_COLLECTION;
       my_attrs            DBMS_LDAP.STRING_COLLECTION;
       my_modval           DBMS_LDAP.BERVAL_COLLECTION;
       my_pset_coll        DBMS_LDAP_UTL.PROPERTY_SET_COLLECTION;
       user_handle         DBMS_LDAP_UTL.HANDLE;

       l_mod_array         RAW(32);
       user_id             VARCHAR2(2000);
       user_type           PLS_INTEGER;
       user_nickname       VARCHAR2(2000);
       old_passwd          VARCHAR2(60) DEFAULT NULL;
       new_passwd          VARCHAR2(60) DEFAULT NULL;
       remote_dn           VARCHAR2(256);                        
       
       i                   PLS_INTEGER;
       j                   PLS_INTEGER;
       k                   PLS_INTEGER;
       
  BEGIN
     plg_debug( '=== Begin of WHEN-MODIFY-REPLACE plug-in');
     DBMS_LDAP.USE_EXCEPTION := FALSE;
     user_type     := DBMS_LDAP_UTL.TYPE_DN;
     user_id       := dn;
     
     -- converting dn to nickname
     my_session := LDAP_PLUGIN.init(ldapplugincontext);
     plg_debug( 'ldap_session =' || RAWTOHEX(SUBSTR(my_session,1,8)));
     
     retval := LDAP_PLUGIN.simple_bind_s(ldapplugincontext, my_session);
     plg_debug( 'simple_bind_res =' || TO_CHAR(retval));
     
     user_nickname := get_nickname(dn, my_session);
     plg_debug( 'user_nickname =' || user_nickname);
     
     -- unbind from the directory  
     retval := DBMS_LDAP.unbind_s(my_session);
          
     FOR l_counter1 IN 1..mods.COUNT LOOP
        IF (mods(l_counter1).operation = 2) AND
          (mods(l_counter1).type = 'userpassword') THEN
           
           FOR l_counter2 IN 1..mods(l_counter1).vals.COUNT LOOP
              new_passwd := mods(l_counter1).vals(l_counter2).val;
           END LOOP;
        END IF;
        
        IF (mods(l_counter1).operation = 0) AND
          (mods(l_counter1).type = 'userpassword') THEN
           
           FOR l_counter2 IN 1..mods(l_counter1).vals.COUNT LOOP
              new_passwd := mods(l_counter1).vals(l_counter2).val;
           END LOOP;
        END IF;
        
        IF (mods(l_counter1).operation = 1) AND
          (mods(l_counter1).type = 'userpassword') THEN
           
           FOR l_counter2 IN 1..mods(l_counter1).vals.COUNT LOOP
              old_passwd := mods(l_counter1).vals(l_counter2).val;
           END LOOP;
        END IF;
     END LOOP;  
          
     IF new_passwd IS NOT NULL AND old_passwd IS NOT NULL THEN
       BEGIN
          auth_external.change_passwd(user_nickname, old_passwd, new_passwd);
       EXCEPTION
          WHEN OTHERS THEN
             rc := 1;
             plg_debug( 'auth_external.change_passwd() raised exception.');
             errormsg := 'auth_external.change_passwd() raised exception.';
             RETURN;
       END;
      ELSIF new_passwd IS NOT NULL AND old_passwd IS NULL THEN
       BEGIN
          auth_external.reset_passwd(user_nickname, new_passwd);
       EXCEPTION
          WHEN OTHERS THEN
             plg_debug( 'auth_external.reset_passwd() raised exception.');
             rc := 1;
             errormsg := 'auth_external.reset_passwd() raised exception.';
             RETURN;
       END;
      ELSE
             rc := 1;
             errormsg := 'PLG_Exception. Not enough info to change passwd.';
     END IF;

     plg_debug( 'external change password succeed');
     rc := 0;
     errormsg := 'No when_mod_replace plguin error msg';
     
     retval := DBMS_LDAP.unbind_s(my_session);
     
     plg_debug( 'End of WHEN-MODIFY-REPLACE');     
     --COMMIT;
  EXCEPTION
     WHEN others THEN
        rc := 1;
        errormsg := 'PLG_Exception: when_modify_replace plguin';
        plg_debug('Exception in when_modify. Error code is ' || to_char(sqlcode));
        plg_debug('   ' || Sqlerrm);    
  END;

END OIDEXTAUTH;
/
SHOW ERRORS
--list


EXIT;