Authentication and Authorization with MS Active Directory

The process of integrating a BI platform within a company infrastructure can be quite challenging.

The average medium-sized company usually uses several technologies to run their business, sometimes specific to departments and other times implemented across different functions. It is also a difficult task to manage all groups and users from different departments so you have to find a trade-off solution in order to facilitate any future maintenance tasks. Leveraging existing LDAP technologies within the company is a classical example.

This article, as a matter of fact, explains the integration of Active Directory with OBIEE 10.x (and previous versions) for Authentication and Authorization.

In a nutshell, the process of Authentication consists of logic used to recognize and check login and password of the user, while Authorization involves assigning permissions and privileges through Groups.

For example, if one user belongs either to the group of Accountants or to the EMEA Region, then this user has restricted ability. The user is authenticated in Active Directory, but we can use AD to pass the user groups to OBIEE as well. Then OBIEE matches the groups via Initialization Blocks in the administration tool and the web catalog to control permissions and privileges to reports, dashboards, data, etc.

As we all know, the integration between OBIEE and the Oracle-proprietary LDAP tool, Oracle Internet Directory (OID) is seamless and easy.

What about Microsoft Active Directory? Well, there are a few limitations to overcome. For instance, we can not use import, import all or synchronize users from OBIEE Admin Tool.

14

Figure 1 – User list in the Active Directory Administration console.

 

There are several possible configurations to implement Authentication and Authorization in OBI.

 

A common strategy is to do the authentication against an LDAP server and then the authorization with an external table providing the groups the user belongs to.

 

While in a previous ClearPeaks blog article we explain how to implement SSO Authentication using IIS, which is a Microsoft Windows based technology as well, this article will explain how to do both against an Active Directory server.

 

Windows based environments are widely spread and single sign-on is very convenient for users.

 

First, you have to set up an LDAP server in OBI and do the authentication through it with a session initialization block that populates the user session variable.

 

This is simple and it is well described in this blog article as well as in this one.

 

The second task is to setup a function in the database to query the AD and dynamically get the groups the user belongs to.

 

This is one of the biggest selling points for this strategy as it allows to implement a single administration point for all user groups that can eventually be reused by different systems.

 

2

Figure 2: Row-wise initialization query to be implemented in OBI to get all user groups.

 

Notice the function called getusergroup in the figure above.

 

In the rest of the article we will describe how to create and use it in an OBI session initialization block to get the GROUP system session variable.

 

For those familiar with the way OBI manages the Authorization strategy, GROUP is a reserved session variable that provides the groups that the user belongs to.

 

Using the above mentioned function, we will populate GROUP from the Active Directory.

3

Figure 3 – User Groups are managed in a single point: AD console

 

Now let’s describe how to set up the getusergroup function.

 

This function uses the DBMS_LDAP_UTL package, an Oracle-built tool to communicate to an LDAP directory from a database.

 

For non-Oracle database technologies, similar packages are either provided or need to be created.

 

Detailed information regarding the DBMS_LDAP_UTL package can be found in the following Oracle documentation:

 

DBMS_LDAP & DBMS_LDAP Sample

 

Although the function DBMS_LDAP_UTL.get_group_membership is usually used to get all user groups, it works with OID but not with the implementation of LDAP by Microsoft Active Directory, as it will simply throw a “get_group_membership returns: -3″ message.

 

Therefore it is advised to use the function DBMS_LDAP.search_s (search scope) which performs a search within the AD tree looking, in this case, only for the attributes we are interested in, for example:

 

login name = USER (OBIEE session variable) = sAMAccountName  and GROUP (OBIEE session variable) =  memberOf

 

Find below the details about the getusergroup function. Of the whole code, we are going to focus on the most interesting parts.

 

It receives an input parameter, username, varchar2 type, which takes the USER system session variable (coming from sAMAccountName from AD during the authentication process).

 

NOTE: sAMAccountName variable is created in AD during the authentication process, so it is not available before the user logs in.

 

— Customize the variables as needed

ldap_host     := ‘172.16.101.42’;

ldap_port     := 389;

ldap_user     := ‘CN=Administrator,CN=Users,DC=vmwscebs01,DC=clearpeaks,DC=local’;

ldap_passwd   := ‘password’;

ldap_base     := ‘DC=vmwscebs01,DC=clearpeaks,DC=local’;

— end of customizable settings

DBMS_OUTPUT.PUT_LINE(‘DBMS_LDAP Search Example to directory .. ‘);

