How to know what file types are supported to generate previews on Office 365 ?

In our previous articles (here and here) we have seen how to generate a preview for a document stored in SharePoint / Office 365.

When you develop applications around Office 365 and because you can store potentially (there are some exceptions) any types of documents in a SharePoint site, it could be interested to know what file types are supported to generate a preview for those documents.

There’s no official documentation on how to achieve this operation but you have to know that it’s possible using the same handler as that described in our previous articles (GetPreview.ashx).

Indeed, that handler has some hidden features and one of them is the ability to get supported file types. To do that, you just have to call the following URL (don’t forget to pass a valid authentication token in HTTP headers, as you do with REST APIs) :

https://<tenant>.sharepoint.com/_layouts/15/GetPreview.ashx?Action=supportedtypes

When you call that URL, the server return a JSON response such as the following :

{
  "DocumentTypes":[
    {"Always":false,"FileExtension":".wmv","MultiPreview":false},
    {"Always":false,"FileExtension":".3gp","MultiPreview":false},
    {"Always":false,"FileExtension":".3g2","MultiPreview":false},
    {"Always":false,"FileExtension":".3gp2","MultiPreview":false},
    {"Always":false,"FileExtension":".asf","MultiPreview":false},
    {"Always":false,"FileExtension":".mts","MultiPreview":false},
    {"Always":false,"FileExtension":".m2ts","MultiPreview":false},
    {"Always":false,"FileExtension":".avi","MultiPreview":false},
    {"Always":false,"FileExtension":".mod","MultiPreview":false},
    {"Always":false,"FileExtension":".dv","MultiPreview":false},
    {"Always":false,"FileExtension":".ts","MultiPreview":false},
    {"Always":false,"FileExtension":".vob","MultiPreview":false},
    {"Always":false,"FileExtension":".xesc","MultiPreview":false},
    {"Always":false,"FileExtension":".mp4","MultiPreview":false},
    {"Always":false,"FileExtension":".mpeg","MultiPreview":false},
    {"Always":false,"FileExtension":".mpg","MultiPreview":false},
    {"Always":false,"FileExtension":".m2v","MultiPreview":false},
    {"Always":false,"FileExtension":".ismv","MultiPreview":false},
    {"Always":false,"FileExtension":".mov","MultiPreview":false},
    {"Always":true,"FileExtension":".docm","MultiPreview":false},
    {"Always":true,"FileExtension":".docx","MultiPreview":false},
    {"Always":true,"FileExtension":".dotx","MultiPreview":false},
    {"Always":true,"FileExtension":".dotm","MultiPreview":false},
    {"Always":true,"FileExtension":".bmp","MultiPreview":false},
    {"Always":true,"FileExtension":".jpg","MultiPreview":false},
    {"Always":true,"FileExtension":".jpeg","MultiPreview":false},
    {"Always":true,"FileExtension":".tiff","MultiPreview":false},
    {"Always":true,"FileExtension":".tif","MultiPreview":false},
    {"Always":true,"FileExtension":".png","MultiPreview":false},
    {"Always":true,"FileExtension":".gif","MultiPreview":false},
    {"Always":true,"FileExtension":".emf","MultiPreview":false},
    {"Always":true,"FileExtension":".wmf","MultiPreview":false},
    {"Always":false,"FileExtension":".pdf","MultiPreview":false},
    {"Always":true,"FileExtension":".pptm","MultiPreview":true},
    {"Always":true,"FileExtension":".pptx","MultiPreview":true},
    {"Always":true,"FileExtension":".potm","MultiPreview":true},
    {"Always":true,"FileExtension":".potx","MultiPreview":true},
    {"Always":true,"FileExtension":".ppsm","MultiPreview":true},
    {"Always":true,"FileExtension":".ppsx","MultiPreview":true},
    {"Always":false,"FileExtension":".xlsm","MultiPreview":false},
    {"Always":false,"FileExtension":".xlsx","MultiPreview":false}
  ]
}

You get an array of document types and for each item, you are able to know :

  • FileExtension => Does it really need to be explained ? 😉
  • Always => Does the handler is always able to generate a preview or can it fail (e.g. because the file contains unsupported features)
  • MultiPreview => Does the handler is able to generate multiple previews for the same document (e.g. slides on PowerPoint presentations)

As you can see, there’s only a few document types that are supported by Office 365 to generate previews. Now you can improve your applications to not try to generate previews for unsupported types 😉

Getting all boards to which a document belongs using the Office Graph

