Azure Functions, JS and App-Only Updates to SharePoint Online

Have you ever, really wanted to have your JavaScript perform a RunWithElevatedPrivileges against SharePoint Online?  Do something that the current user just don't have permission to do?

Today we tackle this core problem that every SharePoint JavaScript developer has thought about at least once.  And we will do it with AzureFunctions.

1.  Register Azure Active Directory App

You can go through dev.office.com to get started - but you'll need various bits of the portal.  So do this from old portal https://manage.windowsazure.com

You need to write copy out your Client ID and Client Secret

Lets give this some App-Only Permissions

Also need to allow implicit flow, that means download the manifest, change the json (below) and upload it back.

 

2.  Create Azure Function

Go to http://functions.azure.com and sign in. 

This picture looks like an ad in my blog...

This picture looks like an ad in my blog...

Create a new function - I choose the HttpTrigger with Node template.

2.1 Add NPM modules

NodeJS modules (= C# reference libraries) are loaded via npm install (= nuget).  To access this, you need to go to

Function app settings > Go to App Settings > Tools(tipped by @crandycodes)

npm install adal-node
npm install request

The errors are because I don't have a package.json file in the folder.  Add adal-node and also request.  Check they are here.

2.2 NodeJS code

Go back to the function and add this code

var request = require("request");
var adal = require("adal-node");

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'fE4kulPjYOUR-CLIENT-SECRET=';
    var resource = 'https://graph.microsoft.com';

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };

        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Run it

Got our auth token, and got our list of groups.  Perfect.

2.3 Call SharePoint Online

var request = require("request");
var adal = require("adal-node")

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'fE4kulYOUR-CLIENT-SECRET=';
    //var resource = 'https://graph.microsoft.com';
    var resource = 'https://johnliu365.sharepoint.com';

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        /*
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };
        */
        var options = {
            method: "POST",
            uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('Poked')/items",
            body: JSON.stringify({ '__metadata': { 'type': 'SP.Data.PokedListItem' }, 'Title': 'Test ' + (req.body.name || "hello!") }),
            headers: {
                'Authorization': 'Bearer ' + accesstoken, 
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
            }
        };


        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Changed resource to sharepoint.com, also changed the POST options - I want to insert a list item.