DBMS_OUTPUT.PUT_LINE(RPAD(‘LDAP Host ‘,25,‘ ‘) || ‘: ‘ || ldap_host);

DBMS_OUTPUT.PUT_LINE(RPAD(‘LDAP Port ‘,25,‘ ‘) || ‘: ‘ || ldap_port);

— Choosing exceptions to be raised by DBMS_LDAP library.

DBMS_LDAP.USE_EXCEPTION := TRUE;

my_session := DBMS_LDAP.init(ldap_host,ldap_port);

DBMS_OUTPUT.PUT_LINE (RPAD(‘Ldap session ‘,25,‘ ‘) || ‘: ‘ ||

RAWTOHEX(SUBSTR(my_session,1,8)) || ‘ (returned from init)’);

— bind to the directory

retval := DBMS_LDAP.simple_bind_s(my_session,

ldap_user, ldap_passwd);

DBMS_OUTPUT.PUT_LINE(RPAD(‘simple_bind_s Returns ‘,25,‘ ‘) || ‘: ‘ || TO_CHAR(retval));

— issue the search

–my_attrs(1) := ‘*’; –‘*’ retrieve all attributes

my_attrs(1) := ‘sAMAccountName’;

my_attrs(2) := ‘memberOf’;

my_attrs(3) := ‘distinguishedName’;

retval := DBMS_LDAP.search_s(my_session, ldap_base,

DBMS_LDAP.SCOPE_SUBTREE,

‘(&(sAMAccountName=’ || username  ||‘)(objectclass=user))’,

my_attrs,

0,

my_message);

DBMS_OUTPUT.PUT_LINE(RPAD(‘search_s Returns ‘,25,‘ ‘) || ‘: ‘  || TO_CHAR(retval));

DBMS_OUTPUT.PUT_LINE (RPAD(‘LDAP message  ‘,25,‘ ‘) || ‘: ‘ ||

RAWTOHEX(SUBSTR(my_message,1,8)) || ‘ (returned from search_s)’);

— count the number of entries returned

retval := DBMS_LDAP.count_entries(my_session, my_message);

DBMS_OUTPUT.PUT_LINE(RPAD(‘Number of Entries ‘,25,‘ ‘) || ‘: ‘ || TO_CHAR(retval));

DBMS_OUTPUT.PUT_LINE(‘—————————————————‘);

–execute immediate ‘truncate table system.AD_GROUPS’ ;

— get the first entry

my_entry := DBMS_LDAP.first_entry(my_session, my_message);

entry_index := 1;

— Loop through each of the entries one by one

while my_entry IS NOT NULL loop

— print the current entry

my_dn := DBMS_LDAP.get_dn(my_session, my_entry);

— DBMS_OUTPUT.PUT_LINE (‘        entry #’ || TO_CHAR(entry_index) ||

—  ‘ entry ptr: ‘ || RAWTOHEX(SUBSTR(my_entry,1,8)));

–DBMS_OUTPUT.PUT_LINE (‘        dn: ‘ || my_dn);

my_attr_name := DBMS_LDAP.first_attribute(my_session,my_entry, my_ber_elmt);

attr_index := 1;

group_index := 1;

my_groups.delete();

groups_varchar2:=;

my_username:=;

my_fullname:=;

while my_attr_name IS NOT NULL loop

my_vals := DBMS_LDAP.get_values (my_session, my_entry,my_attr_name);

if my_vals.COUNT > 0 then

FOR i in my_vals.FIRST .. my_vals.LAST loop

if my_attr_name = ‘sAMAccountName’ then

my_username := SUBSTR(my_vals(i),1,200) ;

—  DBMS_OUTPUT.PUT_LINE(‘User: ‘|| my_username );

end if;

if my_attr_name = ‘distinguishedName’ then

my_fullname := substr(SUBSTR(my_vals(i),1,200),

4 ,instr(SUBSTR(my_vals(i),1,200) , ‘,’)-4 );

—  DBMS_OUTPUT.PUT_LINE(‘Full name: ‘|| my_fullname );

end if;

if my_attr_name = ‘memberOf’ then

my_groups(group_index):= substr(SUBSTR(my_vals(i),1,200),

4 ,instr(SUBSTR(my_vals(i),1,200) , ‘,’)-4 );

–DBMS_OUTPUT.PUT_LINE(‘Group: ‘|| my_groups(group_index) );

group_index := group_index+1;

end if;

–DBMS_OUTPUT.PUT_LINE(‘           ‘ || my_attr_name || ‘ : ‘ ||

SUBSTR(my_vals(i),1,200));

end loop;

end if;