If you use Delve in Office 365 (an extension of Office Graph), you should know what are boards, for logically classify your documents, without moving them to a new physical location (document libraries).

Like every services in Office 365, there’s an API to allow developers to create applications around Office 365. If you want more information about how to query the Office Graph in Office 365, you can refer to the following article at MSDN.

If you read the previous article, you will see that there’s no information in the API documentation, on how to know which boards a document belongs.

To achieve this goal, there’s an undocumented action (ID = 1046) you can use to query the Office Graph. This action is used such as the others :

ACTOR(<ActorId>,action:1046)

If you want to know what are the boards for a given document, the ActorId in the syntax above corresponds to the ID of the document. You can get the ID of a document by retrieving the DocId value from the search engine, by using a simple query or a graph query (e.g. for a document from the personal feed of the current user).

If you query the Office Graph with a syntax such as ACTOR(48202211,action:1046), you will get a search result such as below (it was truncated for focusing on the most important part):

Table = {
  Rows = (
    {
      Cells = (
        {
          Key = DocId,
          Value = 4362767297,
          ValueType = "Edm.Int64"
        },
        {
          Key = Path,
          Value = "TAG://PUBLIC/?NAME=FINANCE",
          ValueType = "Edm.String"
        },
        {
          Key = Title,
          Value = Finance,
          ValueType = "Edm.String"
        },
        {
          Key = Edges,
          Value = "[{\"ActorId\":48202211,\"ObjectId\":4362767297,\"Properties\":{\"Action\":1046,\"Blob\":[123,34,84,97,103,78,97,109,101,34,58,34,70,105,110,97,110,99,101,34,125],\"BlobContent\":\"{\\\"TagName\\\":\\\"Finance\\\"}\",\"ObjectSource\":1,\"Time\":\"2015-08-21T11:34:39.0000000Z\",\"Weight\":1}}]",
          ValueType = "Edm.String"
        }
      )
    },
    {
      Cells = (
        {
          Key = DocId,
          Value = 4362767298,
          ValueType = "Edm.Int64"
        },
        {
          Key = Path,
          Value = "TAG://PUBLIC/?NAME=MARKETING+STRATEGY",
          ValueType = "Edm.String"
        },
        {
          Key = Title,
          Value = "Marketing Strategy",
          ValueType = "Edm.String"
        },
        {
          Key = Edges,
          Value = "[{\"ActorId\":48202211,\"ObjectId\":4362767298,\"Properties\":{\"Action\":1046,\"Blob\":[123,34,84,97,103,78,97,109,101,34,58,34,77,97,114,107,101,116,105,110,103,32,83,116,114,97,116,101,103,121,34,125],\"BlobContent\":\"{\\\"TagName\\\":\\\"Marketing Strategy\\\"}\",\"ObjectSource\":1,\"Time\":\"2015-08-21T11:19:06.0000000Z\",\"Weight\":1}}]",
          ValueType = "Edm.String"
          }
        }
      )
    }
  )
}

The search result above indicates that our document (ActorId = 48202211) was added in two boards called “Finance” and “Marketing Strategy“. Each row in the search result corresponds to a board with its properties (retrieved from those that have been specified in the search query).

Getting boards for multiple documents within the same request

As we have seen, it’s pretty simple to retrieve the boards for a single document but what should I do if I want to retrieve the boards for many documents ?

For performance issue it’s not viable to execute a request for each document. So what’s the solution ?

Office Graph lets you to combine many actors in the same request. So if you execute a request such as the one below (each actor corresponds to a document), you will get as response, a search result with the boards for each document.

OR(ACTOR(48204878,action:1046),ACTOR(48204877,action:1046),ACTOR(48202211,action:1046))

Good, but how can I know which document is associated to which board ?

If you paid attention to the search result, there’s a property called “Edges” for each row. The value of this property is a JSON string that has been serialized.

If we deserialize and format those values, they look like this:

Edges for row #1
[
  {"ActorId":48204878,"ObjectId":4362767297,"Properties":{"Action":1046,"Blob":[123,34,84,97,103,78,97,109,101,34,58,34,70,105,110,97,110,99,101,34,125],"BlobContent":"{\"TagName\":\"Finance\"}","ObjectSource":1,"Time":"2015-08-21T11:18:52.0000000Z","Weight":1}},
  {"ActorId":48202211,"ObjectId":4362767297,"Properties":{"Action":1046,"Blob":[123,34,84,97,103,78,97,109,101,34,58,34,70,105,110,97,110,99,101,34,125],"BlobContent":"{\"TagName\":\"Finance\"}","ObjectSource":1,"Time":"2015-08-21T11:34:39.0000000Z","Weight":1}},
  {"ActorId":48204877,"ObjectId":4362767297,"Properties":{"Action":1046,"Blob":[123,34,84,97,103,78,97,109,101,34,58,34,70,105,110,97,110,99,101,34,125],"BlobContent":"{\"TagName\":\"Finance\"}","ObjectSource":1,"Time":"2015-08-21T20:50:40.0000000Z","Weight":1}}
]

