NetScaler EULA Implementations

Customizations

I like to call the NetScaler "the swiss army knife of networking". The reason for this is not only its build-in capabilities, but the level of customization the device allows while pushing the limits.

Most of the time these customizations are functional, not just visually cosmetic appearances.

Although there are atleast a dozen articles showcasing customization of the NetScaler Portals, rarely the approach is universal.

I'll try to present a universal approach while tackling an EULA Customization Request.

EULA

Citrix Gateway and Portal themes have several options for adding an EULA (End User License agreement).

VPN EULA binding is practically decommissioned as it works only with directly assigned policies to the VPN vServer.

This should give you a good overview.

The Customization Request

A customer of ours had implemented an EULA based on the checkbox Link Article, but for one or another reason they needed a second EULA with second checkbox.

Have in mind that the Logon Button is grayed out, until the user accepts the EULA. Needless to say it should remain grayed out until both EULAs are accepted (when second link is added).

Implementing the second EULA

Second EULA message

The VPN vServer does not support second EULA binding, but we can read the EULA on file system level.

The EULA is stored under specific ID for each language on the following path: /var/netscaler/logon/themes/EULA/resources

Image

The ID of the EULA is provided by you in the LogonSchema for the script logic:

Image

This is how the Citrix script is doing in first place (eulaName variable):

var eulaTextNode = $(XMLHttpRequest.responseXML).find("String[id='"+ eulaName + "']");

Just add an ID and message to the language you want.

Makes you doubt if initial binding is needed (most probably not).

XML Schema

The Citrix article instructs you to add a line in your logonSchema.

This is to parse the ID of your EULA (eula1).

<Requirement><Credential><ID>custom-eula</ID><Type>custom-eula</Type><Input><Text><Hidden>true</Hidden><InitialValue>eula1</InitialValue></Text></Input></Credential></Requirement> 

You don't need to add second line for several reasons, but most importantly the Citrix function (on running) returns single XML Schema line based on the ID Tag in the XML Schema (being custom-eula Tag in our case).

getCredentialTypeName: function () { return "custom-eula"; },

We're going to add our Second EULA String ID (eulaName) next to our first, separated by a semicolon:

<InitialValue>eula1;eula2</InitialValue>

String ID = eulaName = XMl Schema InitialValue

Modify the script.js

Replace the script.js provided by Citrix in your portal theme with the one below:

The script will:

  • Add an html tag defining the second hyperlink
  • Disable the Logon Button until both EULAs are accepted
  • Select different EULA per String ID from the XML Schema
