Friday, November 27, 2015

Limiting Add-in visibility in the corporate store

SharePoint Add-ins * are distributed through the App Catalog. This is a great method for end users to add functionality to their sites. Most apps you want to have available to as many users as possible, everybody should be able to add the local weather app, or the handy currency calculator to their sites.

However, some Add-ins should not be available to everyone. Think for example of Add-ins that allow for access into line of business applications. Users will probably not even be able to use the Add-in, if they are not able to access the LOB application, but you don't want them to be able to add the app to their sites in the first place.

I see five ways of preventing users from adding the app to their site. Spoiler alert; I think the fifth is the right way, so skip ahead if you are just looking for that :)


1. Sideloading the Add-in
Add-ins don't have to be distributed through the catalog. You can enable sideloading and just publish the Add-in to the target site directly. However, this is a development feature, meant for developers to quickly deploy and test their Add-ins. The catalog gives you a central place to push updates to the Add-in, and get error reports from the instances. Basically, you should not use developer tools to power a production scenario.

Still want to use sideloading? Look here for ways to enable this.


2. Remove the Add-in from the catalog
You could just quickly remove the Add-in from the catalog after you have deployed it to the right sites. However, you would have to add it again every time you want to add the Add-in somewhere else, and you lose the centralized error tracking. Furthermore, you would have to add it again to perform upgrades, which you then have to execute quickly on all instances, before pulling the Add-in again. All-in-all, quite cumbersome.


3. Check the location in the app installed event
If the app is a provider hosted app, you could execute the 'App Installing' event. This allows you to execute checks on for example the location the Add-in is being added, or the identity of the person doing the adding. If there is a mismatch, you can block the installation.

To use this method however, you need to be the developer of the Add-in. Furthermore, it requires you to either package the app with allowed locations or identities, or also create a location to manage this (the web.config of the provider hosted Add-in would be a logical place). Finally, this method doesn't prevent the user from seeing the Add-in in the catalog, just from adding it to the site.


4. Ask for extra permissions for the App
This might just be the easiest solution, but also the worst. Do not do this. :)

You could let your app just ask for extra permissions. For instance, tenant admin permissions. Remember, a user cannot install an Add-in with more permissions than the user has. So only tenant admins will be able to add an Add-in which requests tenant admin permissions.

However, if somebody manages to subvert the Add-in into doing something it shouldn't, it will be doing so with tenant admin permissions. This is bad and goes against the principle of least privilege.


5. Break permissions in the App Catalog
Still with us? This is the one I ended up using. You can use item level security in the App catalog. Just add the Add-in as you always would, but afterwards edit the security for the item:


 And stop inheriting:



Remember, the App Catalog is just a SharePoint library, so this is all possible with the OOTB tooling. Just remove the permissions to see the Add-in for anyone you don't want to let install the Add-in.

I like this way because it's easy, intuitive, and secure. It does not mess with updates or error reporting. However, I haven't been able to gather info anywhere on whether this is a supported scenario. Does Microsoft support breaking these permissions? Until I find somewhere that they don't, this is the way I'm leaning.



So, the fifth it is, according to me. But it is still not ideal. Do you disagree, or have another way? Please leave a comment below.

* note that I'm mention Add-in, instead of App. I seem to have finally accepted the change!

SharePoint remote event receivers returning 405 (Method not allowed)