Edges for row #2
[
 {"ActorId":48204877,"ObjectId":4362767298,"Properties":{"Action":1046,"Blob":[123,34,84,97,103,78,97,109,101,34,58,34,77,97,114,107,101,116,105,110,103,32,83,116,114,97,116,101,103,121,34,125],"BlobContent":"{\"TagName\":\"Marketing Strategy\"}","ObjectSource":1,"Time":"2015-08-21T20:50:35.0000000Z","Weight":1}},
 {"ActorId":48202211,"ObjectId":4362767298,"Properties":{"Action":1046,"Blob":[123,34,84,97,103,78,97,109,101,34,58,34,77,97,114,107,101,116,105,110,103,32,83,116,114,97,116,101,103,121,34,125],"BlobContent":"{\"TagName\":\"Marketing Strategy\"}","ObjectSource":1,"Time":"2015-08-21T11:19:06.0000000Z","Weight":1}}
]

As you can see, it’s an array in which each object corresponds to an actor with some properties.

The important values for each object corresponds to :

  • ActorId ==> The ID of our document
  • ObjectId ==> The ID of the board
  • TagName ==> The name of the board
  • Time ==> When the document was added to the board

In this example in which we queried the Office Graph for three documents, two of them (ActorId = 48204877 and ActorId = 48202211) were added in two boards (“Finance” and “Marketing Strategy“). The third document (ActorId = 48204878) was only added in the “Finance” board.

Create time-limited share link for a SharePoint document using REST API

If you use SharePoint to store your documents, you necessarily needed one day to share a document with an external user to your organization.

SharePoint, like other solutions such as OneDrive, DropBox, Box, GoogleDocs… gives you the capability to generate guest links on files stored in document libraries.

To be able to generate those kind of links, you must enable the correct option at the tenant level as you can see on the screenshot below.

External Sharing - Settings

When the option is enabled, you generate the link from the share dialog box on a document.

External Sharing - Guest Link

It’s very useful but as you can see, there’s no option to limit the time for which the link is available. When you want to disable the link, you have to go back to the dialog box of the document an select the “Disable” option. If you do this on hundreds or thousands of documents, it will be unmanageable very fast.

REST API to our rescue

Fortunately, as developers, there’s often a solution that we can use to cover our needs.

In our case, if you look into the REST API (but it’s also available with CSOM), on a SP.File object, there’s a method called GetPreAuthorizedAccessUrl which allows you to generate a time-limited link.

There’s many possibilities to get a SP.File object to then call the method, so let’s take an example using the GetFileByServerRelativeUrl from an SP.Web :

https://tenant.sharepoint.com/_api/Web/GetFileByServerRelativeUrl('/Shared%20Documents/2013%20Contoso%20products.pdf')/GetPreAuthorizedAccessUrl(24)

As you can see above, we retrieve the SP.File object associated to a file stored in a library, then we generate the link which will be available for one day (24 hours). The response returned by the server is something like :

https://tenant.sharepoint.com/_layouts/15/download.aspx?guestaccesstoken=lsRrsejmU9HlzY0Kw87A9NFu4yeDgMPusDBGF%2frz6AU%3d&docid=0588b11e4a047423e8667ee0ed9d67bf9&expiration=8%2f18%2f2015+9%3a14%3a34+AM&userid=28&authurl=True

Now that you have a link which doesn’t require authentication to access/download the file, you just have to send it to your external users.

After one day, if they try to use the same link, they will receive an error message.

As we have seen in this article, SharePoint has all necessary things to generate time-limited links to share documents.

Unfortunately, there’s no option (at this time) in the UI to use this capability, so it’s up to you to develop it. 😦

Determine which languages are activated on a SharePoint site with REST API

If you develop mobile applications which need to be multilingual, it will happen sooner or later that you wanted to know which languages are activated on your SharePoint sites.

To retrieve information about your SharePoint sites, your first instinct will be to use the REST API. To achieve this goal you typically call URLs such as :

https://contoso.sharepoint.com/_api/Web
https://contoso.sharepoint.com/sites/HR/_api/Web

