IAM April 29, 2020 hans No comments

Overriding browser language for WebSEAL pages

By default ISAM (IBM Security Access Manager) relies on the browsers accept-language header setting, for deciding which language to return certain management pages (e.g pkms* pages) and messages in.

Reference: The accept-language HTTP header

There are probably a number of ways to customize or develop a solution that takes into account a ‘user-selected’ language. (meaning: separate from Browser setting)

Just to name a few:

  • Delete all language folders / and implement a self-translated set of content (HTML page content, but also error message content) that is displayed based on a javascript/cookie approach
  • Develop a LRR (Reference: Local Response Redirection) solution, which could again be javascript/cookie based, or possibly even include language query string preferences

In this post I’ve opted for an approach where I make use of HTTP Transformation rules (Reference: HTTP Transformation) to rewrite the browser requested accept-language header.

Depending on the language (or languages) selected, browsers will send a request that looks like this.

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1

My goal is to change the en-US(,xx;xxx) value into a language value that matches an out-of-the-box supported language in ISAM. For this I’m using a basic HTTP Transformation rule that manipulates the incoming request. (The term ‘incoming’ here refers to the request arriving to WebSEAL – so having already passed certain network hops or devices)

<xsl:template match="//HTTPRequest/Headers">
<!-- Perform Header processing here. Output should be in the form <Header name="NAME" action="add|update|remove">VALUE</Header> if required. --> 

<xsl:variable name="langpref" select="//HTTPRequest/Cookies/Cookie[@name='LangPref']/node()" /> 

<Header action="update" name="accept-language"><xsl:value-of select="$langpref" />

</Header> </xsl:template>

Reference: mod-lang.xsl

What the above block of code does is perform modification of Headers of an incoming request (match="//HTTPRequest/Headers"), and is using the value of an incoming cookie called “LangPref” (match="//HTTPRequest/Cookie[@name='LangPref'")

In the Reverse Proxy configuration, link the HTTP Transformation Rule to a resource.

everything = /var/pdweb/shared/xslt/http-transformation/mod-lang

request-match = request:*

Reference: webseald-default.conf_APPEND

NOTE: We’re not making use of the POP (Protected Object Policy) method. The reason being that POP requires authentication/authorization to take place. This authz processing for each and every incoming request would likely mean a performance hit.
Moreover, it just makes sense to have a ‘system-wide’ change of language preference.

Thus far for the HTTP Transformation rule, now into the part where the end-user makes a selection of a language, and persists that preference in a Cookie.
In the spirit of something borrowed, I’ve modified my WebSEAL login.html page to include the following bit of code (in addition to the default html content).

    function onLoadPage() {

Where the added setLang(); is used to load the site language preference if there is already a stored cookie value retrieved in the initial request.

   function SetCookie() {
            var name = "LangPref"
            var langselect=document.getElementById("my-dropdown").value
            var expdate = new Date();
            expdate.setTime(expdate.getTime() + (30 * 24 * 60 * 60 * 1000)); // 30 days from now

            document.cookie = name + "=" + escape(langselect) +
            "; expires=" + expdate.toGMTString() + "" +
            "; path=" + "/" +
            "; domain=" + "" +
            "; secure="+"true";

The SetCookie() function probably could use a code tidy up (and not statically setting of path or domain. But the gist of it is in the setting of cookie (LangPref) value to the selection from the my-dropdown selector (see the <div> class="lang">)

   function setLang2(name){
            var nameEQ = name + "=";
            var ca = document.cookie.split(';');
            for(var i = 0; i < ca.length; i++) {
                 var c = ca[i];
                 while (c.charAt(0) == ' ') c = c.substring(1, c.length);
                 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
            return null;

The setLang2() is called by the setLang() to client-side (browser) check for the cookie, and return the language value.

       function setLang(name){
                var LangSelect = document.getElementById('my-dropdown');
                var selectedLang = setLang2('LangPref');
                LangSelect.value = selectedLang;

And setLang() then populates the my-dropdown selector with the incoming language preference. (this is mostly just for visual accurateness) .

 <div class="lang">
        <select class="login-inputs" id="my-dropdown" name="my-dropdown">
            <option value="en">English</option>
            <option value="ar">Arabic</option>
            <option value="fr">French</option>
        <button class="submitButton button-1 ease-in-anim-fast" onclick="SetCookie()">
        Activate Language

The select tag is used to offer the language choice to the end-user browsing the resources (accessed via WebSEAL).
The thing to keep in mind here, are the different option value’s, that must match the language folders that exist on ISAM.

Reference: Language Support Overview

Automation is preferred, however for the sake of quick and dirty approach, I chose to export (Export Zip) the Management Root followed by deleting all language folders (C,ar,es,…) from the document root.
From the (exported) unzipped Management Root, delete all but the leftover languages (“C”, “ar”, “fr”) and modify the respective login.html files to include the javascript functions and language selector).
Which can then be imported (Import Zip) in the Management Root.

Once that modified (and re-zipped) file is imported (and the Reverse Proxy is restarted), it’s time to get testing.

Testing a GET / method is as easy as simply pointing the browser to the WebSEAL Listening Interface

And to test a Post / method, any login attempt to the login form will do.

Off course I immediately had the XSL mapping rule code functional from the start. (note: please understand sarcasm).
What I found useful is taking a pdweb.http.transformation trace (level 9 – similarly activated as the usual pdweb traces) and using the constructed XMLHTTPRequest with an XSL testing tool. (I like the Eclipse one)

2020-04-28-15:34:42.867+02:00I----- thread(3) trace.pdweb.http.transformation:9 /build/isam/src/i4w/pdweb/webseald/http/transformation/XMLHTTPRequest.cpp:132: XMLHTTPRequest::import constructed XMLHTTPRequest: <?xml version="1.0" encoding='UTF-8'?><HTTPRequest><Credential><Attributes></Attributes></Credential><RequestLine><Method>GET</Method><URI>/</URI><Version>HTTP/1.1</Version></RequestLine><Scheme>https</Scheme><Headers><Header name="accept">text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8</Header><Header name="accept-encoding">gzip, deflate, br</Header><Header name="accept-language">en-US,en;q=0.5</Header><Header name="connection">keep-alive</Header><Header name="host"></Header><Header name="user-agent">Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0</Header><Header name="cache-control">no-cache</Header><Header name="upgrade-insecure-requests">1</Header><Header name="pragma">no-cache</Header></Headers><Cookies><Cookie name="PD-S-SESSION-ID">1_2_0_IOslOQUJNLkni4A7vxXx9L84t6mss6mMwcmskCU8YVUprt1L</Cookie><Cookie name="LangPref">ar</Cookie></Cookies></HTTPRequest>

Leave a Reply

Your email address will not be published. Required fields are marked *