The SharePoint app* infrastructure is a notably fickle beast. I've seen grown system administrators reduced to tears by obtuse error messages and exotic requirements which don't fit company infrastructure policies (wildcard certificates, no support for SSL offloading, multiple IP's or NICs etc etc.).

This week I was confronted by a new one. A provider hosted app we were deploying was unable to register remote event receivers (which we do from another remote event receiver triggered by the app installed event). The provider hosted app returned http error 405 (method not allowed). The relevant section of the ULS:

Error when get token for app i:0i.t|ms.sp.ext|xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx@xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, exception: Microsoft.SharePoint.SPException: The Azure Access Control service is unavailable.    
 at Microsoft.SharePoint.ApplicationServices.SPApplicationContext.GetApplicationSecurityTokenServicesUri(SPServiceContext serviceContext)    
 at Microsoft.SharePoint.ApplicationServices.SPApplicationContext..ctor(SPServiceContext serviceContext, SPIdentityContext userIdentity, OAuth2EndpointIdentity applicationEndPoint)     

Calling remote event receiver failed. URL = [https://xxxxxxxxxxxxxx.com/xxxx/Services/AppEventReceiver.svc], App Identifier = [i:0i.t|ms.sp.ext|xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx@xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx], Event Type = [AppInstalled], Exception = [The remote server returned an unexpected response: (405) Method Not Allowed.] 


The message that jumped at me directly is "The Azure Access Control service is unavailable". I'd hope so with my on premises SharePoint and provider hosted app deployment! This message however is a red herring and expected in an on premises configuration. I found multiple other possible reasons for a 405, including;
  • Certificate problems
  • SSL / TLS supported version mismatch between App server and SharePoint server
  • No handler in IIS for the incoming request

As the famous detective states; "when you have eliminated the impossible, whatever remains, however improbable, must be the truth". It turned out the App Server was missing a feature, specifically the ".Net Framework 4.5 > WCF Services > HTTP Activation" feature:



Adding this one allowed me to finally install the App. It's not clear why the feature was not installed, it was in the install scripts for the App server.


I hope if you're confronted by this 405 you'll find this post, and spend less time on it then I did.



* note the deliberate use of 'App' instead of 'Add-in'. It will require me some time to make the mental switch.

Sunday, July 12, 2015

Configuring metadata defaults with CSOM

SharePoint folders have had it rough the last couple of years. The internet is full of 'folders are evil' posts, but in my view folders still have their uses. One of the interesting features SharePoint folders bring to the table is automatically setting metadata. Per folder you can configure which default values fields should have. This can be a great enabler when people don't want to tag documents manually.

We wanted to use this in a recent project where we had the requirement to provision and configure this from code. The metadata defaults objects however are only available in the full trust object model, and we build for SharePoint online. That leaves us with the puzzle of what actually happens when you configure metadata defaults and figuring out if we can do the same from CSOM. We found that metadata defaults functionality is implemented through two components:

  • The 'client_LocationBasedDefaults.html' file in the forms folder of the document library
  • A feature receiver bound to the same document library


The following screenshots are from an environment that's been configured manually. We have our document library 'Metadata'. This library has two folders, 'Yes' and 'No'. A choice field with the same options is added to the library and has been configured to be set automatically for files that are added to the folders.


With SharePoint designer we can view the contents of the 'client_LocationBasedDefaults.html'. It's basically a mapping of the folder (url), the field to be set, and the field value to be set. All this in a very simple XML structure.

The event receiver can be spotted using the great tool SharePoint Manager 2013. It's interesting to note that the event receiver is part of the "Microsoft.Office.DocumentManagement" namespace, the same namespace that gives us the Document Set which is also great folder and metadata tool.

Well, this doesn't appear to be to hard to do through CSOM! First we need to create the metadata file. The hidden forms folder in the library has the interesting property that you can add files to it just as you would do to any other folder. The following snippet is all you need:
// Creating the LocationBasedDefaults file in the forms folder. Will overwrite if it is already there.
var formsFolder = ctx.Web.GetFolderByServerRelativeUrl("/Metadata/Forms/");
var fci = new FileCreationInformation();
fci.Content = Encoding.UTF8.GetBytes("<MetadataDefaults><a href=\"/Metadata/No\"><DefaultValue FieldName=\"YesNo\">No</DefaultValue></a><a href=\"/Metadata/Yes\"><DefaultValue FieldName=\"YesNo\">Yes</DefaultValue></a></MetadataDefaults>");
fci.Url = "client_LocationBasedDefaults.html";
fci.Overwrite = true;
var metaDataFile = formsFolder.Files.Add(fci);

ctx.Load(metaDataFile);
ctx.ExecuteQuery();


After this we need to bind the out of the box event receiver. Note that we explicitly configure the event receiver to be synchronous. If you don't, the user will need to refresh the screen after dragging a document to the library to see the updated value. The following snippet has all the code required for this:
//Binding the OOTB event receiver
var list = ctx.Web.Lists.GetByTitle("Metadata");
var erci = new EventReceiverDefinitionCreationInformation();

erci.ReceiverName = "LocationBasedMetadataDefaultsReceiver ItemAdded";
erci.SequenceNumber = 1000;
erci.ReceiverClass = "Microsoft.Office.DocumentManagement.LocationBasedMetadataDefaultsReceiver";
erci.ReceiverAssembly = "Microsoft.Office.DocumentManagement, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c";
erci.EventType = EventReceiverType.ItemAdded;
erci.Synchronization = EventReceiverSynchronization.Synchronous;

var receiver = list.EventReceivers.Add(erci);
ctx.Load(receiver);
ctx.ExecuteQuery();


And that's all there is to it. It's also possible to do this with Managed Metadata values. However, to get these to work you need to ensure they're already in the taxonomy hidden list on the site you are deploying this to. That's slightly out of scope for this write-up, but let me know if you run into trouble with that.

Attached: A full demo project for a provider hosted app which creates and configures a document library with managed metadata defaults.

Rating