By default when you call those URLs, not all properties available for the site are returned in the server response. Only a subset (called the default scalar property set) is included in the response as you can see in the sample below :

{
    AllowRssFeeds = 1;
    AlternateCssUrl = "";
    AppInstanceId = "00000000-0000-0000-0000-000000000000";
    Configuration = 0;
    Created = "2015-06-05T15:12:37.89";
    CustomMasterUrl = "/_catalogs/masterpage/seattle.master";
    Description = "A new description for the Contoso Team Site";
    DocumentLibraryCalloutOfficeWebAppPreviewersDisabled = 0;
    EnableMinimalDownload = 1;
    Id = "7e75f9bc-db75-4425-b934-86bdf913f9c5";
    IsMultilingual = 1;
    Language = 1033;
    LastItemModifiedDate = "2015-08-05T17:11:31Z";
    MasterUrl = "/_catalogs/masterpage/seattle.master";
    OverwriteTranslationsOnChange = 0;
    QuickLaunchEnabled = 1;
    RecycleBinEnabled = 1;
    ServerRelativeUrl = "/";
    SiteLogoUrl = "<null>";
    SyndicationEnabled = 1;
    Title = "Contoso Team Site";
    TreeViewEnabled = 0;
    UIVersion = 15;
    UIVersionConfigurationEnabled = 0;
    Url = "https://contoso.sharepoint.com";
    WebTemplate = STS;
    "odata.editLink" = Web;
    "odata.id" = "https://contoso.sharepoint.com/_api/Web";
    "odata.metadata" = "https://contoso.sharepoint.com/_api/$metadata#SP.ApiData.Webs/@Element";
    "odata.type" = "SP.Web";
}

As you can see in the default scalar property set, we are able to know if the current site is multilingual or not (IsMultilingual = 1), but we don’t know which languages have been activated.

To retrieve the value we want, we need to modify our call to the REST API so that we have to include additional parameters in the querystring to specify which properties must be included in the server response.

https://contoso.sharepoint.com/_api/Web?$select=Title,Url,IsMultilingual,SupportedUILanguageIds

When we execute that request, the server return the following response, and we now are able to know which languages (SupportedUILanguageIds) are used by our SharePoint site.

{
    IsMultilingual = 1;
    SupportedUILanguageIds =     (
        1033,
        1036
    );
    Title = "Contoso Team Site";
    Url = "https://contoso.sharepoint.com";
    "odata.editLink" = Web;
    "odata.id" = "https://contoso.sharepoint.com/_api/Web";
    "odata.metadata" = "https://contoso.sharepoint.com/_api/$metadata#SP.ApiData.Webs/@Element&$select=Title,Url,IsMultilingual,SupportedUILanguageIds";
    "odata.type" = "SP.Web";
}

Note that although it’s very useful to be able to select which properties will be included in the server response, if you want to include all the default properties plus a new one (SupportedUILanguageIds in our case), you have to put the entire list in the querystring.

Many types in SharePoint REST API doesn’t include all available properties in their default scalar property set. If you need to know which properties are included and which are not, you can refer to the documentation (e.g. https://msdn.microsoft.com/en-us/library/dd928431%28v=office.12%29.aspx).

Generate thumbnail of a document stored in SharePoint from its URL

In a previous article, we have seen how to generate a document thumbnail from its metadata (siteId, webId, uniqueId, docId…) retrieved from the search engine.

With these metadata, you just have to build and call an URL such as the following :

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

Although easy to implement, this solution is not convenient every time, especially when you don’t use the search engine to retrieve data.

For example, when you use the Video REST API or the SharePoint REST API to manipulate list items, you don’t retrieve some of the required properties to generate the preview as described in our previous article.

One solution should be to query the search engine for the associated file to retrieve required metadata, then build and call the URL to generate the preview. This solution works very well but it’s pretty inefficient because you have to make 2 requests for each thumbnail.

A better solution based on the same HTTP Handler

Last week, I was working on different things around Office 365 and I discovered a better solution based on the same HTTP Handler.

It’s possible, rather than passing many parameters (siteId, webId, docId…), to pass only one parameter called ‘path’.

This parameter expects a value equal to the URL of the document/file for which you want to generate a thumbnail. To generate a preview, the URL should look like this :

https://tenant.sharepoint.com/portals/hub/_layouts/15/getpreview.ashx?path=https%3A%2F%2Ftenant.sharepoint.com%2Fportals%2Fcommunity%2FpVid%2FBiking%2520to%2520Work.mp4

In the sample above which generates a thumbnail for a video stored in an Office 365 video channel), you just have to encode the path of the file, and add it to the querystring for the ‘path‘ parameter.