//Customization to support EULA on AAA login page
 function eula_success (responseData, textStatus, XMLHttpRequest) {
    if(XMLHttpRequest.responseXML){
		var euArr = eulaName.split(";")
        var eulaTextNode = $(XMLHttpRequest.responseXML).find("String[id='"+ euArr[0] + "']");
		var eulaTextNode2 = $(XMLHttpRequest.responseXML).find("String[id='"+ euArr[1] + "']");
    }
    if(eulaTextNode.length == 0)
        eulaTextNode.text("");      //Set blank EULA message.
	
	if(eulaTextNode2.length == 0)
        eulaTextNode2.text("");      //Set blank EULA2 message.
   
    var eulaAccept = $('<a href="#" id="nsg-eula-display" style="color:#02A1C1"></a>').text(_localize('terms #1'));
    eulaAccept.click(function (e) {
        e.preventDefault();
        CTXS.ExtensionAPI.showMessage({
                                    localize: true, // if true try to localize messages
                                    messageText: eulaTextNode.text(), // Text of message
                                    messageTitle: "End_User_License_Agreement", // Title of message (optional)
                                    okButtonText: "OK", // Text for ok button (optional)
                                    isalert: false, // if true show an alert style message (optional)
                                    okAction: function() {}, // action to do if ok is clicked (optional)
                                });
    });
 
    var eulaChecbox = $('<div><input type="checkbox" id="nsg-eula-accept" name="nsg-eula-accept" tabindex="0"></div>');
    if(LargeUI)
        eulaChecbox.attr('style','width:385px');
    var eulaLabel = $('<label for="nsg-eula-accept"></label>').text(_localize('eula_agreement'));
    eulaLabel.append(eulaAccept);
    eulaChecbox.append(eulaLabel);
	

    var eulaAccept2 = $('<a href="#" id="nsg-eula-display" style="color:#02A1C1"></a>').text(_localize('terms #2'));
    eulaAccept2.click(function (e) {
        e.preventDefault();
        CTXS.ExtensionAPI.showMessage({
                                    localize: true, // if true try to localize messages
                                    messageText: eulaTextNode2.text(), // Text of message
                                    messageTitle: "End_User_License_Agreement", // Title of message (optional)
                                    okButtonText: "OK", // Text for ok button (optional)
                                    isalert: false, // if true show an alert style message (optional)
                                    okAction: function() {}, // action to do if ok is clicked (optional)
                                });
    });
 
    var eulaChecbox2 = $('<div><input type="checkbox" id="nsg-eula-accept2" name="nsg-eula-accept2" tabindex="0"></div>');
    if(LargeUI)
        eulaChecbox2.attr('style','width:385px');
    var eulaLabel2 = $('<label for="nsg-eula-accept2"></label>').text(_localize('eula_agreement'));
    eulaLabel2.append(eulaAccept2);
    eulaChecbox2.append(eulaLabel2);
	
	var eulaChecboxAll = eulaChecbox.append(eulaChecbox2)
    eulaDOM = eulaChecboxAll;
}
 
CTXS.ExtensionAPI.addCustomCredentialHandler({
                    // Name of credential returned in form from server
					

					getCredentialTypeName: function () { 
					return "custom-eula";
					},
					
					
                    getCredentialTypeMarkup: function (requirements) {
                        eulaDOM = "";
						
                        if(requirements.input.text.initialValue != "") {
                            eulaName = requirements.input.text.initialValue;
                               
                                var language = $.globalization.currentCulture().name;
                                if(language == "") {
                                    language = "en";
                                }
                                $.ctxsAjax({
                                    store:null,
                                    type: 'GET',
                                    url: "/logon/themes/EULA/resources/"+ language +".xml",
                                    dataType: 'xml',
                                    async: false,
                                suppressEvents: true,
                                    success: eula_success,
                                    error: function(responseData, textStatus, XMLHttpRequest) {
                                    },
                                    refreshSession: true
                                });
                           
                        }
                        function disableFormsButton($button) {
                        // Disable the button and stop it appearing clickable
                        $button.addClass('disabled').removeAttr('href');
                    }
 
                    function enableFormsButton($button) {
                        // Enable the button and make it clickable again
                        $button.removeClass('disabled').attr('href', '#');
                    }
                    $(document).on('ctxsformsauthenticationdisplayform', function (e, data) {
                        var $form = data.authenticationElement.find('.credentialform'),
                        $eulaAcceptCheckbox = $form.find('#nsg-eula-accept'),
						$eulaAcceptCheckbox2 = $form.find('#nsg-eula-accept2'),
                        $loginButton = $form.find('#loginBtn');
						var box1 = false; 
						var box2 = false;
                        if ($eulaAcceptCheckbox.length && $loginButton.length) {
                          // This is a EULA form
                          // Disable the submit button by default
                          disableFormsButton($loginButton);
                          // Add the custom behaviour for the checkbox
                          $eulaAcceptCheckbox.on('click', function () {
                            if (this.checked) {
								box1 = true;
							}
							else {
								box1 = false;
                            }
							if (box1 && box2) { 
								enableFormsButton($loginButton); 
							} else {
								disableFormsButton($loginButton); }
							
                          });
						  
						  
						  $eulaAcceptCheckbox2.on('click', function () {
							if (this.checked) {
								box2 = true;
							} 
							else {
								box2 = false;
                            }
							if (box1 && box2) { 
								enableFormsButton($loginButton); 
							} else { 
								disableFormsButton($loginButton); }
							
                          });
                      }
                   
                  });
                    return eulaDOM;
                }
			}			
			
			);
 
