Showing posts with label Digital Workplace. Show all posts
Showing posts with label Digital Workplace. Show all posts

Sunday, March 29, 2015

Anonymous access of SharePoint content in authenticated SharePoint context

Intranets, or better Digital Workplaces, provides employees with access to business information and functionalities. Typical the access is authorized, role-based and sometimes on individual level. In both cases it is requires that the intranet users are first authenticated to next check the authorization for the authenticated visitor. This is why intranets are typically authenticated, while external-facing sites have anonymous access enabled.
Webapplications utilize resources of diverse types for branding the site: css, images, and javascript code. Authorization on these items is most times unnecessary, as it is “code” and not (role) sensitive information. SharePoint-based portals often store these static resources as ‘content’ in a SharePoint style library. In Office 365 context it is the only possible approach to provision branding resources, deployment to the layouts folder requires access to the SharePoint farm.
SharePoint authentication handling adds some overhead to the basic execution of the http request. For instance in case of NTLM authentication, each http connection between client and SharePoint must negotiate the authentication state via the NTLM handshake, adding 2 requests (type 1 and type 2) before SharePoint responds on the actual client request. Although this overhead is in absolute value small on indivual request level, the impact can become noticable when the number of http requests increases.
So I considered it a good idea to enable anonymous access on the style library, and as such avoid this authentication-overhead for the static resources retrieved from SharePoint content database. The result is that direct retrieval of any resource stored in the style library does not involve authentication processing. However, to my initial surprise the authentication is still applied when the resource is retrieved in context of an authenticated SharePoint page. I compared the http requests send from the 2 different situations, and noticed that in the situation requested from authenticated SharePoint context the http request includes the NTLM authentication header. And in that case, even although the style library on itself does allow anonymous access, as the http request includes NTLM authentication information, IIS “honors” this and still sets up the NTLM authentication handshake with the browser.
Conclusion: anonymous access to SharePoint data is not possible whenever that data is retrieved in context of an authenticated SharePoint page on the same webserver domain. Successful anonymous access requires to break out of the authenticated SharePoint context. E.g. by storing the static resources in another webapplication and have the authenticated SharePoint page refer to the resources on that other webapplication and thus another domain for which the authentication cookie from the authenticated SharePoint page does not apply.

Monday, February 9, 2015

Pre-provision personalized page for new (first-time) users

A SharePoint customer is realizing the concept of Digital Workplace with SharePoint 2013 as platform foundation. Crucial element of this Digital Workplace is that the entry point is personalized, each employee can personalize the entry point [Dashboard] to own interests and work responsibilities. The user can select Apps from the company AppStore to place on the personalized home page.
To familiarize the employees with this new concept, at Go-Live the homepage will already be pre-provisioned for each individual user with a default set of Apps from the company AppStore.
The following aspects make this task somewhat challenging:
  1. Personalization of the home page requires to pre-provision the page impersonated as the individual users, and access the personalized page scope.
  2. The Digital Workplace is a new webapplication; the employees have not yet visited this webapplication. (SharePoint) Consequence is that the employee accounts are not yet administrated in the SharePoint user list, and thus no usertoken can be determined.
  3. It is not allowed to deploy a custom-build application in the SharePoint farm. This precludes the usage of a .Net console application for pre-provision execution.

Solution design

Construct a Powershell script that is feed with an user list, for each user access the home page impersonated as that user, and add the collection of Apps as ClientWebPart to the personalized webpartmanager scope.

Structure

Specialties

The users have not yet visited the webapplication, and thus are not administrated yet in the SharePoint sitecollection user list. Solution: use ‘ensureUser’.

Powershell script

