Category Archives: Office 365 – REST API

Search documents and generate a preview with Office 365 REST API

Since Delve and Office Graph were announced last year, you may have asked how it’s working and if it’s possible to retrieve documents from all your SharePoint sites, and not only content from Delve/Office Graph.

Delve - HomePage

In the rest of this article, we are not going be focused on Delve / Office Graph (maybe in a future post) but we’re going to explain how to search any kinds of documents and how to generate a preview for these.

If you’re not familiar with Office 365 REST API, you are able to perform searches on content stored in your SharePoint sites by using the search endpoint.

https://<tenant>.sharepoint.com/_api/search

This endpoint offers 2 different possibilities to submit a query to the SharePoint search index.

For the first one, you just need to submit an HTTP GET request to URL below with some parameters in the querystring.

https://<tenant>.sharepoint.com/_api/search/query?querytext='OneDrive'

This first usage is described by a lot of blogs thus, do a search with Google/Bing if you want more information on this.

The second possibility is to submit an HTTP POST request to :

https://<tenant>.sharepoint.com/_api/search/postquery/

This second choice is more flexible if your search query is complex or very long (remember that an URL cannot exceed ~4000 characters).

Below you can see and example of the body you have to include in your HTTP POST message. (Important : SharePoint is case sensitive when you submit your search query).

{
    request =     {
        Querytext = "(ContentTypeId:0x010100F3754F12A9B6490D9622A01FE9D8F012*)",
        RowLimit = 20,
        SelectProperties =         (
            DocId,
            DefaultEncodingURL,
            DocumentPreviewMetadata,
            SiteId,
            Title,
            UniqueId,
            WebId
        ),
        StartRow = 0,
        TrimDuplicates = True
    }
}

This query will return the first 20 documents from Office 365 Video (based on the ContentTypeId). We only select properties needed (DocId, SiteId, Title…) and want to exclude duplicates.

The StartRow parameter allows you to perform paging if necessary.

There are many more kinds of parameters you can pass to your search query. Refer to the following MSDN page if you need more information : https://msdn.microsoft.com/en-us/library/office/jj163876.aspx

Now that we are able to retrieve a list of documents (Office 365 videos in our case), how to generate a preview for each of them ?

By analyzing how Delve is working, you can see that thumbnails are generated by calling an URL with the following format :

https://<tenant>.sharepoint.com/_layouts/15/getpreview.ashx?guidFile=<GUID>&guidSite=<GUID>&guidWeb=<GUID>&docid=<Int>&metadatatoken=<String>

As you can see, these parameters are similar to those we have put in our search query :

  • guidFile = UniqueId
  • guidSite = SiteId
  • guidWeb = WebId
  • docId = DocId

But what about the metadatatoken parameter ? That’s the magic because we have put a particular property named DocumentPreviewMetadata in our search query.

If you look at the search result response, this property is returned in the following format.

{
    Key = DocumentPreviewMetadata,
    Value = "{\"previews\":[{\"h\":187,\"src\":0,\"w\":300}],\"token\":\"300x187x0\"}",
    ValueType = "Edm.String"
}

As you can see, the value for the property is a JSON serialized object in which you are able to get the width, the height and the token associated to the file preview. If the file has multiple preview (e.g. many slides in a PowerPoint document), the previews array will contain multiple values.

If you deserialize this value and put the token value into the metadatatoken parameter of the URL mentioned previously, you have all the items necessary to call the URL and to generate the preview for all your documents.

Thanks for reading this article and I hope it will help you to develop awesome apps by using the Office 365 REST API 😉

List subwebs by using the Office 365 REST API requires to have edit rights

Today I was facing a weird issue and I think it can be the same for others people so I decided to share this experience.

For an application which uses the Office 365 REST API (especially the SharePoint REST API), I tried to reduce the rights necessary to run (in the Microsoft Azure Portal).

The initial configuration was the following :

Azure Rights - Initial Configuration

With this configuration, I was able to retrieve data from sites, webs, subwebs, lists, etc…

Because the application doesn’t necessary need to edit the data, I tried to remove the ‘Edit or delete items in all site collections‘ permission as you can see below.

Azure Rights - IssueWith this new configuration, I tried to execute the following request :

https://tenant.sharepoint.com/_api/Web/Webs/

The URL above allows you to retrieve the list of subwebs from the given site.

When I execute this request, I receive the following JSON response which indicates that I’m not authorized to do this action.

{
    error =     {
        code = "-2147024891, System.UnauthorizedAccessException";
        message = "Access denied. You do not have permission to perform this action or access this resource.";
    };
}

But if I try to access https://tenant.sharepoint.com/_api/Web/ to retrieve information (title, server relative URL…) from the current site, everything works fine.

Revert the permissions to the initial configuration has solved the issue but I’m very surprised to see that it’s necessary to have ‘edit‘ permissions to be able to retrieve the list of subwebs.

Hope this will help you if you encounter the same issue  😉

 

Use ADAL for iOS/OSX with O365 Discovery REST API

You want to access your Office 365 data from an iOS / OSX application and don’t know how to start ? You are in the right place 😉

In this article, we will see the first step to write an application connected to Office 365 which consists to discover what services are available for the current user and what are the endpoints URLs for each.

When you want to connect to Office 365 from an application and use the REST API to retrieve data, you need to authenticate against Azure Active Directory (each user accounts are stored in AAD).

To help us to achieve this goal, Microsoft has released a library named ADAL (Active Directory Azure Library) which was written in Objective-C and can be used with iOS and OSX.

You can download ADAL for free on GitHub : https://github.com/AzureAD/azure-activedirectory-library-for-objc

The next step to be able to write an application which operates with Office 365 is to declare it in the Azure directory associated to your Office 365 tenant. If you are a global administrator of your tenant, you can achieve this by using the Azure Management Portal : https://manage.windowsazure.com

