Monday, April 1, 2013

SharePoint Hosted App, App Part, Document Library Upload.aspx / EditForm.aspx and “X-Frame-Options”

 

Before we drill and talk further would like to take sometime to explain different pieces in the topic. Lets first look at X-Frame-Options.

X-Frame-Options : In a simplest way this is a response header which tells browser (client) that the Website doesn’t want its page (which is currently being accessed) to be shown in a IFrame so as to prevent Clickjacking (Wikipedia : http://en.wikipedia.org/wiki/Clickjacking).

So how do X-Frame-Options related to SharePoint App’s. Well every app developer in SharePoint 2013 is now familiar with the App Model Concept, it has its own App Web and isolated domain which is different then Hosting Web.

To enhance security in SharePoint 2013 by default the “X-Frame-Options” header is sent back with response If you open up SPRequestModule in your fav decompiler you’ll find below statement written in PreSendRequestHeaders method, which gets added to every response if the AllowFraming Flag is not found in the current context items collection.

   1:  if (!httpContext.Items.Contains(SPRequestModule.AllowFramingFlag) && SPRequestModule.ContextCompatibilityLevel != 14)
   2:                         
   3:   {
   4:                              
   5:     httpContext.Response.AddHeader("X-FRAME-OPTIONS", "SAMEORIGIN");
   6:                          
   7:   }

Wait a minute what does that mean, so can we override this and make these line skip for those pages which explicitly marks and request for exception? yes this can be done simply by adding AllowFraming WebPart on to the page which you want to be able to be accessed from within IFrame.


"AllowFraming” Webpart does nothing but adds a simple statement in on load.


protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if (this.Context != null && !this.Context.Items.Contains(SPRequestModule.AllowFramingFlag))
    {
        this.Context.Items.Add(SPRequestModule.AllowFramingFlag, "1");
    }
    if (this.Visible)
    {
        this.Visible = false;
    }
}



Consider a requirement, you have to upload a document to Document library which is in App, and this App is hosted in an App Part on some page, sound pretty simple.


Just as expected you’ll just point to upload.aspx page of App Document Library which will look something like http://your-fancy-appurl/SomeApp/Lists/Documents/Forms/Upload.aspx and using SP.UI.ModalDialog.showModalDialog you can open the page Upload page in SharePoint dialog box, but you’ll be presented with  annoying error message that you cannot do that it’s because of X-Frame-Options header instructed the browser to do so.


Load denied by X-Frame-Options: http://your-fancy-app-url/_layouts/15/Upload.aspx?List={guid}&IsDlg=1 does not permit cross-origin framing.




Take a pause and you’ll be thinking is where did that _layout/15/Upload.aspx page came in picture (this I’ll explain in later post) and what it has to do with X-Frame-Options.


Investigating further, I opened Upload.aspx under layouts folder and had a look if it has got AllowFraming webpart added to it or not, and i couldn’t locate so i added that entry manually and again gave a try and voila it worked !!!! because now the SPRequestModule –> X-Frame-Options won’t get executed. Next moving further after uploading document i was redirected to EditForm.aspx and i was again shown the same error (highlighted above) but this time instead of Upload.aspx it was EditForm.aspx, so where do you find this now, its actually stored in DataBase and not in layouts which you can just go and make an entry and make it work.


Hold you horses dude, get back to basics, how does this EditForm.aspx gets created? If you look at the schema.xml file of document library the below section where it is all written


<Forms>
      <Form Type="DisplayForm" SetupPath="pages\form.aspx" Url="Forms/DispForm.aspx" WebPartZoneID="Main" />
      <Form Type="EditForm" SetupPath="pages\form.aspx" Url="Forms/EditForm.aspx" WebPartZoneID="Main" />
      <Form Type="NewForm" Url="Forms/Upload.aspx" WebPartZoneID="Main" />
</Forms>



These forms are generated based on forms.aspx a template page which is in pages folder under 15/Template. Well now i again had a look to form.aspx and i again couldn’t locate the AllowFraming webpart, so i added one there again. So in order to regenerate my EditForm.aspx from the same form.aspx i need to re-deploy my app and now everything worked in my App in an App Part.


But is it a good practice No !!!  Ninjayou shouldn’t be modifying Baring teeth smilethese pages to make these solution work, so what’s next


A more elegant approach would be to use HttpModule and trap BeginRequest Event and make it work.


   1:      public class ZSFramingModule : IHttpModule
   2:      {
   3:          public void Dispose()
   4:          {
   5:          }
   6:   
   7:          public void Init(HttpApplication context)
   8:          {
   9:             context.BeginRequest += OnBeginRequest;
  10:              
  11:          }
  12:   
  13:          private void OnBeginRequest(object sender, EventArgs e)
  14:          {
  15:              HttpApplication l_application = (HttpApplication)sender;
  16:   
  17:              if (l_application.Request.Url.ToString().ToLower().Contains(@"/_layouts/15/upload.aspx")
  18:                                                  ||
  19:                  l_application.Request.Url.ToString().ToLower().Contains(@"editform.aspx"))
  20:              {
  21:                  HttpContext.Current.Items.Add("AllowFraming", "1");
  22:              }
  23:   
  24:          }
  25:      }



Hope this helps someone Smile 


 


Happy SharePointing !!!