This is so sad!  "Unsupported app only token."  :-( :-(

1.1 Backtrack

To move on, we need to backtrack a bit.  @richdizz has a great blog post that documents this - to perform App Only operations on SharePoint Online, the client ID / Client Secret doesn't cut it.  You need to get auth token via certificate.

https://blogs.msdn.microsoft.com/richard_dizeregas_blog/2015/05/03/performing-app-only-operations-on-sharepoint-online-through-azure-ad/

Rich also pointed me to Travis Tidwell (Form.IO) 's excellent

https://github.com/formio/keycred

npm install -g keycred
keycred
(go through the options)

You need three things:

1. The generated keyCredentials JSON

You need to add this part to your AD App Manifest json file - under keyCredentials.  Your JSON should have a really long value for "value"

2. The private key

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAveX4jNn/eBPM1kdRNMPAlh6rT/JFoah9QkUbeYPkYGqWvn7X
~~snipped~~
0ibMc7T5K7AGVT0q0ppBLheFQkeSnPbHJrX40xILEkzd/0RLvC8X
-----END RSA PRIVATE KEY-----

Copy the entire thing, including -----BEGIN and END ----- and save this into a file.  I called mine funky.pem
Upload the funky.pem file into the folder.  I use the VS Online tool.
 

 

3. Certificate Fingerprint:

85b82741408c6c3af462b3a378e3e8963efaad70

2.4 Update to acquireTokenWithClientCertificate

var request = require("request");
var adal = require("adal-node");
var fs = require("fs");

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'DONT NEED USE CERT';
    //var resource = 'https://graph.microsoft.com';
    var resource = 'https://johnliu365.sharepoint.com';

    var thumbprint = '85b82741408c6c3af462b3a378e3e8963efaad70';
    var certificate = fs.readFileSync(__dirname + '/funky.pem', { encoding : 'utf8'});

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCertificate(resource, clientId, certificate, thumbprint, function(err, tokenResponse) {
    //authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        /*
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };
        */
        var options = {
            method: "POST",
            uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('Poked')/items",
            body: JSON.stringify({ '__metadata': { 'type': 'SP.Data.PokedListItem' }, 'Title': 'Test ' + (req.body.name || "hello!") }),
            headers: {
                'Authorization': 'Bearer ' + accesstoken, 
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
            }
        };


        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Use fs.readFileSync to read funky.pem file and acquireTokenWithClientCertificate instead of ClientSecret

Result

Note - updated by "funky"

Triggers

There are several Azure Functions triggers that immediately jumps out at me as being really useful.

HTTP Trigger

Your JavaScript can call the azure function and trigger the action.  You will need to turn on CORS.  And filter to only your domain.

But this will give your client side javascript ability to call an elevated function.

Timer Jobs

You can trigger your function based on a timer.  In this scenario, your javascript will run against SharePoint like a timer job. 

WebHooks

Currently only Exchange in Office 365 supports WebHooks.  But SharePoint will support it in the future.  When this becomes available, a webhook can trigger an Azure Function and essentially, you have a List Item event receiver.

Except all in JavaScript.

So much fun haha I'm so excited!

Further Reading

 

Hype Level Insane - #FutureOfSharePoint

I wrote this as an email to colleagues.  But it got so long and exciting with pictures I decided to publish it.  There are no spoilers and I don't know any secrets.

 

 

#FutureOfSharePoint event is a keynote follow by sessions

There are more sessions after the keynote by Jeff Teper on whatever they will announce, so if you are a SharePoint guy you might be up for a looooonnnnng day.

Venue setup.  Small invite only place.

 

A lot of SharePoint MVPs have invitations to be physically at the event – there’s a significant gathering of them.  Perhaps Microsoft will consider giving our SharePoint MVP titles back 😉  Office Server and Services is nice.  But being a SharePoint MVP was special.

A group of SharePoint MVPs.  A murder of SharePoint MVPs?

There are times I really think about moving to America.  This is one of those times.

 

Australian Timezone

I’m debating whether to sleep really early and wake up at 2am.  Or just read it tomorrow – I’m pretty sure there will be Sway, liveblog, twitter coverage #FutureofSharePoint, yamjams and a number of MVP impression videos after the event.

I do hope the event will be broadcasted via Skype-broadcast.  Eat your own dogfood Team Office.

Next week Collab365 is broadcasting from MS HQ.  I hope they are setting up with MS and will utilize Skype to run their online conference.  In the past years we have used YouTube.

There is a PowerBI livequery dashboard set up with MS’s new Microsoft Flow

http://whitepages.unlimitedviz.com/2016/05/integrating-microsoft-flow-power-bi-real-time-reporting/

 

SharePoint Can't Be "Dead"

Reading the tea-leaf from what’s public:  To people that says “SharePoint is dead”...

Jeff Teper would not have returned to this role and be “Corporate Vice President of OneDrive and SharePoint” only to tell the world SharePoint is dead.

MS would not invite 20+ SharePoint MVPs to a physical event to tell the world SharePoint is dead.

My expectation is that the news will be really positive.  But my personal hype level is already far too high.

 

Key MS people to follow:

https://twitter.com/jeffteper  Father of SharePoint, Corporate VP.

https://twitter.com/mkashman

https://twitter.com/williambaer

https://twitter.com/AdamHarmetz 

https://twitter.com/danholme   Founder of IT Unity and MVP, recently joining Microsoft to continue Office evangelism. 

 

This rant is already too long and with pictures I might make this a blog post.  But no guesses from me.  Things will either get released with much fanfare, or they won’t.  😊

SharePoint Events and news catch up for Australia - April/May 2016

I was preparing this list for our April Sydney SharePoint Usergroup event.

April 28-29 Digital Workplace Conference in Melbourne

tile-blog-dwc.png

This is the annual Australian SharePoint Conference / ShareThePoint Conference by Debbie Ireland.  The biggest Australian SharePoint show for the year.  A lot of opportunity for networking, user cases and physically meeting and talking with the community experts.  Lots of Australian and overseas visiting SharePoint experts across the spectrum (IT Pro, Business, Power User, Dev) at this event.  http://dwcau.com.au/  

 

April 28-29 Cloud Roadshow in Sydney

This is a Microsoft event for a developer audience.  Focused on Azure.

https://microsoftcloudroadshow.com/sydney

 

May the 4th - Future of SharePoint

This is an streaming online event with Jeff Teper, the father of SharePoint (and now OneDrive).  It would be live on May the 5th for Australians and probably very early in the morning.  Check timezones.

https://resources.office.com/en-us-landing-the-future-of-sharepoint.html

 

May 7 Office 365 Saturday Perth

The SharePoint / Office 365 Saturday events for 2016 kicks off again from Perth.

http://www.o365saturdayaustralia.com/perth

 

May 10-12 Collab365 Online Summit

The SP24 / Collab 365 guys are back again for 2016 with a huge lineup of streaming and recorded sessions.

http://collab365.events/collab365-summit-2016/

 

OneDrive for Business

There was a recent blog post was really good with information on 2016 roadmap.

https://blogs.office.com/2016/04/12/onedrive-for-business-recognized-as-an-efss-leader-and-continues-momentum-with-spring-updates/#wsEJ4t8xOHg24iYE.97

(Ignore the marketing intro about industry recognition… the roadmap items like Win 8.1, pause sync, and also Doc Lib sync in 3rd quarter 2016 is good info)

 

New Document Library UX went live

https://support.office.com/en-us/article/What-is-a-document-library-3b5976dd-65cf-4c9e-bf5a-713c10ca2872?ui=en-US&rs=en-US&ad=US

Almost universally, the reception is cautious.  We like seeing updates.  We don't like missing ribbons and hiding team collaboration features.  Robert Crane has one of the earliest pieces on this http://blog.ciaops.com/2016/04/this-seems-like-siloing-to-me.html that I really echo with.

UserCustomAction-ConfigPage adding CSS file links

I've had several questions in comments about how to add CSS files using the Simplest-Safest-Most-Future-Proof-Way to customize your SharePoint and SharePoint Online using UserCustomActionConfigPage

This is something I wanted to talk about from day 1 of this series - as I did cover you can add more than 1 CSS files, and specify their ranking via the Sequence number.

Because UserCustomAction doesn't create a link for CSS references, the way to do this is via the ScriptBlock and create a LINK element and attach it on page creation.

How to use this?

Grab the latest configure-page.aspx from Github.

Reference a CSS file instead of a JS file.  The ID is optional, but will be respected if specified.

The Sequence is honoured - 1001 will follow 1000.

Result - the CSS reference is loaded into the page.

Happy branding!


SP2013/2016 Responsive-UI and UserCustomActionsConfigPage

I just pushed an update to my Simplest-Safest-Most-Future-Proof-Way to customize your SharePoint and SharePoint Online using UserCustomActionsConfigPage (UCACP) github repo.  This is an important update because of a BIG thing that has just happened over at PnP side.

The PnP team has just released a SP2013/2016 Responsive UI pack

I jumped in a had a read, it uses the ScripLink way to attach a single javascript file, the file then looks for script id="PnPResponsiveUI"  element on the page, and attaches a CSS file.

My single page UserCustomActionsConfigPage does very similar things, except I use the ScriptSrc element in a UserCustomAction and that doesn't set the Script ID.

So this update to UserCustomActionConfigPage adds an additional textbox.  You can leave it blank.  But if you specify an ID, it will use ScriptBlock instead to insert the UserCustomAction.

New ID field

Installing 2013/2016 Responsive-UI pack with UCACP:

Copy three files into your Site Assets.   NOTE some browsers will save github files as HTML (because Github doesn't want to serve the files so it tells browsers these are HTML) - please double check the files are saved correctly as raw CSS or JS.

 

 

ID: PnPResponsiveUI
Url: SiteAssets/PnP-Responsive-UI.js

Usually, you can leave the ID blank.  But PnP-Responsive-UI.js needs it to be PnPResponsiveUI

Install to Current Web.  The Site User Custom Actions list should reflect there is now a SiteAssets/PnP-Responsive-UI.js inserted.  There is no ScriptSrc, because it is inserted via ScriptBlock.

The ScriptBlock is inserted using a Script tag attached to the head element.  This is identical to how PnP is doing it via PowerShell

One note - also, switch off your Mobile View feature.


What does it look like?

The Debugger shows both CSS and JS are injected correctly.  Note, I'm injecting from my own SiteAssets library.  PnP creates a folder under Style Library.

See the Responsive-UI in action with the Hamburger menus.

PnP PowerShell Installation

I want to reference drisgill's post on this Responsive-UI http://blog.drisgill.com/2016/03/a-look-at-the-pnp-responsive-ui-package-for-sharepoint-on-premises.html

He talks about the first impressions of the Responsive-UI, as well as a detailed section on how to install the Responsive-UI via PowerShell (as prescribed by PnP tools).

Installation with PowerShell is fine and dandy, but it assumes you are some sort of master magician.  Multiple downloads, and a dependency on 2015-CU.  (Which BTW you should have installed already).

I consider the PowerShell install perhaps a bit daunting and a barrier for people to try this customization on their site.

The UCACP way is 3 files.

Customization with the User Custom Actions is magical.  But we can leave out the PowerShell here this time :-)

Uninstall Responsive-UI Pack

And the Document Library view is back to normal.

Update 2018-05

Fixed some github broken links

Related Posts