Einmal Links (Disposable Links) für SharePoint Dokumentbibliotheken

SharePoint bietet mit Dokumentbibliotheken eine perfekte Dateiverwaltung. Aber wie können die Dateien über Einmal Links externen, nicht an SharePoint angemeldeten Usern zur Verfügung gestellt werden. Die Anforderung ist, dass externe User einen Link zu einer Datei in einer Dokumentbibliothek erhalten. Sobald dieser Link verwendet wurde, darf er nicht mehr zur Verfügung stehen.

Die Lösung setzt sich aus zwei Teilbereichen zusammen. Wir benötigen eine Verwaltung der ausgegeben Links und zweitens die Möglichkeit als externer User Zugriff, nur auf die eine Datei zu erhalten.

Teil 1 des Problems kann mit einer simplen SharePoint Liste gelöst werden. Ein Feld für die URL zum Dokument und ein Feld mit dem Ticket.  Mit Hilfe einer Application Page und einer Custom Action, die auf diese Application Page zeigt, wird die URL in eine SharePoint Liste eingetragen. Zugleich wird ein Ticket (in diesem Fall ein GUID) erzeugt und mit abgespeichert.

Auf Teil 1 gehe ich nicht näher ein. Die gesamte Solution (inkl. SourceCode) gibt’s zum Download. Link zum Download

Teil 2 ist spannender:

Zunächst ist zu klären wie eine Datei an einen externen Benutzer gesendet werden kann ohne, dass der Benutzer die Dokumentbibliothek verwenden muss.

In einem Page_Load Event kann der Http-Response umgeschrieben werden. Die Response.BinaryWrite() Methode schreibt ein Byte-Array in den Response. Mit den richtigen Page-Header und ContentType ergibt das für den Browser einen Datei-Download. In diesem Beispiel wurde mit einer PowerPoint Datei gearbeitet. Daher “application/mspowerpoint” als ContentType. (siehe Wikipedia)

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {            
   3:    string docUrl = getDocUrl(ticketID, web); // die zur TicketID gehörende Dokument-URL suchen
   4:    SPFile spFile = web.GetFile(docUrl);
   5:   
   6:    Response.Clear();
   7:    Response.AddHeader("Content-Disposition", "attachment; filename=" + spFile.Name);
   8:    Response.AddHeader("Content-Length", spFile.Length.ToString());
   9:    Response.ContentType = "application/mspowerpoint";
  10:   
  11:    Response.BinaryWrite(spFile.OpenBinary());
  12:  }

Um den Benutzer einen Links anbieten zu können, legen wir eine Application-Page an. Als Parameter wird das Ticket mitübergeben. Das Problem ist, dass normale Application-Pages nicht von einem externen, nicht angemeldeten User abgefragt werden können. Auch wenn Anonymous-access aktiviert ist.

Visual Studio legt Application Pages als abgeleitete Klassen von LayoutsPageBase an. Das müssen wir ändern. Nur von UnsecuredLayoutsPageBase abgeleitete Seiten können von anonymen Benutzern abgefragt werden. Zusätzlich muss das Property AllowAnonymousAccess überschrieben werden und “true” zurückliefern.

   1:    public partial class FileGet : UnsecuredLayoutsPageBase
   2:      {
   3:          protected override bool AllowAnonymousAccess { get { return true; } }
   4:   
   5:  ....

In dieser Application Page muss noch der Verweis auf die Masterpage entfernt werden. Da wir im Page_Load den Response umschreiben ist kein Inhalt erforderlich. Außerdem ist der Zugriff auf die Masterpage einem externen User nicht erlaubt, was wieder in einem 401- Access denied endet.

Jetzt bleibt noch eine Hürde zu überwinden: Die Security von SharePoint. Da wir über das SharePoint API die Datei abfragen (web.GetFile(url)) wird die Berechtigung des Users geprüft, ob er überhaupt die Datei in der Dokumentbibliothek lesen darf. Der anonyme User darf das nicht.

Daher lassen wir den gesamten Code um die Datei abzufragen und zurückzuliefern unter “ElevatedRights” laufen. Code der unter SPSecurity.RunWithElevatedPrivileges läuft wird unter dem Systemkonto ausgeführt. MSDN

Der gesamte Code meiner Page_Load Routine:

   1:          protected void Page_Load(object sender, EventArgs e)
   2:          {
   3:              string ticket = Request["ticket"];
   4:              SPSecurity.RunWithElevatedPrivileges(delegate()
   5:              {
   6:                  Guid ticketID = new Guid(ticket);
   7:                  using (SPSite site = new SPSite(SPContext.Current.Web.Url))
   8:                  {
   9:                      using (SPWeb web = site.OpenWeb())
  10:                      {
  11:                          string docUrl = getDocUrl(ticketID, web);
  12:   
  13:                          if (!string.IsNullOrEmpty(docUrl))
  14:                          {
  15:                              SPFile spFile = web.GetFile(docUrl);
  16:   
  17:                              Response.Clear();
  18:                              Response.AddHeader("Content-Disposition", "attachment; filename=" + spFile.Name);
  19:                              Response.AddHeader("Content-Length", spFile.Length.ToString());
  20:                              Response.ContentType = "application/mspowerpoint";
  21:   
  22:                              Response.BinaryWrite(spFile.OpenBinary());
  23:                          }
  24:                      }
  25:                  }
  26:              });
  27:          }
Kommentare sind geschlossen