Citrix Storefront - Adventures in customization - Dynamically configure features based on group membership

11 May 2017

As per my previous posts, dynamically configure Workspace Control and Authentication based on group membership, this post will put them all together and provide a little GUI love to enable showing your options. These two features need to be enabled and configured at different times. The authentication choice needs to be in place before you logon. Workspace Control is configured upon logon.

In order to ‘cheat’, authentication actually uses Windows authentication from your browser to the server-side script. This way we can get your account and query your authentication feature group membership. This won’t work with non-domain machines or browsers that don’t support Windows authentication (Safari?). But this is OK. For those caveats, we just redirect those users to the explicit logon page anyways, since their user account won’t be domain-aware.

If you get explicit logon, and then log in, we query the users entitlements for Workspace Control and configure accordingly. This allows a much more dynamic method than the authentication feature. By doing Workspace Control configuration after logon we can configure the settings to whichever user you use to logon. So if my account is Explicit Logon, Workspace Control Disabled that’s what I get after the Storefront logon screen. However, if I logon with one of my test accounts that has Workspace Control Enabled then it’s enabled when I hit the app list. Even though my local user account and my Storefront logon are different I’ll get the features of the user that logged in to Storefront. I think that’s pretty damn neat.

Ok, so let’s get to my finished product.

I edited the custom\style.css file and added the following:

#customTop {
background:grey;
color: ghostwhite;
text-align: right;
font-size: 12px;
background-color: #968989;
}

And my completed ‘custom\script.js’ file:

// Edit this file to add your customized JavaScript or load additional JavaScript files.

function getCookie(name) {
	var results = document.cookie.match('(^|;) ?' + name + '=([^;]*)');
	return results ? unescape(results[2]) : null;
}
  
/* 
============================================================================================
\\\\\\\\\\\\\\\\\Dynamic Authentication Based On Group Membership\\\\\\\\\\\\\\\\\\\\\\\\\\\
============================================================================================
*/
  
var url = "../../AHS-GroupMembership.aspx";
var displayedOptions = [];
ajaxWrapper({ url: url, type: "GET", dataType: 'text', async:false, success: explicitLogonCheck, error: explicitLogonCheck('Unknown') });

function checkDisplayedOptionsContent() {
	if (displayedOptions.length >= 1) {
		CTXS.trace ("Attempted authentication at least once. This usually completes on the second attempt. Removing first result...");
		displayedOptions.pop();
	}
}

function explicitLogonCheck(data) {
	
	switch(data) {
    case "PassThrough":
		checkDisplayedOptionsContent();
		displayedOptions.push("Pass-through");
        CTXS.trace ("PassThrough authentication detected");
		CTXS.Extensions.excludeAuthenticationMethod = function(authenticationMethod) {
			CTXS.trace("excluding auth:" + authenticationMethod + ":" + (authenticationMethod == CTXS.Authentication.Method.EXPLICIT));
			return (authenticationMethod == CTXS.Authentication.Method.EXPLICIT);
		};
		CTXS.Extensions.includeAuthenticationMethod = function(authenticationMethod) {
			CTXS.trace("including auth:" + authenticationMethod + ":" + (authenticationMethod == CTXS.Authentication.Method.PASSTHROUGH));
			return (authenticationMethod == CTXS.Authentication.Method.PASSTHROUGH);
		};
        break;
    case "ExplicitLogon":
		checkDisplayedOptionsContent();
		displayedOptions.push("Explicit logon");
        CTXS.trace ("ExplicitLogon user group detected.");
		CTXS.Extensions.excludeAuthenticationMethod = function(authenticationMethod) {
			CTXS.trace("excluding auth:" + authenticationMethod + ":" + (authenticationMethod == CTXS.Authentication.Method.PASSTHROUGH));
			return (authenticationMethod == CTXS.Authentication.Method.PASSTHROUGH);
		};
		CTXS.Extensions.includeAuthenticationMethod = function(authenticationMethod) {
			CTXS.trace("including auth:" + authenticationMethod + ":" + (authenticationMethod == CTXS.Authentication.Method.EXPLICIT));
			return (authenticationMethod == CTXS.Authentication.Method.EXPLICIT);
		};
        break;
    case "Unknown":
		displayedOptions.push("Defaulted to Explicit logon");
        CTXS.trace ("Attempting to authenticate.  If we cannot we will default to ExplicitLogon.");
		CTXS.Extensions.excludeAuthenticationMethod = function(authenticationMethod) {
			CTXS.trace("excluding auth:" + authenticationMethod + ":" + (authenticationMethod == CTXS.Authentication.Method.PASSTHROUGH));
			return (authenticationMethod == CTXS.Authentication.Method.PASSTHROUGH);
		};
		CTXS.Extensions.includeAuthenticationMethod = function(authenticationMethod) {
			CTXS.trace("including auth:" + authenticationMethod + ":" + (authenticationMethod == CTXS.Authentication.Method.EXPLICIT));
			return (authenticationMethod == CTXS.Authentication.Method.EXPLICIT);
		};
        break;
    default:
        CTXS.trace ("No possible authentication methods detected.");
	}
}
/* 
============================================================================================
/////////////////Dynamic Authentication Based On Group Membership///////////////////////////
============================================================================================
*/