I have tested with many file types (Word, Excel, PowerPoint, MP4…), stored in different containers (document libraries, video channels…) and it works for each of them.

For information, this solution is used by Delve to generate thumbnails for video files stored in Office 365 Video 😉

Delve - HomePage

Make your life easier with automatic storage management on SharePoint site collections

When you create a SharePoint site collection on your Office 365 tenant, by default you have to specify a storage quota which defines what’s the maximum amount of space you want to allocate to that site collection.

The quota is decreased from the storage pool assigned to your Office 365 tenant, depending on the number of licenses you purchased. By default the amount of available space is equal to 10 GB + 500 MB per user (but you can purchase additional storage space if necessary).

It’s pretty simple to use but there’s an issue with this model. Even if there’s no content stored in the site collection, the quota allocated is decreased from your storage pool. When the amount of available space is equal to zero, you won’t be able to create new site collections.

When you are an administrator of your Office 365 tenant, you are able to see all the site collections and the associated quotas from the SharePoint administration site (https://mytenant-admin.sharepoint.com/_layouts/15/online/SiteCollections.aspx) as you can see below.

Site Collection Storage Management - Before activation

But few months ago, Microsoft announced they were working on a new feature that would simplify the administration of storage space. That feature is available since few days on my tenants so let’s see how to activate it.

To achieve that goal, you have to be an Office 365 administrator and you have to go to the SharePoint admin center. On this site, just select the settings option from the left menu.

If the option is available on your tenant (the deployment on all Office 365 tenants may take some weeks), you should see a new option called Site Collection Storage Management as you can see on the following screenshot.

Site Collection Storage Management - Activation

To activate this new feature, you just need to select the Automatic option (by default it should be on Manual mode) and save your modifications.

If you go back on the screen which list all the site collections created on your Office 365 tenant, you should see that the columns named “storage limit” and “percent used” have disappeared.

Site Collection Storage Management - After activation

Now SharePoint will automatically manage the storage allocation, depending on the needs of each site collection. If your prefer to manage yourself the allocation, you can switch back to the manual mode to define quotas for each site collection.

Developing a FileSystemWatcher for OS X by using FSEvents with Swift 2.0

When Swift was announced at WWDC 2014, I was very disappointed to not be able to use some of powerful C APIs such as FSEvents, to write applications in pure Swift language.

A lot of C APIs needs a C function pointer to pass a callback function when you use the API. From Swift 1.0 to Swift 1.2, it was not possible to use C function pointer in pure Swift language.

It was possible to use FSEvents by wrapping it in an Objective-C class, and by calling this class in Swift by using the bridging features offered by the language, but it was not what I attempted to accomplish.

But things change and at WWDC 2015, Apple announced the possibility to use C function pointers with Swift 2.0.

Today I’m very pleased to give you a sample code which is a good starting point to create your own FileSystemWatcher using the FSEvents API written with Swift 2.0.

import Foundation

public class FileSystemWatcher {

    // MARK: - Initialization / Deinitialization
    
    public init(pathsToWatch: [String], sinceWhen: FSEventStreamEventId) {
        self.lastEventId = sinceWhen
        self.pathsToWatch = pathsToWatch
    }

    convenience public init(pathsToWatch: [String]) {
        self.init(pathsToWatch: pathsToWatch, sinceWhen: FSEventStreamEventId(kFSEventStreamEventIdSinceNow))
    }
    
    deinit {
        stop()
    }

    // MARK: - Private Properties
    
    private let eventCallback: FSEventStreamCallback = { (stream: ConstFSEventStreamRef, contextInfo: UnsafeMutablePointer<Void>, numEvents: Int, eventPaths: UnsafeMutablePointer<Void>, eventFlags: UnsafePointer<FSEventStreamEventFlags>, eventIds: UnsafePointer<FSEventStreamEventId>) in
        print("***** FSEventCallback Fired *****", appendNewline: true)
        
        let fileSystemWatcher: FileSystemWatcher = unsafeBitCast(contextInfo, FileSystemWatcher.self)
        let paths = unsafeBitCast(eventPaths, NSArray.self) as! [String]
        
        for index in 0..<numEvents {
            fileSystemWatcher.processEvent(eventIds[index], eventPath: paths[index], eventFlags: eventFlags[index])
        }
        
        fileSystemWatcher.lastEventId = eventIds[numEvents - 1]
    }
    private let pathsToWatch: [String]
    private var started = false
    private var streamRef: FSEventStreamRef!
    
    // MARK: - Private Methods
    
    private func processEvent(eventId: FSEventStreamEventId, eventPath: String, eventFlags: FSEventStreamEventFlags) {
        print("\t\(eventId) - \(eventFlags) - \(eventPath)", appendNewline: true)
    }
    
    // MARK: - Pubic Properties
    
    public private(set) var lastEventId: FSEventStreamEventId
    
    // MARK: - Pubic Methods
    
    public func start() {
        guard started == false else { return }
        
        var context = FSEventStreamContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
        let flags = UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents)
        streamRef = FSEventStreamCreate(kCFAllocatorDefault, eventCallback, &context, pathsToWatch, lastEventId, 0, flags)
        
        FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode)
        FSEventStreamStart(streamRef)
        
        started = true
    }
    
    public func stop() {
        guard started == true else { return }

        FSEventStreamStop(streamRef)
        FSEventStreamInvalidate(streamRef)
        FSEventStreamRelease(streamRef)
        streamRef = nil

        started = false
    }
    
}