$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'} if ($snapin -eq $null) { Add-PSSnapin "Microsoft.SharePoint.Powershell" } # # -- Initialization -- # # Environment specific settings # $siteUrl = "<site-url>" $ADDomain = "<AD-domain>" # Environment-transparant settings # $homePagePath = "/Pages/Home.aspx" $homePageUrl = "$siteUrl$homePagePath" $errorImportingWbMsg = "Error Importing WebPart" # Pre-Provision configuration value # $loc = Get-Location $Users = Get-Content "$loc\Config\Users.txt" $WebPartDefinitions = Get-Content "$loc\Config\WebPartDefinitions.txt" $webPartCatalogRoot = "$loc\WebPartFiles\" # Access the web as administrator # $web = Get-SPWeb -identity $siteUrl # # -- Functions -- # function AddWebPartToPage($limitedWebPartManager, $WebPartDefinition, $User) { $TitleInAppCatalog = $WebPartDefinition.Split("|")[0] $WebPartFile = $WebPartDefinition.Split("|")[1] $WebPartZoneId = $WebPartDefinition.Split("|")[2] # Initialize the XmlReaderSettings Object which is required for the # XmlReader Object $xmlReaderSettings = New-Object System.Xml.XmlReaderSettings # Create the XmlReader Object by using the WebPart Definition file # and the ReaderSettings Object $WebPartUrl = "$webPartCatalogRoot$WebPartFile" $xmlReader = [System.Xml.XmlReader]::Create($WebPartUrl, $xmlReaderSettings); # Get the WebPart Definition Object based on the webpart definition xml file $oWebPartDefinition = $limitedWebPartManager.ImportWebPart( $xmlReader,[ref]$errorImportingWbMsg); # Add the WebPart to the WebPartManager by specifing the Zone and the Index. $limitedWebPartManager.AddWebPart($oWebPartDefinition,$WebPartZoneId,1); $limitedWebPartManager.SaveChanges($oWebPartDefinition) } function PreProvisionForUser($User) { $impSite = New-Object Microsoft.SharePoint.SPSite( $web.url, $web.EnsureUser("$ADDomain\$User").userToken) $impWeb = $impSite.OpenWeb(); $page = $impWeb.GetFile($homePagePath) if ($page.Exists) { # Make the Web as AllowUnSafeUpdates as true $impWeb.AllowUnsafeUpdates = $true # Get the LimitedWebPartManager $limitedWebPartManager = $page.GetLimitedWebPartManager( [System.Web.UI.WebControls.WebParts.PersonalizationScope]::User) # Reset any personalization state (e.g. in case of repeated pre-provision) $limitedWebPartManager.ResetAllPersonalizationState() # Pre-provision the page by adding all the configured webparts foreach($WebPartDefinition in $WebPartDefinitions) { AddWebPartToPage $limitedWebPartManager $WebPartDefinition $User } # Revert the AllowUnsafeUpdates to False once we are done. $impWeb.AllowUnsafeUpdates = $false Write-Host "Pre-Provision done for user:" $User } $impWeb.Dispose(); $impSite.Dispose(); } #### EXECUTE ### foreach ($User in $Users) { PreProvisionForUser($User) }

Sunday, January 25, 2015

Flexible business process forms handling in SharePoint

SharePoint customers typically apply the product also as technology foundation to run their employee business applications on. SharePoint as platform contains much of the required infrastructure and functional capabilities to make this possible. And if your business requirements go beyond what SharePoint provides out-of-the-box, SharePoint as technology framework enables the business application developers to extend the product featuring with own custom developed features.
In this posting, I’ll outline the architecture and technical design for such a business process application delivered through SharePoint as the Digital Workplace context.

High-over functional specification

  1. Users must be able to self initiate an instance of the business process(es);
  2. Users must be able to select from a set of business processes;
  3. The selection of business processes that a user is allowed to start, is role based;
  4. A process consists of steps to execute, and data to input; both specific per process type;
  5. The user experience for all business process handling must be user-friendly, intuitive and responsive;
  6. The business process form collects user input to process and administrate in a business administration;
  7. The business process form retrieves master data from the business administration to initialize input-controls (e.g. dropdown menu, checkboxes) with predefined and fixed input values;
  8. The business process forms must validate user input, to minimize load and disruption on the business administration due functional incorrect input. The validations contain simple pattern validators (e.g. valid postal code, email address), composite validators (if field X selected, then field Y and Z are required), and business validations (e.g., employee of function group Y may not be transitioned to function group X);
  9. The user must be enabled to temporarily park an initiated process instance, to continue with it at a later moment (e.g. after lunch break, or a meeting).

Required functional management flexibility

  1. Functional management must themselves be able to manage the authorization on process types
  2. New process type(s) including forms must Go-Live without disruption of the landscape / no release moment

Architecture and Software Design

SharePoint custom solutions can be JavaScript-based executing in the browser, .Net code executed on the SharePoint server (farm-solutions or sandboxed), or a combination of the two. As of SharePoint 2013, the third and now the preferred deployment model is the SharePoint App-model.
In all the 3 deployment options, SharePoint can be interoperated for data administration and capabilities. It must be noted that for the client-model the exposed capabilities in SharePoint 2010 are limited to mostly data servicing, but SharePoint 2013 (on-premisse + Online / Office 365) opens up almost all of the server-side API for client-side invocation.
At the time this application was designed and developed, the involved customer-organization was (and still is) using SharePoint 2010. The App-model was therefore not possible. Yet, I aimed to setup the design as much prepared for the “loosely-coupled SharePoint architecture” that is now advocated for custom SharePoint solutions.
Applied design decisions:
  • Administrate the process specifications in a SharePoint list
    • The out-of-the-box SharePoint forms enable functional management to add, edit and delete process type specifications
    • For role-based access, apply granulair SharePoint permissions on individual list item == process type specification
    • Standard SharePoint REST service listdata.svc for clientside quering the process specifications administration
  • Provide the process forms as HTML5/Javascript/CSS mini-applications, and use knockout.js for bi-directional data binding (Angular.js is another valid option).
  • Administrate the process forms as HTML files in SharePoint document library
    • The out-of-the-box SharePoint forms enable functional management to add, edit and delete process forms snippets
    • Standard SharePoint as document/file administration for clientside on-the-fly retrieve (http-get) the process forms snippets
  • Templated html that contains placeholders to dynamically fill with relevant html
    • At application start, bind a selection menu to the retrieved processes data collection
    • On user-selection of a process, get the process specific html and insert within the page rendering
  • Administrate the process Models and ViewModels as javascript files in document library
    • Standard SharePoint forms available enables functional management to add, edit and delete process forms snippets
    • Standard SharePoint as document/file administration for clientside on-the-fly retrieve (http-get) the javascript resources
  • Utilize javascript validator library; and validate the user input per formwizard step, on the moment the user wants to navigate to next step in the wizard.
  • Utilize SharePoint BCS to interoperate from SharePoint context to the external business administrations; for data processing and business validations.
  • Utilize webapi to expose a Javascript callable gateway into SharePoint BCS invocation.

