Getting attachments from an O365 SharePoint list item Cross-Domain

In this article I will demonstrate the nuances of being able to get Office 365 SharePoint attachments in the same domain and in the Cross-Domain scenarios.

Getting information about a list item

When requesting information from a list in SharePoint, the detail returned does not by default contain any information about attachments other than “yep I got some”. The image below shows a typical response from a simple request to show the first value in the list

var path = "https://psclistensdev.sharepoint.com/sites/demo/";
$.ajax({
  url: path+"_api/web/lists/GetByTitle('Company')/Items?$top=1",
  type: "GET",
  headers: {
    "Accept": "application/json",
    "Authorization": "Bearer " +  app.getCookie("OAuthToken")
  },
  contentType: "application/json;odata=verbose"
}).done(function(res){
    console.log(res)
})

s1

To be able to see information about the Attachments we add the $expand=AttachmentFiles parameter to the URL

var path = "https://psclistensdev.sharepoint.com/sites/demo/";
$.ajax({
  url: path+"_api/web/lists/GetByTitle('Company')/Items?$expand=AttachmentFiles&$top=1",
  type: "GET",
  headers: {
    "Accept": "application/json",
    "Authorization": "Bearer " +  app.getCookie("OAuthToken")
  },
  contentType: "application/json;odata=verbose"
}).done(function(res){
    console.log(res)
})

s2

Looking at the Attachment information

There are multiple URLs which are returned in the JSON – looking at each of them we find that some open/download the file

  1. //psclistensdev.sharepoint.com/sites/demo/Lists/Company/Attachments/1/quickstart_guide.pdf
    • works great file downloads
  2. //psclistensdev.sharepoint.com/sites/demo/_api/Web/Lists(guid’eaf7c922-649e-4447-b695-df9030e85072′)/Items(1)/AttachmentFiles(‘quickstart_guide.pdf’)
    • Creates a file called “AttachmentFiles(‘quickstart_guide.pdf’)” which when opened in notepad is actually an XML reference document…

So when downloading the file attachment in the same domain – requesting the file via the first URL – works just fine

s3

But if we move to a different domain (which has previously been added as a trusted App through Azure AD) we find that the Cross Domain headers are not provided and therefore the download fails…..

s7

I believe (although I cannot be sure) that this is because the direct URL for the attachment does not use the “_api” it is not being picked up by the Azure AD App permission process and the headers are not being added.

Doing it the other way

Going back to URL #2 (which if you remember returns an XML file)

  • //psclistensdev.sharepoint.com/sites/demo/_api/Web/Lists(guid’eaf7c922-649e-4447-b695-df9030e85072′)/Items(1)/AttachmentFiles(‘quickstart_guide.pdf’)

If we try to request this, it does actually work…..but returns the JSON version of the XML file (cos I asked it to in the headers)

s5

The trick here is to add “$value” to the end of the URL. This triggers SharePoint to send out the attachment itself

  • //psclistensdev.sharepoint.com/sites/demo/_api/Web/Lists(guid’eaf7c922-649e-4447-b695-df9030e85072′)/Items(1)/AttachmentFiles(‘quickstart_guide.pdf’)/$value

s6

Conclusion

In this article we have seen how there are multiple ways to collect the reference to a file attachment from a list item in O365 SharePoint. While both URLs work when requested from the same domain, only one of them triggers the Azure AD App registration model to add the appropriate HTTP Headers to the request, allowing a Cross-Domain request to be successfully executed.

 

 

 

 

 

 

Advertisements

Simplifying O365 Outlook client Add-In Authorization without C#

In this article I will demonstrate a simple method for getting an Authorization into and Outlook Add-in without writing any C# code. I will also highlight some of the problems involved and how the solution elegantly overcomes this.

Introduction

In the previous article we looked at how the How the example O365 Authorized CORS application works and in that example is a location.href change which works just fine within a web browser, but fails badly as an outlook client Add-in.

