Contents tagged with WOPI

  • SharePoint 2013, Office Web Apps 2013 and Excel files with Data Connections and Secure Store

    Tags: SharePoint 2013, Office Web Apps, WOPI

    Introduction

    This is a follow-up post on the SharePoint 2013, Office Web Apps 2013 and Excel files with Data Connections post that I previously wrote. That post talked about how you needed to do, so called, WOPI Suppressions if you had Excel files with Data Connections and had those data connections configured to use the authenticated users account. The WOPI Suppression made sure that the rendering of the Excel book was done by Excel Services in SharePoint 2013 rather than with Office Web Apps 2013.

    But if you have Excel documents with external data and do not rely on delegating the account (and don’t do the Negotiate, SPN and delegate dance), but instead like to store the credentials to the data in the connection string (not recommended) or like to take advantage of the Secure Store in SharePoint 2013 – then you actually have the option to not do any WOPI Suppressions. Let’s take a look on how you can get this to work: an Excel sheet with an external data connection using Secure Store to store credentials and then using Office Web Apps 2013 to refresh the data.

    Configuring the Secure Store

    Configuring a Secure Store ApplicationFirst of all we need to have the Secure Store Service Application up and running in our SharePoint farm. Once that is done and you have generated the encryption key, we need to add a new Target Application. To create the Target Application use the New button in the Ribbon. This will take you to a wizard. First of all we need to give the Target Application an ID, this is what we later use to reference this application from Excel (and other sources). Also give it a descriptive name as well as a contact. Then we have to choose what type of Target Application Type to use. Use Individual or Group, where Group is preferred from a management perspective. If you choose Individual you have to set the credentials for each user individually and for Group you have one set of credentials per application.

    On the next wizard page you set up the “fields” for the application. By default it assumes that it is Windows credentials that we would like to store, if that is the case you only need to click Next. Otherwise, if you’re using for instance SQL credentials, you have to set up two new fields using the User Name and Password type of fields. On the final page you give permissions to the Target Application itself and if it is a Group application then you also configure what users/groups that are mapped to the credentials.

    When the application is configured we need to set the credentials for it. For a Group Application you use Central Administration or PowerShell to set the credentials to be used by the group of users. For Individual Applications you need to set the credentials for each and every user through Central Admin or create some custom Web Part or similar that the user can use to fill in it’s credentials. Fortunately most of the plumbing is already done for this. You can use the Layouts page called securestoresetcredentials.aspx to allow the user to set their credentials. This is how it is used:

    /_layouts/15/securestoresetcredentials.aspx?TargetAppId=AdventureWorks

    You have to pass the TargetAppId as an argument and set the value to the Application ID of the application. Good to know here is that if you don’t use SSL/TLS (HTTPS) on you SharePoint sites you will get messages that will annoy your users, since this data will be sent in clear text. Just another reason to do HTTPS :-)

    By now we should be all set to continue to the next step.

    Create the Excel book with the Data Connection

    Excel Data Connection propertiesNow it is time to create our Excel book. To do this we just add a data connection as normal but in the connection wizard we click on the Excel Services: Authentication button and then choose to use a stored account. In the stored account input box we have to enter the Secure Store Target Application ID, the one we created in the Secure Store Service Application above. Everything else is precisely as normal.

    Now this workbook is ready to be uploaded to a document library in SharePoint. Good thing here is that now you don’t have to do anything more – no SPN’s, no nothing!

    Refresh data using Office Web Apps 2013

    So, let’s see if this works with Office Web Apps 2013! If you fiddled with the WOPI Suppressions as in my previous blog post, make sure to remove them so we have WAC rendering the Excel Sheet instead of Excel Services (this will work in Excel Services as well, but that is not the purpose of this post).

    Click on the document so that it renders in the browser, make sure to update some data in the external data source so that you can see that the data is actually refreshed, then use Data > Reload All Data Connections to update the sheet. Everything should work fine here, your data should be refreshed.

    So, there it is you can actually use Office Web Apps 2013 to render and refresh external data – only thing is that you can’t use any delegation features.

    Troubleshooting

    Didn’t it work? Of course things can go wrong. But a correctly configured environment should work immediately. Here are some of the common errors that you can get:

    External Excel data connections disabled in Office Web Apps

    Your admins (or you) might have turned of the Excel external data. You can check this by running the following command on a WAC machine:

    (Get-OfficeWebAppsFarm).ExcelAllowExternalData

    If it returns false, then you have turned it off. By default when configuring a new farm it is set to true. If set to false and you want to use external data then use the following command:

    Set-OfficeWebAppsFarm -ExcelAllowExternalData

    Note that it can take a couple of minutes before changes like this actually is propagated in the WAC farm.

    If this is your problem then you’re getting an error like this:

    “The trusted location where the workbook is stored does not allow external data connections.”

    The trusted location where the workbook is stored does not allow external data connections

    You’re running SharePoint over HTTP!!!

    The most common problem is that your SharePoint is still using HTTP and not HTTPS (when will you ever learn!). If that is the case you will get an error like follows:

    “An error occurred while accessing application id XXX from Secure Store Service.”

    An error occurred while accessing application id XXX from Secure Store Service.

    This is perfectly normal and good! Since the SharePoint farm is accessible through HTTP this also means that Office Web Apps Server will try to retrieve the credentials over HTTP – in clear text, and you don’t want that.

    But if you are running a development environment that uses HTTP it could be all right to work around this. Or you might be running IPSec (fat chance…) then it also ok to allow this. Otherwise I do recommend you to change your SharePoint content web application to use HTTPS or don’t just use the Secure Store and WAC.

    Anyhow, this is how you configure WAC to allow retrieval of credentials over HTTP:

    Set-OfficeWebAppsFarm –AllowHttpSecureStoreConnections

    Once this command is executed, wait a minute, reload the workbook and you’re golden.

    No credentials configured

    Another common problem, common when using Individual Secure Store applications, is that the application does not have any credentials set. The error you will see then is the following:

    “An error occurred during an attempt to establish a connection to the external data source.”

    An error occurred during an attempt to establish a connection to the external data source

    To fix this you have to set the Group credentials, if it is a Group application type, or make sure that all users set their individual credentials.

    Summary

    You have now seen that Office Web Apps 2013 can actually render external data, including refreshing it, using the SharePoint 2013 Secure Store service. Just be careful with the security issues I highlighted with SSL/TLS/HTTPS. All this will also work with plain ol’ Excel Services if you need to combine it with delegation.

  • Introducing Open WOPI - an open WOPI Client for SharePoint, Exchange and Lync

    Tags: SharePoint 2013, WOPI, Office Web Apps, Open WOPI

    Today at the SharePoint Evolutions 2013 Conference I announced my latest pet project called Open WOPI. Open WOPI is an open WOPI client that allows you to extend SharePoint 2013, Exchange 2013 and Lync 2013 with file previews and editors for any type of file formats.

    Open WOPI

    The project is now (at least very, very soon) available to download from openwopi.codeplex.com and is published under the Ms-PL license. This is currently an early beta (or what you would like to call it) but will be improved over time.

    Standards

    Open WOPI is based on the [MS-WOP] protocol, published by Microsoft, and used by Office Web Apps Server 2013, SharePoint 2013, Exchange 2013 and Lync 2013.

    File format support

    Currently Open WOPI has support for the following formats:

      • GPX - Uses Bing maps
      • TXT - Viewing and editing
      • VSDX - Thumbnail viewing

    More formats are in the works…

    Documentation

    Not much yet, but I’ll try to add that over the next few weeks.

    More information

    For more information, feedback and ideas about the project please refer to the Codeplex site: openwopi.codeplex.com. I’d like to hear what file formats you would like Open WOPI to support.

    The slides from the presentation at the SharePoint Evolutions conference and links can be found here.

    Contributors

    Thanks to Sam Dolan, aka Pinkpetrol, for the really cool logo for Open WOPI.

  • SharePoint 2013: Building your own WOPI Client, part 4, now Search enabled

    Tags: SharePoint 2013, WOPI, WAC Server

    Well, I thought I should write another episode of my Building your own WOPI Client series, here’s the links to the previous episodes part 1, part 2 and part 3. This time around we’ll dig into another of the different actions that a WOPI Client can surface – the interactivepreview mode.

    Background

    As you’ve seen in the previous posts we can build a viewer and an editor for C# files, to be used in document libraries for instance. What if we would like to lift up and enhance our custom file formats in search, just like Office Web Apps Server does with the Office files. We’ll you can do that very easy, and you should! In this post I’ll show you how to surface the preview mode in a Search flyout. This also means that we’re going to take a look at the new SharePoint 2013 Search Engine, the Design Manager and some funky HTML syntax.

    Configure indexing for C# files

    The first thing we need to do is to actually make SharePoint indexing our C# (.cs files). This is done in two steps. First we need to tell SharePoint 2013 Search to include the .cs file extension and then we need to tell the search engine what filter to use. To include .cs files in the index there is two ways to do it, using PowerShell or using the Central Administration. In Central Administration you go to the Search Service Application and choose File Types, then click on New File Type and type in cs. That’s it. Using PowerShell the following snippet works (as long as you only have one SSA):

    $ssa = Get-SPServiceApplication | ?{$_.TypeName -eq "Search Service Application"}
    New-SPEnterpriseSearchCrawlExtension -name cs -SearchApplication $ssa

    Once that is done we need to fiddle a bit with the registry (usual regedit disclaimers apply here). Start regedit.exe and navigate to HKLM\SOFTWARE\Microsoft\Office Server\15.0\Search\Setup\ContentIndexCommon\Filters\Extensions and choose to add a new Key. Give the key the name “.cs” (without quotations) and then set the default value to the following (fancy crafted) Guid: {0FF1CE15-002C-0000-0000-000000000000}. All you now have to do is to restart the Search Engine like using the PowerShell below and then start a full crawl:

    Restart-Service OSearch15

    Now when you search for any content in your .cs files it should look something like this:

    Boring search result

    It’s something, but not what we want…

    Building the WOPI Preview

    Before we start customizing the UI let’s build the interactiepreview mode of the WOPI client. Just as we did in the previous post, when we added the edit mode, we need to add a new action to the discovery file and then remove and add the bindings again (for that PowerShell, see previous post). This is the action I’ve added to my WOPI Client:

    <action name="interactivepreview"
            ext="cs"
            default="false"
            urlsrc="http://wacky.corp.local/csharp/preview.aspx?&lt;ui=UI_LLCC&amp;&gt;&lt;rs=DC_LLCC&amp;&gt;" />

    As you can see that the urlsrc points to a preview.aspx web form. That web form is essentially a copy of the viewer.aspx, with the exception of the menu which I have removed. In a real world scenario you might consider having very different viewers for the normal view mode and the interactivepreview mode. You want this process to be fast and you might just generate a thumbnail or something.

    When all is compiled and deployed, and you have removed and re-added the WOPI bindings, it’s time to hook this up to search.

    Creating the Display Templates

    Finding the Design ManagerLet’s build the search interface. The SharePoint 2013 Search Interface does not any longer require extensive XSLT skills but instead plain ol’ HTML hacking. Every Result Type in SharePoint 2013 Search can have it’s own Display Templates, and there’s two of them; the normal item view and the flyout/hover view. The easiest way to create new Display Templates is to copy an existing one. For this sample let’s use the Word Display Templates.

    You will find the Display Templates in the new Design Manager (which exists in a Search Center or a site with the Publishing features enabled). You access the Design Manager through the Site Settings menu. Once in the Design Manager you should choose Display Templates in the menu on the left hand side. Then I usually filter on Target Control Type, to narrow down on the Display Templates used for Search; choose SearchResults and SearchHoverPanel. Now scroll down to the Word Item and Word Hover Panel. You’ll see two of each but we’re only interested in the HTML files for now. Download those two files to your local disc and name them Item_Cs.html and Item_Cs_HoverPanel.html respectively.

    The Word Display Templates

    Now open these two files in your favorite HTML tool – for me that is Visual Studio 2012. There’s a lot of weird stuff in here as you will notice, lots of comments and JavaScripts. I’m not digging into all the details here, that’s for Randy Drisgill or someone else to do.

    Let’s do some simple string replacement. In both files replace “Word” with “Cs” as the first thing. Next update the mso:MasterPageDescription to something more appropriate and the title tag, perhaps replace “Microsoft Word” with “C#”. In the Item_Cs.html file  locate the second div having an id equal to "_#= $htmlEncode(itemId) =#_" and remove everything inside it and replace with this instead:

    <div class="ms-srch-item-title">
      <h3 class="ms-srch-ellipsis">
        <a class="ms-srch-item-link" href="_#= ctx.CurrentItem.Path =#_">_#= ctx.CurrentItem.Title =#_</a>
      </h3>
    </div>
    <div class="ms-srch-item-body">
       _#= Srch.U.processHHXML(ctx.CurrentItem.HitHighlightedSummary) =#_
    </div>
    <div id="_#= $htmlEncode(hoverId) =#_" class="ms-srch-hover-outerContainer"></div>

    What this does is that we’re first rendering the title of the document with the value ctx.CurrentItem.Title. All these JavaScript variables are enclosed in __#= … =#_. Then we output the hit highlighted summary using a built-in method (Srch.U.processHHXML). Finally there’s an empty div which will contain the flyout (The flyout is defined in the Item_Cs_HoverPanel.html file). There is one final thing that we need to do and that is to update the hoverUrl  JavaScript variable in this Item_Cs.html file. It should point to the Item_Cs_HoverPanel.js file like this:

    var hoverUrl = "~sitecollection/_catalogs/masterpage/Item_Cs_HoverPanel.js";

    Notice that it points to a JS file, not the HTML file. And also that it’s located directly in the masterpage gallery and not in a subfolder (compared to the original Word html file).

    The Item_Cs_HoverPanel needs no more changes than replacing Word with Cs and updating the title and description.

    To add these Display Templates to the Design Manager, just drag and drop them into your browser where you downloaded the original Display Templates. The files will end up as Draft version so go ahead and publish both of the HTML files.

    The Display Templates

    Once the files are approved, the Design Manager will create two JS files from these HTML files and that’s what’s used behind the scenes when rendering the search results.

    Create a Result Type

    The final thing we have to do is to tell the Search Center to actually use our newly crafted Display Templates when finding C# files. For that we need to create a new Result Type. Go to Site Settings and choose Search Result Types under Site Collection Administration. Click on New Result Type to start creating it. Give the Result Type an appropriate name, for instance “C# Files”. Under Conditions choose Show more conditions and select the FileExtension property and set it so that it “Equals any of…” and enter “cs” in the text box. Under Actions choose the C# item in the drop down and then simply click Save. You should now have a Result Type looking like this:

    The C# Result Type

    Test the stuff…

    All that is left is now to test if all this works. Go to the search page of you Search Center and search for something you know exists in your C# files, that by now should have been indexed. You should then see something like this:

    The WOPI Client in the search result

    You see the items (using the Item_Cs Display Template) and if you hover over one of the search results then you see the preview from the WOPI Client being generated (using the Item_Cs_HoverPanel). Isn’t that nice, huh?

    Summary

    In this post you’ve seen yet another way to leverage a WOPI Client, this time in search, using a preview mode of the files. You have also seen how “easy” it is to generate a custom search result design using the Design Manager, Display Templates and Result Types. I’m really looking forward to seeing WOPI Clients for all kinds of different formats out there…

  • SharePoint 2013: Building your own WOPI Client, part 3

    Tags: SharePoint 2013, WOPI, WAC Server

    This is part three (and counting) of my Building your own WOPI Client series. In part 1 I discussed the WOPI Protocol and especially how to implement the Discovery process of a WOPI Client. In part 2 we built a viewer application as a WOPI Client and connected it to SharePoint. In this part we’re modifying our WOPI Client to support basic editing of the files.

    Modyfing the WOPI Client Discovery data

    The first thing that we need to do is to modify our Discovery method, in our case the static XML file, to tell the WOPI Server that we support editing of the files. It’s a simple procedure and we just add another line of XML like this to the app element:

    <action name="edit"
        ext="cs"
        default="false"
        urlsrc="http://wacky.corp.local/csharp/editor.aspx?&lt;theme=THEME_ID&amp;&gt;" />
    

    This action has the name set to edit, to tell the WOPI Server that we now support editing of cs files. The default attribute is set to false (the view mode is the default one) and we set the urlsrc to point to our editor.aspx file (that we’ll build in a minute).

    Once this is done, the WOPI Server will not pick that change up automatically. Instead we need to manually refresh the WOPI Bindings in SharePoint by removing them and then add them back again:

    Remove-SPWOPIBinding -Server wacky.corp.local
    Add-SPWOPIBinding -Server wacky.corp.local -AllowHTTP

    An IISRESET might be necessary for SharePoint to pick this up.

    Modifying the Viewer

    To be able to switch to Edit mode from the viewer we slightly update the user interface to include an Edit button so that it looks like below. As you can see I also added a Close button, which just return the user to the Document Library where the file resides.

    image

    We’re not covering the HTML and CSS for this, but this is two asp:HyperLink controls. In the Page_Load method, when retrieving the metadata for the document, the NavigateUrl for these links are set like this:

    Close.NavigateUrl = d["CloseUrl"];
    Edit.NavigateUrl = d["HostEditUrl"];
    Edit.Enabled = !bool.Parse(d["WebEditingDisabled"]);

    First I retrieve the CloseUrl from the document metadata and pass into the Close Hyperlink control, next I take the HostEditUrl and configure the Edit Hyperlink control. The URL in the HostEditUrl will contain the URL/urlsrc that is specified as the edit action in the discovery document. Finally I enable the Edit link if the WebEditingDisabled is set to false.

    imageIf we now take a look at the UI in SharePoint we have enabled a couple of ways to edit the document; from the viewer and also from the Document fly-out in the document libraries. Just as the WAC Server does with Office documents.

    Building the Editor

    Creating the editor is very much the same as the viewer. I created a Web Form called editor.aspx in the web project and copied the HTML from the viewer to the new editor.

    I modified the menu in the editor.aspx so that it has View and Save buttons instead of the Edit button. The View button takes the user to the WOPI Client viewer and the Save button saves the document back to the WOPI Server (SharePoint in this case). The Save button is an asp:LinkButton so that I can have a server side callback for that method.

    The Page_Load method in the editor should look like below. It is very similar to the viewer with a few exceptions:

    string _src;
    protected void Page_Load(object sender, EventArgs e)
    {
        _src = Request.QueryString["WOPISrc"];
    
        if (!String.IsNullOrEmpty(_src))
        {
            access_token.Value = Request.Form["access_token"];
            access_token_ttl.Value = Request.Form["access_token_ttl"];
    
            if (!this.IsPostBack)
            {
                // Get the metadata
                string url = String.Format("{0}", _src);
                using (WOPIWebClient client = new WOPIWebClient(url, access_token.Value, access_token_ttl.Value))
                {
                    string data = client.DownloadString(url);
                    JavaScriptSerializer jss = new JavaScriptSerializer();
                    var d = jss.Deserialize<Dictionary<string, string>>(data);
                    hlSite.NavigateUrl = d["BreadcrumbFolderUrl"];
                    hlSite.Text = d["BreadcrumbFolderName"];
                    lblDocument.Text = d["BaseFileName"];
                    Close.NavigateUrl = d["CloseUrl"];
                    View.NavigateUrl = d["HostViewUrl"];
                    Save.Enabled = (bool.Parse(d["SupportsUpdate"]) && !bool.Parse(d["ReadOnly"]));
                }
    
                // Get the content
                url = String.Format("{0}/contents", _src);
                using (WOPIWebClient client = new WOPIWebClient(url, access_token.Value, access_token_ttl.Value))
                {
                    string data = client.DownloadString(url);
                    textarea.InnerText = data;
                }
            }
        }
    }

    I’ve made a couple of changes compared to the viewer. First of all the access_token and access_token_ttl are now hidden input elements on the page instead of local strings, since we need to preserve those values between postbacks because they are needed on all requests back to the WOPI Server. When reading the metadata for the document I set the View button to get the HostViewUrl which is the WOPI Client view URL. Then finally I only enable the Save button if the document supports updates and is not read only. All this is only done if it’s not a postback.

    Instead of using the nice syntax highlighter for presenting the document we’re instead using a textarea control, in which we add the contents of the document.

    Saving the document

    To save the document we need an event handler on the Save button, like this:

    protected void Save_Click(object sender, EventArgs e)
    {            
        string url = String.Format("{0}/contents", _src);
        using (WOPIWebClient client = new WOPIWebClient(url, access_token.Value, access_token_ttl.Value))
        {
            client.Headers.Add("X-WOPI-Override", "PUT");
            try
            {
                client.UploadString(url, textarea.InnerText);
            }
            catch (WebException we) {
                HttpWebResponse response = we.Response as HttpWebResponse;
                ErrorPanel.Visible = true;
                switch (response.StatusCode)
                {
                    case HttpStatusCode.NotFound:
                        ErrorPanel.Controls.Add(new LiteralControl("File unknown/user unauthorized"));
                        break;
                    case HttpStatusCode.Unauthorized:
                        ErrorPanel.Controls.Add(new LiteralControl("Invalid token"));
                        break;
                    case HttpStatusCode.Conflict:
                        ErrorPanel.Controls.Add(new LiteralControl("Lock mismatch"));
                        break;
                    default:
                        ErrorPanel.Controls.Add(new LiteralControl("Unknown error"));
                        break;
                                
                }
            }                
        }
    }
    The code is pretty straight forward and most of it is exception handling. First of all the communication back to the WOPI Server are done on the same URL as when retrieving the document contents, with the difference that we’re using the HTTP POST method (UploadString method). To be able to update a document you must set the X-WOPI-Override header to “PUT”. If you don’t do this you will get a 404 error.

    As you can see I added error handling here as well. To the editor page I added an asp:Panel control which can be used to show any errors. For instance we need to handle cases when someone checked-out the file and set it to read only mode etc. For full information on all possible errors see the [MS-WOP] 3.3.5.3.2 specification.

    That was it, all coding should be done by now. Of course you could pimp the user interface with some nice “Saving..” animated gifs etc.

    Taking it for a test drive…

    If everything compiles fine it’s time to test the editor. Upload a .cs file to any document library and click on it to get to the viewer. From the viewer you should then be able to click on the Edit button and get to the editor. In the editor modify your file and click Save. Verify that your changes is preserved by clicking on the View button or Close the editor and go back to the viewer from the document library. The editor should look something like this:

    image

    Summary

    Now you’ve seen how easy we can build the editor, using the helper classes that we produced in part 2. From here on you should be able to build your own viewers and editors. Make sure that you read the [MS-WOPI] protocol specification so that you now how to handle all the different situations with locked files etc.

  • SharePoint 2013: Building your own WOPI Client, part 2

    Tags: SharePoint 2013, WOPI, WAC Server

    Welcome back to another part in my Building a WOPI Client series. In the previous and first post I walked you through the basics of the WOPI protocol, how the WOPI Discovery mechanism worked and how to implement it and finally how to register a WOPI Client with SharePoint 2013 as WOPI Server. In this post we’ll continue to build on our C# Viewer and now actually add the viewer – we ended the last post quite dull with just showing a simple Hello WOPI web page which we now are going to turn into a real C# viewer.

    The WOPI Protocol Specification (currently) lacks some details…

    The WOPI (Web Application Open Platform Interface) Protocol specification is currently in draft mode (version 0.2) and most of what is in this post is not defined in the specs, especially regarding the security and signing stuff. All the details in this post comes from extensive trial and error, and might be wrong – I’m trying to keep the post up to date once the specs are finalized.

    How the WOPI Client and WOPI Server interaction works

    Before we take a look at the code and how it’s implemented it is crucial to understand how the WOPI Client and WOPI Server talks to each other. When a user request to use one of the WOPI app actions the server will send a request to the WOPI Client on the address specified in the Discovery document (the urlsrc attribute specifies the address). The WOPI Server sends the “callback” address and access tokens to the WOPI Client so that the client can initiate contact and request the required resources to generate the WOPI Client interface. The callback URL is sent in the query string to the client and has the name WOPISrc,. It also sends an access token and its time to live as the request body. The WOPISrc URL has the following form HTTP://server/<...>/wopi*/files/<id> as described in the [MS-WOPI] 3.3.5. This URL is used by the client to request information about the document that it is going to generate the view for. For instance if we do an HTTP GET to the WOPISrc URL then we get metadata about the document, if we do an HTTP GET to the WOPISrc and appends “/contents” we get the actual content of the document or an HTTP POST to the “/contents” means that we’re updating the document.

    WOPI Security

    We cannot just request the resources as mentioned above, the WOPI client must also pass the access token, given to the client from the server, to make sure that the WOPI Client just don’t try to access files it doesn’t have access to. The access token must be sent as a query string parameter to the WOPI Server and as the Authorization header of the HTTP request. When building a WOPI Client we don’t have to care about what’s in the actual access token, just that it’s there and that we need to pass it on every request. But this is not enough we also need to send the X-WOPI-Proof header back to the WOPI Server. Remember from the last post that we specified a proof-key (which is the public version of the key), it is here that private key is used, to sign the X-WOPI-Proof header. Most of this is not yet in the [MS-WOPI] specs, so bare with me if I got something wrong. But hey, it works on my machine!

    Building a WOPIWebClient

    Since we need to add quite a few headers and query string parameters to each and every request from our WOPI client to the WOPI Server I have baked all this into a WebClient derivative, called WOPIWebClient. This class will make sure that all the necessary plumbing are there for us to make secure calls to the server:

    public class WOPIWebClient: WebClient
    {
        private readonly string _access_token;
        private readonly string _access_token_ttl;
        private readonly DateTime _utc; 
    
        public WOPIWebClient(string url, string access_token, string access_token_ttl)
        {
            _access_token = access_token;
            _access_token_ttl = access_token_ttl;
            _utc = DateTime.UtcNow;
        }
        protected override WebRequest GetWebRequest(Uri address)
        {
            UriBuilder builder = new UriBuilder(address);
    
            string append = String.Format("access_token={0}&access_token_ttl={1}", 
                _access_token, 
                _access_token_ttl);
    
            if(builder.Query == null || builder.Query.Length <=1){
                builder.Query = append;
            } else {
                builder.Query = builder.Query.Substring(1) + "&" + append;
            }
    
            WebRequest request = base.GetWebRequest(builder.Uri);
            if (request is HttpWebRequest)
            {
                // Add AuthZ header
                request.Headers.Add(
                    HttpRequestHeader.Authorization, 
                    String.Format("Bearer {0}", 
                        HttpUtility.UrlDecode(_access_token.Replace("\n", "").Replace("\r", ""))));
    
                request.Headers.Add(
                    "X-WOPI-Proof", 
                    WOPIUtilities.Sign(
                        WOPIUtilities.CreateProofData(builder.Uri.ToString(), _utc, _access_token)));
                    
                // TODO: Add X-WOPI-ProofOld here
    
                request.Headers.Add(
                    "X-WOPI-TimeStamp", 
                    _utc.Ticks.ToString(CultureInfo.InvariantCulture));
    
                request.Headers.Add(
                    "X-WOPI-ClientVersion",
                    "Wictor.WOPIClient.1.0");
    
                request.Headers.Add(
                    "X-WOPI-MachineName",
                    Environment.MachineName);
            }
            return request;
        }
    }

    Let’s walk through the code. First of all we have a constructor that takes three arguments; the url to use to call back to the WOPI Server and the access_token and the time to live value for the access token (access_token_ttl). Then I override the GetWebRequest method of the WebClient so that I can add the required headers and modify the URL before it sends it away. First of all in that overridden method we need to add the access token and the ttl to the query string (as access_token and access_token_ttl). Once that is done we can create the WebRequest and then add our headers. First of all is to add the Authorization header, that header must have the value of “Bearer <access_token>” (see [MS-WOPI] 2.2.1).

    It’s now where things starts to get tricky. We need to send the X-WOPI-Proof header to the server. This is a set of not so random bytes that are signed using our private proof key. In the code above I first create the proof using a utility method called CreateProofData and then sign the data using another utility method, called Sign. We’ll take a look at the implementation of those in a minute. If you are changing proof keys you should also add the old proof by using the X-WOPI-ProofOld header. After that I add the current time stamp in the X-WOPI-TimeStamp header.

    Finally I add a couple of other headers (also defined in 2.2.1) which are optional but might help you debug or troubleshoot your WOPI Client. When SharePoint is the WOPI Server you can see this data for all incoming requests in the ULS logs.

    Creating the proof data

    The proof data must be correctly generated otherwise the WOPI Server will not accept the request. The proof data is an array of bytes consisting of the access token, the requested url and the current timestamp, which we also send in clear text through the HTTP headers. Since the X-WOPI-Proof header is also signed before it is sent to the server, the WOPI Server can validate that no one tampered with the url or token. This is how the proof data is created:

    public static byte[] CreateProofData(string url, DateTime time, string accesstoken)
    {
        UTF8Encoding encoding = new UTF8Encoding();
        byte[] accessbytes = encoding.GetBytes(
            HttpUtility.UrlDecode(accesstoken));
        byte[] urlbytes = encoding.GetBytes(
            new Uri(url).AbsoluteUri.ToUpperInvariant());
        byte[] ticksbytes = getNetworkOrderBytes(time.Ticks);
    
        List<byte> list = new List<byte>();
        list.AddRange(getNetworkOrderBytes(accessbytes.Length));
        list.AddRange(accessbytes);
        list.AddRange(getNetworkOrderBytes(urlbytes.Length));
        list.AddRange(urlbytes);
        list.AddRange(getNetworkOrderBytes(ticksbytes.Length));
        list.AddRange(ticksbytes);
        return list.ToArray();
    }

    Using the access token, URL and time/ticks we convert them to byte arrays and add them to a list of bytes, each byte array is prefixed with the length of the byte array. Finally the list is casted into one big byte array – this is our proof that will be signed. Also worth to notice is that the Ticks and length bytes has to be converted to the reversed order, that is flip the byte order significance. For that I use a method, with two overloads, like this:

    private static byte[] getNetworkOrderBytes(int i)
    {
        return BitConverter.GetBytes(IPAddress.HostToNetworkOrder(i));
    }
    private static byte[] getNetworkOrderBytes(long i)
    {
        return BitConverter.GetBytes(IPAddress.HostToNetworkOrder(i));
    }

    Signing the proof data

    To make sure that the data is not tampered with or created by some fake WOPI client, we need to sign this proof data. This is done using the private key we generated in the first post. We used the following script to generate the proof-key (used by the WOPI Server to verify the signed data) and the data with a private key that is used to sign the proof data:

    $crypt = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048
    $proof = [System.Convert]::ToBase64String($crypt.ExportCspBlob($false))
    $proofwithkey = [System.Convert]::ToBase64String($crypt.ExportCspBlob($true))
    $proof
    $proofwithkey
    

    In our signing method we’ll use the $proofwithkey value, like this (the actual data is cropped for obvious reasons):

    public static string Sign(byte[] data)
    {
        using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048))
        {
            provider.ImportCspBlob(
                Convert.FromBase64String("BwIAAACkA...yMp3k="));
            var signed = provider.SignData(data, "SHA256");
            return Convert.ToBase64String(signed);
        }
    }

    This method takes the byte array (proof data) as input, encrypts it using our private proof key and then returns the data as a Base64 encoded string. This is demo code and the private proof key is in this case hardcoded – in a production system you would like to have this stored in a secure and manageable store.

    The VS2012 SolutionThat is all that is required to make a WOPI WebClient, now let’s put it into use…

    Building the UI

    To build the C# Viewer I decided to use Syntax Highlighter by Alex Gorbatchev, so I downloaded the latest drop and added the script and CSS files to the Web project for the WOPI Client. I also added a custom CSS that I use for some custom UI styling (remember that I’m a kick ass UI designer as well!).

    Next up is to create our user interface. The viewer.aspx file that we created in the previous post with just some dummy text is next up on the agenda.

    First of all is to add the scripts and CSS files into the head element, like this:

    <head runat="server">
        <title></title>
        <link href="/styles/WOPIClient.css" rel="stylesheet" />
        <script type="text/javascript" src="/scripts/shCore.js"></script>
        <script type="text/javascript" src="/scripts/shBrushCSharp.js"></script>
        <link type="text/css" rel="stylesheet" href="/styles/shCoreDefault.css"/>
        <script type="text/javascript">SyntaxHighlighter.all();</script> 
    </head>

    The final script row in the snippet above is used to start the syntax highlighter.

    The actual UI is composed of a header with some document information and then an area where the document contents should be shown, using the syntax highlighter. This is how the body of the viewer.aspx looks like:

    <body>
        <form id="form1" runat="server">
            <div id="topdiv">
                <asp:HyperLink ID="hlSite" runat="server" Text="Site"/>  / 
                <asp:Label ID="lblDocument" runat="server" Text="Document.cs"/>
            </div>
            <div id="surface">
                <pre class="brush: csharp" style="overflow:auto;overflow-y:scroll">
                    <asp:Literal ID="litCode" runat="server" />
                </pre>
            </div>
        </form>
    </body>

    The topbar contains a Hyperlink control that we will use to link back to the library where the document resides and it also contains a Label control which will show the document name. The document area just contains a simple Literal control where we will output the C# file contents.

    Now onto the code behind implementation of this viewer.aspx, this is how it looks like:

    protected void Page_Load(object sender, EventArgs e)
    {
        string src = Request.QueryString["WOPISrc"];
                
        if (!String.IsNullOrEmpty(src))
        {
            string access_token = Request.Form["access_token"];
            string access_token_ttl = Request.Form["access_token_ttl"];
    
            // Get the metadata
            string url = String.Format("{0}", src);
            using (WOPIWebClient client =
                 new WOPIWebClient(url, access_token, access_token_ttl))
            {
                string data = client.DownloadString(url);
                JavaScriptSerializer jss = new JavaScriptSerializer();
                var d = jss.Deserialize<Dictionary<string, string>>(data);
                hlSite.NavigateUrl = d["BreadcrumbFolderUrl"];
                hlSite.Text = d["BreadcrumbFolderName"];
                lblDocument.Text = d["BaseFileName"];
            }
    
            // Get the content
            url = String.Format("{0}/contents", src);
            using (WOPIWebClient client = 
                new WOPIWebClient(url, access_token, access_token_ttl))
            {
                string data = client.DownloadString(url);
                litCode.Text = data;
            }
        }
    }

    The Page_Load method starts by retrieving the WOPISrc URL which is the URL that we will use to call back into the WOPI Server. The next thing it fetches is the access_token and access_token_ttl query form values – these are sent back with the request to the WOPI Server (as we discussed above).

    We are doing two calls to the WOPI Server. The first one, HTTP GET, using the exact WOPISrc URL to retrieve the metadata about the document. The WOPI Server returns a JSON construct that I convert into a Dictionary so that we can retrieve the name of the library/folder and its URL as well as the name of the document. There are several more properties that can be used, for full reference of this data structure see [MS-WOPI] 3.3.5.1.1.2.

    The second HTTP GET call uses a modified URL, we append “/contents”. This will return back the actual document data from the WOPI Server. Since a C# file is just a text file, we’ll just set that data to the Literal control.

    This ends the coding and configuring of our WOPI Client, let’s see if it works!

    Test it!

    To test it you must first register your WOPI Client, unless you did it before. If you have changed anything in the discovery xml, then you need to re-register it. See the previous post on how to register and unregister the WOPI bindings.

    Once the client is registered, just upload a C# (.cs) file to any document library and then click on the title of the file. You should be redirected (if you just registered or re-registered the bindings an IISRESET might be necessary) to the WOPI frame and start the WOPI client and it should look something like this:

    A C# WOPI Client

    Mission accomplished, isn’t that beautiful!

    Summary

    You have now over two posts seen how to build a C# Viewer for SharePoint 2013 (and Exchange 2013 and Lync 2013) implemented as a WOPI Client, according to the [MS-WOPI] protocol specification. There’s a few moving parts but I hope that I have helped you through all the quirks, and by “borrowing” this code you should be up and running pretty fast.

    I see a big opportunity window here for ISV’s or other software companies to build viewers and editors for their own document/data formats. I know there is a huge demand of products like this, for instance for drawings such as AutoCAD or why not a viewer for Photoshop PSD files?

    I have a plan on what to do with the code that I have been using to build these demos, watch this space for more info. But before that I’ll add some more features to our WOPI client in the near future.

  • SharePoint 2013: Building your own WOPI Client, part 1

    Tags: SharePoint 2013, WAC Server, WOPI

    Hi friends, finally time for some posts with some real code samples, and not some silly scripts. In this post, and a couple of follow up posts, I will walk you through the basics behind the WOPI protocol and WOPI Apps and WOPI Hosts. In the end you will see how we can create our own viewers and editors for files just like the WAC Server 2013 can view and edit Microsoft Office files in SharePoint 2013.

    What the heck is this WOPI stuff you are talking about?

    Before we look into all my beautiful code, let’s go through some of the basics about the new WOPI protocol. WOPI stands for Web Application Open Interface and is a protocol created and published by Microsoft to allow direct file communication between a WOPI App and a WOPI Host. You can find the details in the [MS-WOPI] specification. Well, it’s not much details yet, since it’s a working draft currently (version 0.2), but give it some time, or keep on reading these posts. Unfortunately since the specification is not yet done, things in this post might change in the future.

    This is taken directly from the specification:

    One example of how a client might use WOPI is by providing a browser-based viewer for a specific type of file. That client uses WOPI to get the contents of the file in order to present that content to the user as a web page in a browser. [MS-WOPI 1.3]

    I think it’s more easier to explain by using an example. For instance, take SharePoint 2013 and Office Web App Server 2013 (aka WAC Server). SharePoint 2013 is a WOPI Host (WOPI Server in the specification), it can use WAC Server, the WOPI App (WOPI Client in the specification), to allow the users to view and edit files. The end user will initiate a request to view a document from the browser to the WOPI Host, the WOPI Host will query the WOPI Client for it’s capabilities (this process is called discovery). The user will then ask the WOPI Client to show the file, The WOPI Client will talk to the WOPI Host (hosting the actual documents) and get information about the file and eventually the actual contents of the file, and then finally render it so that the client can see it.

    This whole process involves quite a few HTTP(S) round trips and all requests are signed and verified to make the process secure. WOPI is a protocol built on top of HTTP/HTTPS and the only allowed ports are 80/HTTP and 443/SSL. I recommend you to always use SSL for any kind of production environment, even though I in these posts use HTTP/80 – but that’s just to make it easier to debug using Fiddler.

    So what are we going to build…

    The WAC Server is a great and scalable implementation that allows users to work with Microsoft Office documents. But what about other document types, shouldn’t we have the opportunity to enjoy in-browser viewing and editing of those? Well, that’s exactly what this blog post series is about. I see this as a huge area where ISV’s can plug into the SharePoint world and build native viewers and editors. For instance, imagine if Autodesk took their web base AutoCAD editor AutoCAD WS and made that into a WOPI Client. All companies in the construction industry would dance in joy!

    But what about us developers, don’t you all store your C# files in SharePoint, and how would it be with a nice C# viewer and editor in SharePoint. Well, that can be fixed. Follow along and let’s build it.

    But, this ain’t for the cloud! You can only do these kind of amazing thing on real tin!

    The WOPI Client outline

    First of all, to build our WOPI Client we need to create a web site, hosted on port 80 or 443. This website will contain the WOPI discovery mechanism and it will have a ASP.NET forms page which acts as the viewer. In this sample we’ll make it a bit simpler for us and only allow HTTP/80 access to the client.

    Setting up the project

    IIS Debug settingsTo start with I create a new Visual Studio 2012 project using the Empty ASP.NET project template. This project is configured to be hosted in IIS for which I use a dedicated FQDN host header. This way it’s easier to host it on the same server as SharePoint during development.

    Implementing WOPI Discovery

    The first thing we need to do is to implement the WOPI Discovery mechanism. This mechanism is used by SharePoint 2013 (the Host) when you use the New-SPWOPIBinding cmdlet to connect to your client. That cmdlet will discover all features supported by the WOPI Client and depending on what parameters you give to the cmdlet it will register these bindings with SharePoint.

    The Discovery fileWOPI Discovery is implemented by making sure that your WOPI client responds to http://server/hosting/discovery. The client must return a data structure that describes the capabilities of the WOPI Client. This can be done using numerous methods. Here we’ll do a static implementation and just add a couple of folders and an XML file to the project.

    Since we want it to respond to /hosting/discovery we just update the web.config so that discovery.xml is a default document, like this:

    <configuration>
    ...
      <system.webServer>
        <defaultDocument enabled="true">
          <files>
            <add value="discovery.xml"/>
          </files>
        </defaultDocument>
      </system.webServer>
    </configuration>
    

    Now it’s time to actually define the WOPI operations in the discovery.xml file created. This is how the discovery must look like for our C# viewer:

    <?xml version="1.0" encoding="utf-8"?>
    <wopi-discovery>
      <net-zone name="internal-http">
        <app name="CSharp" 
             favIconUrl="http://wacky.corp.local/images/csharp.ico" 
             checkLicense="false">
          <action name="view" 
                  ext="cs" 
                  default="true" 
                  urlsrc="http://wacky.corp.local/csharp/viewer.aspx?&lt;theme=THEME_ID&amp;&gt;" />
        </app>
      </net-zone>
      <proof-key oldvalue="" value="BwIAA...1w=" />
    </wopi-discovery>

    The first we need to define in the discovery XML is the net-zone which specifies the intended network type. The name must match the zone you have configured SharePoint 2013 to use. Look it up by using the Get-SPWOPIZone cmdlet.

    Note: in a real world scenario you would build this XML dynamically and generate a net-zone block for each zone you would like to support. There must be at least one net-zone element and max four of them.

    In each net-zone you will define a set of app elements. An app has a name, an icon and optionally a requirement for a license check. Note that I’m hardcoding the URL’s here, you would never do that of course and generate this XML dynamically instead. In each app element the operations that app supports are specified using action elements. Each action has a name, that name must be one of the following (the WOPI specification is a bit kinder though and forces the WOPI servers to accept any values): view, edit, mobileview, present, presentservice, attendservice, attend, editnew, imagepreview, interactivepreview, formsubmit, formedit and rest. We use view here, since we’re building a viewer (to start with). For the usage of each name, please look into the WOPI Specification, or follow along in this blog post series. The ext attribute specifies the extension of the files, default specifies in this case that this is the default action for this app. Finally we have the urlsrc attribute. That attribute represents the URL to be used by the WOPI Host when using this action on the app. The URL has a set of replaceable parameters that can be used. In this URL I’ve specified THEME_ID to be able to have a light or dark theme on the viewer. There’s a few more parameters to use.

    [Updated 2012-09-30] Finally in this WOPI discovery XML we have the proof-key element. In the snippet above I’ve cropped the value attribute since it’s normally quite a long base64 encoded string. This element is so far not documented in the specifications but the value corresponds to a certificate key which is used to sign and verify responses from the WOPI client, to make sure no one tampered with the data. Typically you would like this value to be created when you install your WOPI client, but here’s the way to generate a static value for this demo (in PowerShell). Also this key should be re-generated at regular intervals, and therefore the oldvalue attribute. You’ll also see how this key is used in the next blog post.

    $crypt = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048
    $proof = [System.Convert]::ToBase64String($crypt.ExportCspBlob($false))
    $proofwithkey = [System.Convert]::ToBase64String($crypt.ExportCspBlob($true))
    $proof
    $proofwithkey
    

    Take the value from the $proof variable and add to the proof-key value attribute in the discovery.xml, and save the value from $proofwithkey – you’ll need that in the upcoming posts. Simply explained the proof-key sent from the client to the server during the discovery process allows the server to verify all signed requests from the client.

    image

    That’s it, the WOPI Discovery implementation is done.

    Register the WOPI Client with the SharePoint 2013

    Even though we have not created our view yet, we can verify that the discovery works by trying to connect the WOPI client to SharePoint (our WOPI Host). This is done by using the New-SPWOPIBinding cmdlet, like this:

    New-SPWOPIBinding -ServerName wacky.corp.local -AllowHTTP

    Two things to notice here, I must specify the FQDN of the server name, without any ports (remember only 80 and 443 is allowed) and by default it tries to use HTTPS. To allow HTTP communication I need to add the AllowHTTP parameter. Once you run this command it will return all available WOPI Bindings. Note that any errors in the discovery definition will be reported as invalid XML exceptions. If you have not connected SharePoint to a WAC Farm, then this should be your only binding and it should look like this.

    We got the binding to work!

    If it looks like above, then your golden!.

    In case you want to remove your WOPI bindings you just use the Remove-SPWOPIBinding cmdlet like this:

    Remove-SPWOPIBinding -Server wacky.corp.local

    Some final fixes before we test it…

    In the discovery XML we specified an icon and a viewer page. Let’s add them. I borrowed the icon from the Visual Studio templates and added that to an /images folder and I added an empty Web Form to the /csharp folder. Just add some random text to it so you know it’s your file. The project looks like this after the additions:

    Final project structure

    Test it!

    Now let’s fire up SharePoint, add a CS file to a document library. Then click on the file and the WOPI Client should be invoked and your viewer should be shown. Notice the icon and that the title of the page is the name of your file.

    And we see the viewer...

    If you receive the standard Download File dialog when clicking the file after you’ve done your WOPI Binding then execute an IISRESET and wait a minute or two, SharePoint needs to recover from all you awesomeness!

    Summary

    In this post we’ve gone through the basics in how WOPI Servers and WOPI Clients works as well as started our implementation of our very own WOPI Client. You’ve seen how the WOPI discovery process works and how to register the WOPI Client with SharePoint 2013.

    In the next part I’ll show you how to actually create the viewer by reading the file from the WOPI Server following the WOPI protocol standard.

About Wictor...

Wictor Wilén is a Director and SharePoint Architect working at Connecta AB. Wictor has achieved the Microsoft Certified Architect (MCA) - SharePoint 2010, Microsoft Certified Solutions Master (MCSM) - SharePoint  and Microsoft Certified Master (MCM) - SharePoint 2010 certifications. He has also been awarded Microsoft Most Valuable Professional (MVP) for four consecutive years.

And a word from our sponsors...

SharePoint 2010 Web Parts in Action