I hope this will help you in your future applications and feel free to share this article if you found it helpful 😉

Developing Yammer apps for iOS/OS X with ADAL, REST API and Swift 2.0

You’re interested to develop applications on top of Yammer (Microsoft’s Enterprise Social Network) but you’re currently working on iOS/OS X devices ? No problem 😉

Yammer offer to developers a REST API to allow them build applications on top of the product. Microsoft has also started to move the authentication mechanism to Office 365, to let people to use the same credentials as for other services in their company (Outlook, SharePoint, Skype for Business…).

In this article, we’ll discover what are the main steps to create our first application which will use the Yammer REST API and Office 365 authentication, in conjunction with Swift 2.0, the new programming language of Apple platforms, updated during the WWDC 2015.

Declare your application in Azure Active Directory

The first step to accomplish to be able to develop an application which will call the Yammer REST API with the Office 365 authentication (based on Azure AD), is to declare your application in Azure.

To do this, you need to be a global administrator of your tenant, and 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.

Few weeks ago, Microsoft has released a new permission which allows you to use the Yammer REST API (it’s a preview for now). So just add it to your application (as you can see below) and save your modifications.

Office 365 Yammer - Azure AD Permissions

Authenticate with ADAL

The second step to develop your application is to acquire an authentication token from Azure AD and to pass it to the Yammer REST API when you want to retrieve data.

The simplest way to acquire an authentication token from Azure AD is to use the Azure Active Directory Library (aka. ADAL) which can be downloaded for free on GitHub : https://github.com/AzureAD/azure-activedirectory-library-for-objc

After downloading the library and integrating it in your application (cf. documentation on GitHub on how to achieve this goal), you can acquire a token with just few lines of code.

var authError: ADAuthenticationError?
var authContext = ADAuthenticationContext(authority: "https://login.windows.net/common", error: &authError)
var bearerToken: String!

authContext.acquireTokenWithResource("https://www.yammer.com/", clientId: "...", redirectUri: "...", completionBlock: { (result: ADAuthenticationResult!) in
    guard (authError == nil) && (result.accessToken != nil) else {
        // Authentication Error
        return
    }

    bearerToken = result.accessToken
}

The Swift code above is pretty simple to understand :

  • We declare an authentication context
  • We call the acquireTokenWithResource method with required parameters (clientId and redirectUri are the same you filled in step #1)
  • If the token was successfully acquired, we store it in a variable to use it later

Use the Yammer REST API

Now that we have a valid authentication token, we are now able to use the Yammer REST API. If you need some information about all the capabilities available in that API, you can refer to the official documentation : https://developer.yammer.com/v1.0/docs/userscurrentjson

In Swift, we are able to use all the frameworks (Foundation, AppKit, UIKit…) available on iOS/OS X and which allow us to perform advanced operations in a simple manner.

To perform HTTP communications, we can use the NSURLSession class. With just few lines of code, we are able to send a request to a server (synchronously or asynchronously) and to retrieve the response.

let URLSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSMutableURLRequest(URL: NSURL(string: "https://www.yammer.com/api/v1/users/current")!)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(bearerToken, forHTTPHeaderField: "Auhtorization")
let task = URLSession.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) in
    guard (data != nil) else {
        // No data returned
        return
    }
    processResult(data)
}
task?.resume()

