For the past year I’ve been working on an enterprise search solution for a Canadian federal government client employing SharePoint Server 2013 / 2016 and integrating with line-of-business data via Business Data Connectivity Services (BCS, formerly BDC) with a BDC .NET Connector. The elements of this solution, notably SharePoint Search and Business Data Connectivity Services, encompass a complex set of technologies which would easily occupy a blogger for months if not years. On this occasion we’ll delve into one small but important part, creating BDC Profile Pages in a host header / host named site collection context.
Some time ago one of my colleagues reported an issue in our development and testing environments; when we attempt to generate a profile page for an external content type in the BDC service application:
We get this warning:
Close up:
With the text:
This content cannot be displayed in a frame
To help protect the security of information you enter into this website, the publisher of this content does not allow it to be displayed in a frame.
What you can try:
Open this content in a new window
Following which when we click the link “Open this content in a new window” the dialog opens as a full page in another tab of the browser, the CreateProfileDialog.aspx page, but with a somewhat funky layout due to the non-dialog display:
This error message “This content cannot be displayed in a frame” is well known in the general web developer community. There is a lot of discussion in blogs and forums with a variety of suggested fixes or workarounds, such as:
- Add the target host name to Internet Explorer’s Intranet or Trusted Sites zone
- Add an X-Frame-Options response header in IIS
- Add an AllowFraming webpart to the SharePoint application page or its master page
- Implement a custom or deploy a third-party IIS Request Module such as https://ventigrate.codeplex.com/wikipage?title=Permissive%20XFrame%20Header
However these proposed solutions either don’t make sense for the SharePoint out-of-the-box application pages in play with the Create-Upgrade function for Profile Pages, or they disable on too broad a scope, eg for an entire site, what are generally appropriate anti-Clickjacking security measure.
So how should we resolve this blocked dialog frame issue?
First let’s dig a bit deeper into the page request and SharePoint / IIS response to understand what is actually going on to cause the frame dialog to be blocked. First place to look is the F12 Developer Tools Network tab to view the browser requests and responses when opening the dialog frame:
Text version of the Network Summary:
/_admin/BDC/CreateProfileRedirector.aspx?AppId=32d275d2-d180-4027-9dba-98f4864b7f0b&EntityName0=Person&EntityNS0=http%3A%2F%2Fportal%2Fbdcconfig&IsDlg=1 | HTTP GET 302 |
http://portal/bdcconfig/_layouts/CreateProfileDialog.aspx?AppId=32d275d2-d180-4027-9dba-98f4864b7f0b&EntityName0=Person&EntityNS0=http%3A%2F%2Fportal%2Fbdcconfig&IsDlg=1&ReturnSiteID=2420e1e2-12ef-48b2-aaf5-8027cd20681c | HTTP GET 302 |
http://portal/bdcconfig/_layouts/15/CreateProfileDialog.aspx?AppId=32d275d2-d180-4027-9dba-98f4864b7f0b&EntityName0=Person&EntityNS0=http%3A%2F%2Fportal%2Fbdcconfig&IsDlg=1&ReturnSiteID=2420e1e2-12ef-48b2-aaf5-8027cd20681c | HTTP GET 200 |
So what exactly is going on here? When we click on the Create/Upgrade button in the BDC service app ribbon bar it invokes in a standard SharePoint dialog frame the application page CreateProfileRedirector.aspx which through internal magic returns an HTTP 302 Found response with response header:
In essence the CreateProfileRedirector.aspx page determines the Profile Page Host site URL and redirects us there to create the BDC profile page.
However, the path /_layouts/CreateProfileDialog.aspx is not “correct” because the site (collection) is in 15/16 compatibility mode, so a further redirect (HTTP 302 Found response) is necessary to take us to:
Which is the final (redirected) response URL.
In each case whether the response is HTTP 302 or 200 the response headers include:
- X-FRAME-OPTIONS SAMEORIGIN
Which instructs the browser to only display the response in the (standard SharePoint) dialog frame if it comes from the same origin as the requestor page. Since our original response is from the Central Admin site on origin c2931069022 but the final redirected response is from the Profile Page Host site on origin portal, the browser blocks the dialog frame, but the browser permits the final redirected response URL when requested as a full browser page.
The response header X-Frame-Options supports several values including ALLOW-FROM which would allow us to define the BDC service app admin site origin thereby allowing the dialog frame to display when called from that site. So where is this response header coming from, and can we control it?
My first thought is that the X-Frame-Options response header may originate from response headers configured in IIS, so I open IIS Manager to see what headers are defined for the SharePoint Central Admin web application / IIS website:
Hmmm, there is no X-Frame-Options header defined. So this response header is not coming from config in IIS itself.
OK let’s try adding the X-Frame-Options response header in IIS and see if that overrides wherever else it might be coming from:
- X-Frame-Options : ALLOW-FROM http://c2931069022
In the hope that it will override the existing SAMEORIGIN value and resolve everything:
Unfortunately that only resulted in duplicate X-Frame-Options response headers:
With the same result, the dialog frame is blocked by the browser.
My next thought is could the X-Frame-Options response header possibly originate in one of the SharePoint application pages CreateProfileRedirector.aspx and CreateProfileDialog.aspx, so I proceed to check both their ASP.NET markup and their code behind, to see if they are responsible.
CreateProfileRedirector.aspx has only the barest markup and no mention of response headers or X-Frame-Options:
<%@ Page language=”C#” MasterPageFile=”~/_layouts/15/seattle.master” Inherits=”Microsoft.SharePoint.Portal.WebControls.CreateProfileRedirector, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c” %>
While CreateProfileDialog.aspx has slightly more markup it too makes no mention of X-Frame-Options:
<%@ Page language=”C#” MasterPageFile=”~/_layouts/15/seattle.master” Inherits=”Microsoft.SharePoint.Portal.WebControls.CreateProfileDialog, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c” %>
…
Similarly their common master page /_layouts/15/seattle.master also does not reference an X-Frame-Options response header
To complete this check I inspect the code behind for these pages using a .NET disassembler, JetBrains dotPeak is the perfect tool for the job, https://www.jetbrains.com/decompiler/, it is awesome and free!
Since these two application pages are part of the Microsoft.SharePoint.Portal namespace and located in the assembly of the same name, we use Open from GAC:
Scroll and expand the left-hand tree view to the two page classes and double-click the class names to display their disassembled .NET code:
But no luck, searching for X-Frame-Options or SAMEORIGIN turns up no hits.
Another possible source for the X-Frame-Options response header is in an IIS Module. After a little more digging I realize that the SharePoint IIS Module, SPRequestModule may be responsible for setting this response header. Once again back to JetBrains dotPeak to load the Microsoft.SharePoint assembly and search it’s disassembled .NET code for that class:
Low and behold, there are the critical lines of code:
if (!context.Items.Contains((object) SPRequestModule.AllowFramingFlag) && SPRequestModule.ContextCompatibilityLevel != 14)
context.Response.AddHeader(“X-FRAME-OPTIONS”, “SAMEORIGIN”);
So that is how the X-Frame-Options response header is added to the CreateProfileDialog.aspx page response.
While this discovery is of some interest, there is no out-of-the-box configuration point in SharePoint, whether in Central Admin or by PowerShell or any other means, to adjust the value for this response header in the SPRequestModule assembly. SharePoint forces it to always be set as SAMEORIGIN if the AllowFraming flag is not set. The latter is generally controlled by adding the AllowFraming webpart to your custom application page. We are not about to modify the out-of-the-box SharePoint application pages because we want to avoid any problems with cumulative updates / service packs and remain fully compliant with our Microsoft support arrangements.
We need the flexibility to override the SAMEORIGIN value in the X-Frame-Options header emitted by the SPRequestModule and replace it with the ALLOW-FROM value and a URL that we can specify.
We’ll pick up the next steps in this story in another blog post.
A big thank you to CloudShare for providing me a cloud-based virtual development environment with a SharePoint farm to develop this blogpost. “CloudShare enables you to provision complete development environments in minutes.” Be sure to check them out.