On the Azure Management Portal, go to Active Directory section, select the appropriate directory, select applications and create a new native application.

After you fill the requested information (a name and a redirect URL), you have to declare for each service (SharePoint, Exchange, Active Directory…) what permissions are needed by your application as you can see below.

Azure Manage Portal - Application Permissions

We are now ready to write code to connect to Office 365 and to discover what services are available for our user account.

In your application (it works in the same way for iOS and OSX), add the ADAL library in your project as usual when you use a third party library/framework.

    NSString *clientId = @"12345678-abcd-1234-abcd-1234567890ab";
    NSURL *redirectUri = [NSURL URLWithString:@"https://beecomedigitaldemo"];

    ADAuthenticationContext *authenticationContext = [ADAuthenticationContext authenticationContextWithAuthority:@"https://login.windows.net/common" error:nil];
    [authenticationContext acquireTokenWithResource:@"https://api.office.com/discovery/" clientId:clientId redirectUri:redirectUri completionBlock:^(ADAuthenticationResult *result) {
        if (result.status == AD_SUCCEEDED) {
            // Authentication succeeded
        }
        else if (result.status == AD_USER_CANCELLED) {
            // Authentication cancelled by the user
        }
        else {
            // Authentication failed
        }
    }];

To authenticate against Azure Active Directory, we have to use the ADAuthenticationContext class available in ADAL.

To instanciate a context, you need to indicate what is the authentication authority you will use. The default authority to logon with Azure Active Directory is https://login.windows.net/common but if it’s applicable for your company, you can specify another authority.

After the instanciation of the context, you have to acquire an authentication token for the resource which you will request later.

In our case, we want to discover services offered by Office 365. The resource associated is https://api.office.com/discovery/ (be careful to not omit the trailing slash character in the URL otherwise it will not work).

When you want to get a token by calling the acquireTokenWithResource method, you have to set a clientId and a redirectUri. These values are those you have declared in the Azure Management Portal.

This method executes asynchronously so when it’s finished, the completionBlock passed in the last parameter is executed. To ensure that the request was successfull, you can test the result status. If everything was good, status will be equal to AD_SUCCEED. Otherwise it will be equal to AD_FAILED or AD_USER_CANCELLED.

Now that we are successfully authenticated and we have a valid authentication token, we can discover what services are available. To do it, we just have to send a request to the discovery REST API endpoint.

// In-house wrapper of NSURLSession to simplify HTTP communications
BCDHttpClient *httpClient = [[BCDHttpClient alloc] init];

NSURL *serviceURL = [NSURL URLWithString:@"https://api.office.com/discovery/v1.0/me/services"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:serviceURL];
[request setValue:[NSString stringWithFormat:@"Bearer %@", result.accessToken] forHTTPHeaderField:@"Authorization"];

[httpClient dataWithRequest:request completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
    if (error == nil) {
        id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonObject);
    }
}];

As you can see above, we build a NSMutableURLRequest with the URL of the Office 365 discovery endpoint : https://api.office.com/discovery/v1.0/me/services

To call this endpoint and retrieve information associated to our user profile, we need to add an Authorization HTTP header with the following format : “Bearer our_access_token“.

The access token is the one that have been retrieved by ADAL and is accessible in the result parameter of the completion block.

Then we execute the request by using our in-house implementation of a HTTP client (just a lightweight wrapper around NSURLSession). If the request complete successfully, we deserialize the retrieved data (in JSON format) and print them in the console.

You can see below the result printed in our console for all services found for the current authenticated user. In this example, our user have access to SharePoint and Exchange with many different services for each (MyFiles, RootSite, Calendar, Contacts and Mail).

{
    "@odata.context" = "https://api.office.com/discovery/v1.0/me/$metadata#allServices";
    value =     (
                {
            "@odata.editLink" = "services('MyFiles@O365_SHAREPOINT')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('MyFiles@O365_SHAREPOINT')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = MyFiles;
            entityKey = "MyFiles@O365_SHAREPOINT";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://beecomedigitaldemo-my.sharepoint.com/_api/v1.0/me";
            serviceId = "O365_SHAREPOINT";
            serviceName = "Office 365 SharePoint";
            serviceResourceId = "https://beecomedigitaldemo-my.sharepoint.com/";
        },
                {
            "@odata.editLink" = "services('RootSite@O365_SHAREPOINT')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('RootSite@O365_SHAREPOINT')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = RootSite;
            entityKey = "RootSite@O365_SHAREPOINT";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://beecomedigitaldemo.sharepoint.com/_api";
            serviceId = "O365_SHAREPOINT";
            serviceName = "Office 365 SharePoint";
            serviceResourceId = "https://beecomedigitaldemo.sharepoint.com/";
        },
                {
            "@odata.editLink" = "services('Contacts@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Contacts@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Contacts;
            entityKey = "Contacts@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        },
                {
            "@odata.editLink" = "services('Mail@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Mail@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Mail;
            entityKey = "Mail@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        },
                {
            "@odata.editLink" = "services('Calendar@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Calendar@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Calendar;
            entityKey = "Calendar@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        }
    );
}

As mentioned before, we have to declare what permissions are needed by our application. But what happens exactly for the user if he’s not agree with these permissions ?

When ADAL tries to acquire an access token and if the user account is not stored in the same directory as your tenant, he must consent to authorize the application for the permissions you are requested.

ADAL - User ConsentIf the user doesn’t consent to authorize your application to use the required permissions, ADAL returns an AD_FAILED status when you call acquireTokenWithResource. The consent is required only once. For subsequent requests the same consent is used by ADAL.

Now you are theorically able to use ADAL and Office 365 Discovery REST API to authenticate and retrieve information about available services for the current user  😉