The most important part in the code above, to authenticate the request, concerns the addition of an Authorization header to the request. The header is a concatenation of the token retrieved in step 2 with the Bearer keyword such as “Authorization: Bearer our_authentication_token“.

If the request is successfully executed (it returns data), then we call the processResult method by passing the retrieved data as parameter.

Deserialize JSON response and catch errors

The last step to perform in our application is to parse the retrieved data. Because we have passed an Accept header to the previous request to indicate that we want to retrieve the data as JSON, we just have to deserialize and to use them.

Once again thanks to Foundation which expose a class (NSJSONSerialization) to perform this operation.

If you used Swift 1.x during the last year, focus your attention on the code below. A brand new way to deserialize and catch potential errors has been introduced with Swift 2.0 (do … catch).

func processResult(data: NSData?) {    
    do {
        let JSONObject = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
        print(JSONObject, appendNewline: true)
    } catch let error as NSError {
        print(error, appendNewline: true)
    }
}

In the code above, we just print the deserialize object so it should display something like that :

{
    "activated_at" = "2015/01/01 01:01:00 +0000";
    admin = true;
    "birth_date" = "";
    "can_broadcast" = true;
    "can_browse_external_networks" = 1;
    "can_create_new_network" = 1;
    contact = { ... };
    department = Direction;
    email = "...";
    expertise = "Office 365 REST API, SharePoint, iOS, Objective-C, Swift";
    "external_urls" = ();
    "first_name" = "Stéphane";
    "follow_general_messages" = 0;
    "full_name" = "Stéphane Cordonnier";
    guid = "<null>";
    id = 123456789;
    interests = "<null>";
    "job_title" = "Technology Addict";
    "kids_names" = "<null>";
    "last_name" = Cordonnier;
    location = Paris;
    ...
}

If you want to use and parse the deserialized object, it’s a key/value dictionary so it’s pretty simple to use it.

Search videos using the Office 365 Video REST API

Office 365 is an awesome solution to share videos in a dedicated portal, organized in different channels. Channels are used (for example) to split videos by themes or categories.

But if you create a lot of channels in your organization or if you have a lot of videos in each channel, it may be pretty difficult to find relevant videos.

If you want to create an application on top of Office 365 Video, it’s interesting to know that the REST API exposes capabilities to search videos, globally at the service level, or at the channel level.

Build and submit search queries

When you want to use the Office 365 Video REST API, you have to submit requests to the endpoint which is only accessible through the portal URL as you can see below.

https://tenant.sharepoint.com/portals/hub/_api/VideoService

As we previously said, this endpoint exposes search capabilities at two different levels :

  • Video Service (across all channels)
  • Channel

Depending on which level you want to search videos, you have to build requests by appending path components as you can see below :

Across all channels

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Search/Query

On a specific channel

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Search/Query

Add parameters to the querystring to specify search criterias

To retrieve only information that you want, you have to add some parameters to the querystring. Three possibilites are offered to you :

  • querytext
  • startItemIndex
  • itemLimit

As you can imagine, the most important parameter is querytext which allows you to make fulltext searches. startItemIndex and itemLimit are used to perform paging operations.

For example if you want to search the first 10 videos which contains the ‘Awesome’ word, across all channels, your request should look something like that :

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Search/Query?querytext='Awesome'&itemLimit=10

In the same manner, if you want to retrieve the same videos on a specific channel, your query should look something like that :

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Search/Query?querytext='Awesome'&itemLimit=10

By querying those URLs, you will retrieve a list of videos. Each video is described by the following structure :

<entry>
<id>https://tenant.sharepoint.com/portals/hub/_api/VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')</id>
<category term="SP.Publishing.VideoItem" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="edit" href="VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Author" type="application/atom+xml;type=entry" title="Author" href="VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')/Author" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Owner" type="application/atom+xml;type=entry" title="Owner" href="VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')/Owner" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/PeopleInMedia" type="application/atom+xml;type=feed" title="PeopleInMedia" href="VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')/PeopleInMedia" />
<title />
<updated>2015-01-01T01:00:00Z</updated>
<author>
  <name />
