Welcome to my blog, stay tunned :
Home | Blogs | Stephane Eyskens's blog

SharePoint 2013 - Integration Challenges - #5 Same Origin Policy - HTML5 postMessage

This blog post was moved to https://stephaneeyskens.wordpress.com/2014/01/31/sharepoint-2013-integra...


Now that you understand better the scenario, let's see how to achieve this.


Code of the sender page


The sender page is hosted in IBM Connections and it wil send a request to the SharePoint page to receive either the documents, either the presentations.

<!DOCTYPE html>
<html>
<head>
        
        


    <body>
     
     
      
     
</body> </html>

So, basically, it subscribes to the onmessage event to receive incoming calls which in our situation is to handle replies from SharePoint. It's checking the origin because messages can come from anywhere since the onmessage event is opened to the entire world :). It declares a hidden iframe that points to the SharePoint page. It declares the function that effectively performs the postMessage call. At last, the buttons call a common JavaScript function that sends the order (1 or 2) to SharePoint.
Remember that if your IFRAME targets a SharePoint page, you'll need to get rid of the X-FRAME-OPTIONS, go read this post if needed http://www.silver-it.com/node/149



Code of the receiver page


This code is a little bit more complex but this is due to the amount of things that need to be done on the SharePoint side.

<script type="text/javascript">
    "use strict";
    (function () {
        var SPObject = null;
        var CurrentUser = null;        
        Type.registerNamespace('SilverIT.SPIntegrationDemo');
        SilverIT.SPIntegrationDemo = function () {                        
            var ctx = new SP.ClientContext.get_current();
            var web = ctx.get_web();
            ctx.load(web);
            var user = web.get_currentUser();
            user.retrieve();
            ctx.executeQueryAsync(
                function () {
                    CurrentUser = user.get_loginName().substring(user.get_loginName().lastIndexOf('|') + 1);                                        
                },
                function (data) {
                    throw "Could not retrieve current user";
                });
        };

        SilverIT.SPIntegrationDemo.prototype = (function () {
            TransformSearchResults = function (results) {
                var JsonData = '{ "SearchResults":[';
                var Paths = JSLINQ(results)
                 .Select(function (item) {
                     return JSLINQ(item.Cells.results).Where(
                         function (r) { return r.Key === 'Path' || r.Key === 'Title'; }).Select(
                         function (item) { return item.Value });
                 });
                for (var p = 0; p < Paths.items.length; p++) {
                    if (p > 0)
                        JsonData += ',';
                    JsonData += '{"Title":"' + ((Paths.items[p].items[1] != '') ? Paths.items[p].items[1] : Paths.items[p].items[0]) + '",';
                    JsonData += '"Path":"' + Paths.items[p].items[0] + '"}';                    
                }
                JsonData += "]}";
                return JsonData;
            }
            return {            
                GetMyRecentDocuments: function GetMyRecentDocuments(source, origin) {                    
                    $.ajax({
                        url: "/_api/search/query?querytext='-FileExtension:\"ppt*\" AND IsDocument:1 AND AuthorOWSUser:\"" + CurrentUser + "\"'&selectproperties='Path,Title'&rowlimit=5&sortlist='Created:descending'",
                        headers: { "Accept": "application/json; odata=verbose" },
                        contentType: "application/json; odata=verbose",
                        success: function (data, textStatus) {                            
                            var results;
                            if (data.d) {                                
                                if (data.d.query)
                                    results = data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;
                                else if (data.d.postquery)
                                    results = data.d.postquery.PrimaryQueryResult.RelevantResults.Table.Rows.results;
                                else
                                    throw "Results not found";                                
                                if (results.length === 0) {
                                    SPObject.PostMessage(source, origin, '""', '');
                                    return;
                                }
                                else {
                                    SPObject.PostMessage(source, origin, TransformSearchResults(results), '');
                                }
                                
                            }
                        },
                        error: function (data, errorCode, errorMessage) {
                            if (data.error.message !== undefined) {
                                SPObject.PostMessage(source, origin, '""', data.error.message.value);
                            }
                            else {
                                SPObject.PostMessage(source, origin, '""', data.responseText);
                            }
                        }
                    });
                },
                GetMyRecentPresentations: function GetMyRecentPresentations(source, origin) {
                    $.ajax({
                        url: "/_api/search/query?querytext='FileExtension:\"ppt*\" AND IsDocument:1 AND AuthorOWSUser:\"" + CurrentUser + "\"'&selectproperties='Path,Title'&rowlimit=5&sortlist='Created:descending'",
                        headers: { "Accept": "application/json; odata=verbose" },
                        contentType: "application/json; odata=verbose",
                        success: function (data, textStatus) {                    
                            var results;                            
                            if (data.d) {
                                if (data.d.query)
                                    results = data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;
                                else if (data.d.postquery)
                                    results = data.d.postquery.PrimaryQueryResult.RelevantResults.Table.Rows.results;
                                else
                                    throw "Results not found";
                                if (results.length === 0) {
                                    SPObject.PostMessage(source, origin, '""', '');
                                    return;
                                }
                                else {
                                    SPObject.PostMessage(source, origin, TransformSearchResults(results), '');
                                }
                            }
                        },
                        error: function (data, errorCode, errorMessage) {
                            if (data.error.message !== undefined) {
                                SPObject.PostMessage(source, origin, '""', data.error.message.value);
                            }
                            else {
                                SPObject.PostMessage(source, origin, '""', data.responseText);
                            }
                        }
                    });
                },
                PostMessage : function PostMessage(source, origin, data, error) {
                    source.postMessage('{"Message":'+data+', "Error":"'+error+'"}', origin)               
                }
            };
        })();

        SP.SOD.executeOrDelayUntilScriptLoaded(
            function () { SPObject = new SilverIT.SPIntegrationDemo(); }, "SP.js");        

        window.onmessage = function (e) {
            if (event.origin == 'http://connectionserver) {
                var o = JSON.parse(event.data);
                if (CurrentUser == null) {
                    SPObject.PostMessage(
                        event.source, event.origin, '', 'Cannot be used within an anonymous context')
                }
                switch (o.OperationType) {
                    case 1:                        
                            SPObject.GetMyRecentDocuments(event.source, event.origin);                                                                                         
                            break;
                    case 2:
                        SPObject.GetMyRecentPresentations(event.source, event.origin);
                        break;
                    default:
                        throw "Unknown Operation";
                }
            }            
        };
    })();
</script>.

Here is more or less the sequence of the operations:

  • The code declares an object that during its instantiation gets the identity of the current user. Alternatively, you can use _spPageContextInfo if you have it present in your page
  • The code loads SP.js and instantiates the SPIntegrationDemo object
  • The page registers for the onmessage event
  • When a message is received, it checks the origin, parses the data, determines the operation type and call the relevant method of the SPIntegrationDemo object accordingly.
  • The two methods perform a query against the SharePoint search engine using the REST API
  • The search results are parsed by a common private method that transforms them into a simpler JSON string
  • The data is returned to the sender via postMessage



Authentication


One of the advantages of using an IFRAME is of course the authentication since the browser will consider the remote domain just as a normal domain and
will handle the authentication as it does usually.


Security concerns


This technique can be a wide open door for cross-site scripting attacks. Therefore, you need to make sure you always check the origin of the sender. When registering for the onmessage handler, the browser will be able to receive messages from everywhere including malicious pages. To secure it even better, you can parse the data and check that what you receive is well what you expect to receive. This precaution is necessary since you might not have control over the sender which can itself be attacked by malicious applications and thus, somehow forwarding the attack to you.


Happy Coding!