When the example CORS application is packaged and deployed as a client Add-in it appears to work until you click the “Get Token Button”. The location.href change causes an Internet Explorer window to open, where you can log in……but the end result is you being in an Internet Explorer Window and the token is not in the Outlook Add-in.

A recognized way to solve this issue 

Simon Jaeger has written a number of excellent articles on how to create Outlook Add-ins through Visual Studio and writing some C#

http://simonjaeger.com/developing-outlook-add-ins-where-to-integrate-and-what-you-can-do/

http://simonjaeger.com/building-a-good-authentication-flow-in-an-office-add-in/

The guys in my team tell me that those are a pretty sweet solution to the problem, but my problem is I am not a C# developer and have no desire to become one.

What Simon’s code did do though, was was to inspire me to try something different.

Solving the problem without C#

As I understand it Simon’s example opens an authentication window and then passes the authentication token back to the Add-In via SignalR

 // Show prompt in a new window
    window.open(redirectUri.replace("signalrid", signalrId), "",
        "menubar=0,location=1,resizable=1,scrollbars=0,status=0,toolbar=0,width=550,height=600");

But it struck me that the window.open opens the possibility for the open window to communicate with the opener. (Yeah I know that’s a dumb sentence but it is accurate).

So I put it to the test. I created a button on my sample index.html and made the deployment as an add-in

c1

The code on the button – dead simple – open the window on itself (it really doesn’t matter which site)

<button onClick="window.open('https://xomino365.azurewebsites.net/o365/index.html')">Open Window</button>

Looking at the open window we can bring up developer tools and use the console to test if window.opener is null.

c3

and it isn’t.

So then let’s do a test – let’s see if we can manually stick a value back into the opener……and it did !!

c4

Well in that case we are all sorted !!

Modifying the example code

I am modifying my existing code for the sake of a consistent story, this is not how you would do it in production.

  • If the index.html opens and window.opener is not null I call the function to requestToken() automatically.
  • The page reloads
  • If the page loads detects that we have a token, and we have a window.opener, it
    • Sends the token back to the Outlook client Add-In
    • Hides all the buttons
    • Clicks the Make CORS Request button automatically
    • Closes the popped up window
            if (window.opener){
                window.opener.document.getElementById("TxtOauthToken").value = token;
                window.opener.document.getElementById("doCors").style.visibility = "visible";
                window.opener.document.getElementById("doCors").click()
                window.opener.document.getElementById("getToken").style.display = "none";
                window.close()
            }

And that’s it – the user either sees a quick popup and then the Add-In gets the data from SharePoint. If they have not checked the “keep me logged in” box, or have had their login expire, they see a log in screen where they authenticate with O365 SharePoint, the window then closes and runs in the Add-In.

c5

Conclusion

In this article we have seen how to use a window.open from within our Outlook Add-in to be able to create an Authorization token which can be passed back to the Add-In and used to get Cross-Domain SharePoint data from O365.

In a future article I will streamline this code to create something which can be used for just authorization and is not the same webpage as the Add-In.

 

PS

If you have never done this before you may be prompted with the following dialog – which MAY come up in the background as well. You need to (and tell your users to ) check the box and Allow this in the future.

c2

PPS

I also discovered that you can use WebSockets to transfer data “into” the Outlook Client Add-In. This turned out to be unnecessarily complex and raises some security concerns – but – makes for some excellent ideas for future Add-In functionality ideas.

How the example O365 Authorized CORS application works.

In this article we will look into how the example provided on the Microsoft HowTo site works and then discuss some practical implementations thereof.

Introduction