</author>
<content type="application/xml">
  <m:properties>
    <d:ChannelID m:type="Edm.Guid">01234567-abcd-cded-1234-1234567890ab</d:ChannelID>
    <d:CreatedDate m:type="Edm.DateTime">2015-01-01T01:00:00Z</d:CreatedDate>
    <d:Description />
    <d:DisplayFormUrl>https://tenant.sharepoint.com/portals/community/pVid/Forms/DispForm.aspx?ID=1</d:DisplayFormUrl>
    <d:FileName>AwesomeVideo.mp4</d:FileName>
    <d:OwnerName>Stephane Cordonnier</d:OwnerName>
    <d:ServerRelativeUrl>/portals/community/pVid/AwesomeVideo.mp4</d:ServerRelativeUrl>
    <d:ThumbnailUrl>https://tenant.sharepoint.com/portals/community/pVid/AwesomeVideo.mp4.PNG?VideoPreview=1</d:ThumbnailUrl>
    <d:Title>My Awesome Video</d:Title>
    <d:ID m:type="Edm.Guid">01234567-abcd-cded-1234-1234567890ab</d:ID>
    <d:Url>https://tenant.sharepoint.com/portals/community/pVid/AwesomeVideo.mp4</d:Url>
    <d:VideoDurationInSeconds m:type="Edm.Int32">120</d:VideoDurationInSeconds>
    <d:VideoProcessingStatus m:type="Edm.Int32">2</d:VideoProcessingStatus>
    <d:ViewCount m:type="Edm.Int32">10</d:ViewCount>
    <d:YammerObjectUrl>https://tenant.sharepoint.com/portals/hub/_layouts/15/videoplayer.aspx?v=https%3A%2F%2Ftenant%2Esharepoint%2Ecom%2Fportals%2Fcommunity%2FpVid%2FAwesomeVideo%2Emp4</d:YammerObjectUrl>
  </m:properties>
</content>
</entry>

Retrieve popular videos across all channels, or for a specific channel

It’s great to be able to search for videos but if you use the web version of Office 365 Video, you probably saw that the homepage displays popular videos. I guess you wonder how to retrieve those videos ?

The search endpoint mentioned above has special syntax to get those data. If you want to retrieve popular videos globally or for a specific channel, you have to submit requests using the following URLs:

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Search/Popular
https://tenant.sharepoint.com/portals/hub/_api/VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Search/Popular

As for simple searches, you can use startItemIndex and itemLimit to perform paging through popular videos.

If you want more information on the Office 365 Video REST API, you can read the official documentation on the MSDN website : https://msdn.microsoft.com/office/office365/APi/video-rest-operations.

AADSTS90093 – Calling principal cannot consent due to lack of permissions

When you develop your own applications which use the Office 365 REST API, it might happen that users are facing the following error message when they try to authenticate, when your application is supposed to ask the consent of the user to access his data.

AADSTS90093 - Authentication Error

The difficult thing to understand is why this message is displayed and why all users are not concerned ?

Declare the application’s rights in Azure AD

When you create an application, you have to declare which rights are needed to access data when you make calls to the REST API.

Those rights are declared in Azure AD through the web console (https://manage.windowsazure.com) as you can see below.

Azure Manage Portal - Application Permissions

Which is not indicated in the web console is that some of those rights need that an administrator (at the tenant level) give a consent to allow your application to use them.

If the consent of an administrator was not given and a non-administrator user tries to use the application, he will receive the following error message : AADSTS90093 – Calling principal cannot consent due to lack of permissions.

What permissions require an administrator consent ?

If you would like a complete description of how permissions work on Office 365, you could refer to the official documentation on MSDN : https://msdn.microsoft.com/office/office365/HowTo/application-manifest

In short if you want to know what permissions require an administrator consent, it depends of which product  and which features you want to use.

You can find below a quick summary (grouped by products) of all the permissions which require an administrator consent. All other permissions (not listed below) only require a user consent.

SharePoint

  • Have full control of all site collections
  • Run search queries as a user
  • Read user profiles
  • Read and write user profiles
  • Read managed metadata
  • Read and write managed metadata

Outlook

No permission requires an administrator consent

Azure Active Directory

  • Read all users’ full profiles
  • Read directory data (except if the application is registered in the same tenant as the user)
  • Read and write directory data
  • Access the directory as the signed-in user (only for web applications)

Yammer

No permission requires an administrator consent

What to do if I encounter the error message ?

If you are facing to the error message mentioned previously, the first thing to do is to check if your application uses one of the permission in the list above.

If it’s the case, make sure that you absolutely need it, otherwise remove the right from the declared permissions for your application in Azure AD and it should solve the problem.

For example, you can perform searches on SharePoint sites under the identity of the current user, even if you only have the “Read items in all site collection” permission. It’s not needed to add the “Run search queries as a user” permission.

If you need the permission, you have to ask a tenant administrator to consent your application. Until the administrator consent, all users which are not administrators of the Office 365 tenant won’t be able to log-in and use your application.