/*
*** END OF EULA SCRIPT ****
*/

Main advantage of using the script instead of hardcoding the EULA message inside of it or in nFactor is the langauge selection.

This would be the result :

Image

(the terms, terms #1, potayto, potahto)

Testing Notes

  • Always Perform backup of your existing config

  • Always implement first on a TST vServer.

  • Disable browser cache

  • Clear NetScaler static Object caching

flush cache contentgroup loginstaticobjects

A Simplified version

I have realized this comes handy not only for EULAs, Terms and conditions, etc.

Below you can find a simplified version with informational links:

Image

Your text will be visualized like below, based on browser language and clicked link:

Image
//Customization to support EULA on AAA login page
 function eula_success (responseData, textStatus, XMLHttpRequest) {
    if(XMLHttpRequest.responseXML){
		var euArr = eulaName.split(";")
        var eulaTextNode = $(XMLHttpRequest.responseXML).find("String[id='"+ euArr[0] + "']");
		var eulaTextNode2 = $(XMLHttpRequest.responseXML).find("String[id='"+ euArr[1] + "']");
    }
    if(eulaTextNode.length == 0)
        eulaTextNode.text("");      //Set blank EULA message.
	
	if(eulaTextNode2.length == 0)
        eulaTextNode2.text("");      //Set blank EULA2 message.
   
    var eulaAccept = $('<a href="#" id="nsg-eula-display" style="color:#02A1C1"></a>').text(_localize('Impressum'));
    eulaAccept.click(function (e) {
        e.preventDefault();
        CTXS.ExtensionAPI.showMessage({
                                    localize: true, // if true try to localize messages
                                    messageText: eulaTextNode.text(), // Text of message
                                    messageTitle: "End_User_License_Agreement", // Title of message (optional)
                                    okButtonText: "OK", // Text for ok button (optional)
                                    isalert: false, // if true show an alert style message (optional)
                                    okAction: function() {}, // action to do if ok is clicked (optional)
                                });
    });
	
	var eulaLink = $('<div><tabindex="0"></div>');
	if(LargeUI)
        eulaAccept.attr('style','width:385px; color:#FFFFFF; font-size: 17px');
	eulaLink.append(eulaAccept);

    var eulaAccept2 = $('<a href="#" id="nsg-eula-display" style="color:#02A1C1"></a>').text(_localize('Data Privacy'));
    eulaAccept2.click(function (e) {
        e.preventDefault();
        CTXS.ExtensionAPI.showMessage({
                                    localize: true, // if true try to localize messages
                                    messageText: eulaTextNode2.text(), // Text of message
                                    messageTitle: "End_User_License_Agreement", // Title of message (optional)
                                    okButtonText: "OK", // Text for ok button (optional)
                                    isalert: false, // if true show an alert style message (optional)
                                    okAction: function() {}, // action to do if ok is clicked (optional)
                                });
    });
	
	var eulaLink2 = $('<div><tabindex="0"></div>');
	if(LargeUI)
        eulaAccept2.attr('style','width:385px; color:#FFFFFF; font-size: 17px');
	eulaLink2.append(eulaAccept2);
	
	
	var eulaAcceptAll = eulaLink.append(eulaLink2)
    eulaDOM = eulaAcceptAll;
}
 
CTXS.ExtensionAPI.addCustomCredentialHandler({
                    // Name of credential returned in form from server
					

					getCredentialTypeName: function () { 
					return "custom-eula";
					},
					
					
                    getCredentialTypeMarkup: function (requirements) {
                        eulaDOM = "";
						
                        if(requirements.input.text.initialValue != "") {
                            eulaName = requirements.input.text.initialValue;
                               
                                var language = $.globalization.currentCulture().name;
                                if(language == "") {
                                    language = "en";
                                }
                                $.ctxsAjax({
                                    store:null,
                                    type: 'GET',
                                    url: "/logon/themes/EULA/resources/"+ language +".xml",
                                    dataType: 'xml',
                                    async: false,
                                suppressEvents: true,
                                    success: eula_success,
                                    error: function(responseData, textStatus, XMLHttpRequest) {
                                    },
                                    refreshSession: true
                                });
                           
                        }
                       
 
                    function enableFormsButton($button) {
                        // Enable the button and make it clickable again
                        $button.removeClass('disabled').attr('href', '#');
                    }
                    $(document).on('ctxsformsauthenticationdisplayform', function (e, data) {
                        var $form = data.authenticationElement.find('.credentialform'),
                        $loginButton = $form.find('#loginBtn');
                   
                  });
                    return eulaDOM;
                }
			}			
			
			);
 
/*
*** END OF EULA SCRIPT ****
*/

You can also modify the different parts of the message like title, here in the code:

        CTXS.ExtensionAPI.showMessage({
                                    localize: true, // if true try to localize messages
                                    messageText: eulaTextNode2.text(), // Text of message
                                    messageTitle: "End_User_License_Agreement", // Title of message (optional)
                                    okButtonText: "OK", // Text for ok button (optional)
                                    isalert: false, // if true show an alert style message (optional)
                                    okAction: function() {}, // action to do if ok is clicked (optional)
                                });

You can modify the width of the message via the css (in case you have a large text to display).

CSS Location: /var/netscaler/logon/themes/CUSTOM_THEME_NAME/css/

.largeTiles .messageBoxPopup, .largeTiles .aboutBox {
 width: 600px;
 padding: 40px;
 color :#333333;
}
Image

The Logic

Citrix has provided an API for extending functionality. Meaning they'll load their functions where you've defined a logic or functionality with javascript code. Have in mind that Citrix function definitions might be server side, but the script is still executed on client side.

Authentication requirements are returned from the server as an XML document (parsed as a javascript object) and the content extracted from it for an ID or credential (from the LoginSchema), which is passed as a parameter to the next function NetScaler is going to call - "getCredentialTypeMarkup".

(This is done under the hood of the Citrix API)

Take it as getCredentialTypeName is your "IF" Statement in your logic.

CTXS.ExtensionAPI.addCustomCredentialHandler({


// The name of the credential, must match the type returned by the server
getCredentialTypeName: function () { return "custom-eula"; },


// Rendering instructions for the credential
getCredentialTypeMarkup: function (requirements) {
}

// Rendering instructions go here
return eulaDOM;

});

The function "getCredentialTypeMarkup" can extract further a given label from the credential such as initialValue. Which is the EULA Name or String ID in the language file.

(Any custom code will go in the "getCredentialTypeMarkup" function)

eulaName = requirements.input.text.initialValue;

Further we can make a language specific GET Request to obtain EULAs for that language:

 $.ctxsAjax({
                                    store:null,
                                    type: 'GET',
                                    url: "/logon/themes/EULA/resources/"+ language +".xml",
                                    dataType: 'xml',
                                    async: false,
                                suppressEvents: true,
                                    success: eula_success,
                                    error: function(responseData, textStatus, XMLHttpRequest) {
                                    },
                                    refreshSession: true
                                });

In addition, you can call external javascript functions from inside the "getCredentialTypeMarkup" that aren't defined & loaded by the Citrix API Hanlder (such as eula_success): There we can split the two EULA Names from the initial values, define the checkboxes and return the change in the elements of the HTML document.

 function eula_success (responseData, textStatus, XMLHttpRequest) {
    if(XMLHttpRequest.responseXML){
		var euArr = eulaName.split(";")
        var eulaTextNode = $(XMLHttpRequest.responseXML).find("String[id='"+ euArr[0] + "']");
		var eulaTextNode2 = $(XMLHttpRequest.responseXML).find("String[id='"+ euArr[1] + "']");
    }
	
... 
...
...

eulaDOM = eulaChecboxAll;

I hope this has been informative to you and I'd like to thank you for reading!