In the previous article (https://xomino365.com/2016/02/01/using-azure-ad-to-allow-authorized-cors-access-to-o365-data/) we looked at how to create an Azure AD application and securely access O365 data from within another web domain. The Microsoft How to site provides and example application on how to get data from your One Drive. But in this case we are going to look at how to get data from a SharePoint site collection.

The basic site

The site does have a good working example, you can copy and paste the code. What I wanted to show here was the end result and how it works. I modified mine slightly to pull data from a SharePoint list rather than OneDrive but it all works.

Starting at the site root (which is anonymously accessible), we click the Get Token button.

a13

The application then makes a request to the Azure authentication pages


aa2

Combining the values added to the example application we get the following values added to the Query_String of the address. These values must coincide with the values in the application manifest.

client_id=xxxxxxxx-xxxx-xxxx-xxxx-dca7fd30e1cb
redirect_uri=https://xomino365.azurewebsites.net/o365/demo.html
resource=https://xomino.sharepoint.com
response_type=token

If the user is not signed in then they are asked to do soaa1

Once successful authentication has been achieved the login end point returns the user to the “redirect_uri” and tacks the OAuth token onto the end of the returned URL

aa3

The application also sets a cookie – this is the Authentication cookie which says that you are logged into O365 – but that is very different from the Authorization (OAuth) token which is currently in the URL. You will also note if you look carefully that there is an expiry time in the Query_String parameters (3599) so assuming this is in second and not minutes – the OAuth token is only good for 60 minutes….

The example application waits for the response and if it find an access_token in the URL it parses it, and then moves it into the OAuth Token field

aa4

In this example the Endpoint is a hard coded URL which represents the Root folder of the root site in my O365 site. It is an out of the box REST URL.

https://xomino.sharepoint.com/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files?@TargetLibrary=’Documents’

If we pull that up in a new tab we get the following

aa5

The reason I am getting this is because I have not actually authenticated with xomino.sharepoint.com at this time – I have logged into Azure AD. This is really interesting because if I run the example right now it will work – AzureAD has given me enough permission to make the request but not to go directly to the site. I say interesting, but it is actually slightly annoying from a developers perspective. Every time a user moves around O365 they have to go through the Single Sign on process – which is very cool, in that it works, but not the ideal user experience.

If I open https://xomino.sharepoint.com in a new tab – I get the cycle login screen and my SharePoint sites open automagically – SSO works. When I go back to the REST endpoint again now I get an authorized page.

aa6

The only reason I even bring this up is to show that Authorization is not the same as authentication. The Access_Token actually grants us permission to retrieve data and interact with the SharePoint site, even though technically we are not logged into it (yet). Pretty smart to be honest!

But this is XML – old school ATOM service. – we want new school JSON and the example app obliges.

Getting JSON Data

When the demo site realizes there is an access_token in the URL it hides the GET TOKEN button and shows the Make CORS Request button.

Clicking the Make CORS request button creates a very specific AJAX request – let’s look at the code directly.

  var xhr = new XMLHttpRequest();
  xhr.open("GET", getEndpointUrl());
  xhr.setRequestHeader("authorization", "Bearer " + token);
  xhr.setRequestHeader("accept", "application/json");
  xhr.send();

In this case the token is the very long access_token from the URL and the getEndpointUrl() is the xomino.sharepoint value in the EndpointURL field.

Once the AJAX call is made you can see what happens in the transfer using the browser developer tools (in this case firebug). The Accept Header is what will cause the O365 server to respond with JSON and not ATOM XML.

The Request Headers

This is what is going out to the O365 servers – as you can see the Accept Header and the Authorization header have been set.

aa7

The Response Headers 

The critical thing to note here is that the Access-Control-Allow-Origin Header = “*” this allows the xomino365.azurewebsites.net domain to successfully request data from the xomino.sharepoint.com domain.

aa8

The response itself

The response is a JSON data object

aa9

making it more readable we can see the object is an array of “results” within d. Each item within the array represents the verbose detail of each file within the site collection.

aa10

Conclusion

 

In this article we have looked in some detail at how the Microsoft HowTo website example works and how the authorization/authentication mechanisms play with the AJAX requests to retrieve data from one domain into another.

In a future article we will look closer at what happens when we start to play with these requests and try and break the system…