/* 
============================================================================================
\\\\\\\\\\\\\\\\\\\\\\\\\\\Dynamic Workspace Control feature\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
============================================================================================
*/

function getUsername () {
	CTXS.trace("getUsername");
	// See https://citrix.github.io/storefront-sdk/requests/
	/*
	See Get User Name
	*/

	var usernameURL = CTXS.Config.getConfigValue("authManager.getUsernameURL");
	var result = "";
	ajaxWrapper({ type: "POST", async: false, url: usernameURL, dataType: "text", success: function (data) {
			CTXS.trace("getUsername data:" + data);
			result = data;
		}
	});
	return result;
}

function workspaceControlEnabled(username) {
	//call custom web service which queries LDAP for group membership and returns true for the "EnableWorkspaceControl" group, or false for the "DisableWorkspaceControl" group
	//if the user is not in either group the default is 'true'
	CTXS.trace("workspaceControlEnabled");
	var result = "";
	
		$.ajax({
		type: "GET",
		url: "../../Get-GroupMembership?Displayname=" + username,
		dataType: "text",
		async: false,
		success: function (data) {
			CTXS.trace("workspaceControlEnabled data:" + data);
			result = data;
		}
	});
	return result;
}
	
function setWorkspaceControl (bool) {
	CTXS.trace("setWorkspaceControl:" + bool);
	if (bool == "true") {
		CTXS.trace("Enabling Workspace Control");
		displayedOptions.push("Session persistence enabled");
		CTXS.Extensions.showWebReconnectMenu = function () {return true};
		CTXS.Extensions.showWebDisconnectMenu = function () {return true};
		CTXS.Extensions.webReconnectAtStartup = function () {return true};
		CTXS.Extensions.webLogoffIcaAction = function () {return "disconnect"};
	} else {
		CTXS.trace("Disabling Workspace Control");
		displayedOptions.push("Session persistence disabled");
		CTXS.Extensions.showWebReconnectMenu = function () {return false};
		CTXS.Extensions.showWebDisconnectMenu = function () {return false};
		CTXS.Extensions.webReconnectAtStartup = function () {return false};
		CTXS.Extensions.webLogoffIcaAction = function () {return "disconnect"};
	}
}

CTXS.Extensions.beforeDisplayHomeScreen = function (callback) {
	CTXS.trace("beforeDisplayHomeScreen stage");
	var username = getUsername();
	var WSC = workspaceControlEnabled(username);
	setWorkspaceControl(WSC);
	
	CTXS.trace("calling beforeDisplayHomeScreen callback");
	callback()
}
/* 
============================================================================================
///////////////////////////Dynamic Workspace Control feature////////////////////////////////
============================================================================================
*/

function ajaxWrapper(options) {
	var defaultOptions = {
		type: 'POST',
		dataType: 'json',
		traditional: true,
		beforeSend: function (jqXHR) {
			var csrfToken = getCookie('CsrfToken');
			if (csrfToken != null) {
				jqXHR.setRequestHeader("Csrf-Token", csrfToken);
			}
			var isUsingHttps = location.protocol.toLowerCase() == "https:" ? "Yes" : "No";
			jqXHR.setRequestHeader("X-Citrix-IsUsingHTTPS", isUsingHttps);
		},
		error: function () {
			CTXS.trace('Ajax error accessing URL: ' + options.url);
		}
	};

	options = $.extend({}, defaultOptions, options);
	$.ajax(options);
}

/* 
============================================================================================
                        Detected options feature bar
============================================================================================
*/
CTXS.Extensions.afterDisplayHomeScreen  = function () {
	CTXS.trace("afterDisplayHomeScreen stage");
	(function($) {
		var GUI = displayedOptions + "";
		$("#customTop").html("Detected: " + GUI + "   ");
	})(jQuery);
}

/* End of customization */

You’ll need to refer to my previous two posts for the Powershell HTTP LDAP listener and the Group-Membership.aspx file.

What does this look like for an end result?

 

And if you are a member of the various combinations of groups?

 

I have to admit, this was a LOT easier than I thought it would be. Storefront is really powerful and easy to use. My biggest complaint is the documentation is lacking on how to use the API’s but searching the Citrix support forums proved very fruitful (especially for how to use the authentication methods) for examples. Hopefully these blog articles will help someone else and further demonstrate the power, flexibility and extensibility of the Citrix Storefront product.