my_attr_name := DBMS_LDAP.next_attribute(my_session,my_entry, my_ber_elmt);

attr_index := attr_index+1;

end loop;

DBMS_OUTPUT.PUT_LINE(‘Groups for this entry: ‘ || to_char(group_index 1) );

FOR i in my_groups.FIRST .. my_groups.LAST loop

PIPE ROW(my_groups(i));

–DBMS_OUTPUT.PUT_LINE(‘GROUP ‘ || my_groups(i));

groups_varchar2 := groups_varchar2 || my_groups(i) || ‘;’;

END LOOP;

groups_varchar2:=SUBSTR(groups_varchar2,1, (length(groups_varchar2)-1) );

–RETURN groups_varchar2;

DBMS_OUTPUT.PUT_LINE(‘GROUPS: ‘ || groups_varchar2 );

my_entry := DBMS_LDAP.next_entry(my_session, my_entry);

–insert into system.AD_GROUPS VALUES (my_username, groups_varchar2, my_fullname);

–commit;

–DBMS_OUTPUT.PUT_LINE(‘====================’);

entry_index := entry_index+1;

end loop;

— unbind from the directory

retval := DBMS_LDAP.unbind_s(my_session);

DBMS_OUTPUT.PUT_LINE(RPAD(‘unbind_res Returns ‘,25,‘ ‘) || ‘: ‘ ||

TO_CHAR(retval));

DBMS_OUTPUT.PUT_LINE(‘Directory operation Successful .. exiting’);

— Handle Exceptions

EXCEPTION

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(‘ Error code    : ‘ || TO_CHAR(SQLCODE));

DBMS_OUTPUT.PUT_LINE(‘ Error Message : ‘ || SQLERRM);

DBMS_OUTPUT.PUT_LINE(‘ Exception encountered .. exiting’);

 

This function is not optimized for a production environment and it will likely require some modifications.

Thus, there are some portions of code commented that you can uncomment to “debug” the code, and some others you should edit or comment.

In the code above, the function DBMS_LDAP.search_s is used with the parameter my_attrs which contains the attributes we are interested in.

Selecting all, it would return some leaves with attributes of the LDAP tree that risk producing errors by the DBMS package because of special characters.

It is advisable, then, to apply filters so to only select the objects that carry out both objectclass=user.

At the same time, the login name is passed as parameter and compared to sAMAccountName, used during the authentication by AD.

The next step is to compile the edited function, and check it is working.

Query your database with a select statement similar to the one below, depending on your function name, in order to get a similar result to Figure 2.

Clipboard01

Now it is time to go to the Administration Tool, and create two initialization blocks: one for the Authentication, which fills the system session variable USER, and another for the Authorization, which fills the system session variable GROUP.

In order to do that, as a best practice we suggest to create a specific connection pool, to be used uniquely for authorization.

The next step is to use the function in the Initialization blocks as per following instructions. Go to the Admin tool à Manage Variables.

1. Create a Session Variable Initialization block.

2. Edit the Data Source, Select Data Source Type: Database, Choose the Connection pool that you specifically created for the authorization.

3. Check Use database specific SQL, and then put in the text box the query above.

4

Figure 4 – Creating the Authorization Initialization block

Then go to Edit Data Target and check Row-wise initialization. This would convert the above SQL in the data source into a single row statement.

init.block.session.variable

Figure 5 – Setting the Row-wise initialization rule

You can Edit the execution precedence and select the Authentication initialization block, initUser in this example, which you created previously.

Check Required for authentication.

Finally, you should get to the window shown in Figure 6.

6

Figure 6 – the Session Variable Initialization block Dialog Box

If we press the Test button we will get the username we have used in the authentication of the tool, and it will be used by the GROUP session variable initialization block:

7

Figure 7 – Testing the variable

Clicking on the Ok button, the new initGroup Initialization block executes the query in the database against Active Directory and gets the groups with the user passed as a parameter.

8

Figure 8 – Results of the test

If the test is successful as per Figure 8, check in the Initialization block and save it.

The next step is to test the user authorization: log into Answers with any user you have defined in your Active Directory.

The purpose is to see all groups belonging to each logged user.

Create a simple report with any row (that you can hide), and a title with this content:

Clipboard02

You will get all the groups that the logged user belongs in the AD separated by semicolons ‘;’.

The final step, of course, is to implement any kind of security rule required across all levels in OBIEE (dashboards, presentation layer, data level, etc.) with the groups retrieved, which is straightforward and not the subject of this article.


admin
info@clearpeaks.com