Software and system components

  • SharePoint
    • List
    • Document Library
    • Listdata.svc
    • Business Connectivity Services (BCS)
    • webapi
  • Knockout.js
  • jQuery + jQuery.UI
  • HTML snippets
  • Javascript Model contracts
  • JSON.net
  • SAP, Oracle, …

Code snippets

<div id="HPW-Proces" data-bind="validationOptions: { insertMessages: false }" style="display:none;"> <div id="dialog-form-container"> <div id="processes-selection" data-bind="css: { hideElement: $root.selectedProcess() != null }"> <ul data-bind="foreach: processes"> <li data-bind="click: $root.selectedProcess"> <h3 data-bind="text: name"></h3> <a href="#" data-bind="text: description"></a> </li> </ul> </div> <div id="process-form" data-bind="with: $root.selectedProcess, css: { hideElement: $root.selectedProcess() == null }"> <ul class="nav-tabs" data-bind="foreach: tabs"> … </ul> <div class="tab-content-panel"> <div class="tab-content" data-bind="foreach: tabs"> <p class="page-title" data-bind="text: name, css: { hideElement: isSelected() !== true }"></p> <div data-bind="template: { name: 'Step' + ($index() + 1), data: $parent, afterRender: $root.initializeControls }, css: { hideElement: isSelected() !== true }"></div> </div> </div> <div class="btnRow"> …. </div> </div> </div>
EnsureScriptFunc("sp.js", "SP.ClientContext", function () { var ctx = new SP.ClientContext(); var site = ctx.get_site(); ctx.load(site, 'Url'); ctx.executeQueryAsync(function (s, a) { var siteUrl = site.get_url(); var hpwProcessUI = document.getElementById('HPW-Proces'); ko.applyBindings(new HPW.AppViewModel.ProcessesVM(siteUrl), viewProcessUI); bindingContext = ko.contextFor(hpwProcessUI); $("#HPW-Proces").show(); }); });
var HPW = window.HPW || {}; HPW.Models = function () { /* This Model Provider will dynamically determine for which process the model data must be retrieved. */ var ModelProvider = { GetModel: function (id) { return (function (id) { var model = null; switch (id) { case "PR01": model = HPW.Models.PR01.Model(); break; case "PR02": model = HPW.Models.PR02.Model(); break; … } return model; })(id); } } // Public interface return { ModelProvider: ModelProvider } }();
HPW.AppViewModel = function () { function ProcessesVM(siteUrl) { var self = this; self.siteUrl = siteUrl; //Provide selectable process models. self.processes = ko.observableArray(); var processenConfigUrl = siteUrl + "/_vti_bin/ListData.svc/HPWProcessesConfiguration?@select=Title,Name,Description,DialogUrl&@orderby=Title"; $.getJSON(processenConfigUrl, function (data) { $.each(data.d.results, function (i, result) { self.processes.push(new HPW.ProcessViewModel(result.ProcessId, result.Name, result.Description, result.DialogUrl)); }); }).fail(HPW.Utilities.ErrorHandler); //Watch selected process. self.selectedProcess = ko.observable(null); //Dynamically load correct data model. self.selectedProcess.subscribe(function (selectedProcessViewModel) { var dialogHtml = selectedProcessViewModel.dialogUrl; $.get(dialogHtml, function (data) { var x = document.getElementById('ProcessTabs'); $(x).html(data); self.selectedProcess().model(new HPW.Models.ModelProvider.GetModel(selectedProcessViewModel.id)); var instanceId = HPW.Utilities.getParameterByName('instanceId'); if (instanceId != null && instanceId != "") { var viewModel = self.selectedProcess().model(); viewModel.loadState(instanceId); } }); });
"use strict"; var HPW = window.HPW || {}; HPW.Models.PR01 = function () { var ViewModel_ProcessX = function () { var self = this; /* VIEWMODEL */ self.steps = [ …]; return { steps: steps; …. } } return { Model: ViewModel_ProcessX } }();