09 juli 2014

SharePoint 2013 - Access Denied voor users met volledig beheer rechten

Dit keer geen blog over design of development, maar een probleem dat ik laatst ben tegen gekomen bij een key user van mijn klant. De betreffende klant maakt sinds kort gebruik van Office365 met SharePoint (2013) Online.

Probleem

De key user zit in de site owners groep in een SharePoint (2013) Online site collection, maar had plotseling geen toegang meer tot zijn sites.

Het was al snel duidelijk dat het probleem zat in het feit dat de key user de rechten op de site collection heeft gebroken, waarbij hij de standaard (EN) [sitenaam] Owners/(NL) Eigenaren van [sitenaam] groep alle rechten heeft ontnomen en in plaats daarvan een nieuwe groep heeft aangemaakt met een andere naam (ivm richtlijnen van het bedrijf).

Je zou dus zeggen dat de stap om dit te herstellen, het herstellen van de rechten van de (EN) [sitenaam] Owners/(NL) Eigenaren van [sitenaam] groep is. Dit bleek echter niet alles op te lossen. De key user kon nu wel weer bij de site, maar bleef op bepaalde items in de site settings een 'Acces Denied' krijgen. Hij kon onder andere geen rechten meer instellen, wat een blocking issue was.

Na een tijdje zoeken en proberen (browser cache weg gooien, appdata cache weggooien, andere browser proberen, andere computer proberen) kwam ik dit artikel van Microsoft Support tegen.

Oorzaak

Het blijkt dat, als de rechten van de standaard (EN) [sitenaam] Owners/(NL) Eigenaren van [sitenaam] groep worden ontnomen en daarna weer worden teruggezet, de rechten niet overal worden hersteld. Op bepaalde lijsten is er nu geen enkele groep meer die nog rechten heeft. Hieronder valt bijvoorbeeld ook de (EN) Access requests/(NL) Toegangsaanvragen lijst (Dit is dus het geval wanneer bij het aanmaken van de site collection is gekozen om de (EN) Allow access requests/(NL) Sta toegangsaanvragen toe setting in te schakelen). De lijsten waarop de rechten nog ontbreken, veroorzaken tevens dat een aantal andere functionaliteiten niet meer werken. In het geval van de 'Pending requests' lijst, wordt behalve de lijst zelf, ook de (EN) Site permissions/(NL) Site machtigingen geblokkeerd.

Oplossing

Nu kan je natuurlijk de hele webapplicatie weg gooien en opnieuw beginnen, maar het kan ook wat minder drastisch. De oplossing voor dit probleem is dat je alle rechten op de getroffen lijsten handmatig moet gaan herstellen. Omdat SharePoint wil voorkomen dat gebruikers (die niet weten waar ze mee bezig zijn) onherstelbare schade aanrichten aan deze onmisbare lijsten, is het niet makkelijk om bij de instellingen van deze lijsten te komen. Hieronder leg ik uit hoe je dat toch voor elkaar krijgt. We gaan beginnen met de (EN) Access Requests/(NL) Toegangsaanvragen lijst. Deze vind je hier:

  • (EN) https://URL-van-de-betreffende-site-collection/Access%20Requests/pendingreq.aspx
  • (NL) https://URL-van-de-betreffende-site-collection/Toegangsaanvragen/pendingreq.aspx
  1. Log in op de getroffen site collectie met een user die administrator rechten (op SharePoint dus, niet op je computer) heeft, en navigeer naar de (EN) Access Requests/(NL) Toegangsaanvragen lijst. Gebruik hiervoor Internet Explorer.
  2. Druk op F12 om de Developer Tools in IE te openen.
  3. Klik op de Network-tab en druk op F5 of klik op het groene pijltje links bovenin de Developer tools (IE10+) om het netwerk verkeer te volgen.
  4. Ververs de pagina in de browser. Als de pagina weer geladen is, stop dan het volgen van het netwerk verkeer door op SHIFT+F5 te drukken of op het rode blokje te klikken.
  5. In het venster van de Developer tools staat een url lijst. Dubbelklik op het bovenste resultaat (dat moet eindigen op pendingreq.aspx).
  6. Klik in het venster van de Developer tools op Request body.
  7. Wederom in de Developer tools, vul 'pageListId:' in het zoekvenster en druk op ENTER.
  8. Het zoekresultaat zal geel gemarkeerd worden weergegeven. Kopieer de GUID die bij het pageListId: staat. De GUID is de combinatie van letters en cijfers die tussen de {} staat. Kopieer de {} ook mee. De GUID is het identificatiemiddel van de lijst. Iedere lijst heeft een unieke GUID.
  9. Open een nieuwe tab in IE en voer de volgende url in de adres balk:
    https://URL-van-de-betreffende-site-collection/_layouts/15/ListEdit.aspx?List={GUID}
    (Vul bij {GUID} het nummer in dat je in de vorige stap hebt gekopieerd, voorbeeld:
    https://voorbeeld.sharepoint.com/sites/voorbeeld1/_layouts/15/ListEdit.aspx?List={3536637D-E28A-4F12-9749-0B789E0A2448}
  10. Je bent nu op de pagina voor de (EN) List settings/(NL) Lijst instellingen. Klik nu op (EN)Permissions for this list/(NL)Machtigingen voor dit lijstitem.
  11. Er is nu niets te zien, wat betekend dat niemand rechten heeft op deze lijst. Voeg nu de groep (EN) [sitenaam] owners/(NL) Eigenaren van [sitenaam] toe en geef deze groep volledig beheer.

Dit was het herstelproces voor de 'Pending requests' lijst. Als je nu met een user uit de (EN) [sitenaam] Owners/(NL) Eigenaren van [sitenaam] groep inlogt en naar de (EN) Site permissions/(NL) Sitemachtigingen navigeert, zal je zien dat alles nu weer goed gaat. Je bent echter nog niet helemaal klaar. Het probleem doet zich ook nog voor op 2 andere lijsten:

  • (EN) Device Channels/(NL) Apparaat kanalen lijst: (EN/NL) https://URL-van-de-betreffende-site-collection/DeviceChannels/AllItems.aspx
  • (EN) Translation Status/(NL) Vertaalstatus lijst: (EN/NL) https://URL-van-de-betreffende-site-collection/Translation%20Status/Alle%20gebruikers.aspx

Om deze lijsten ook te herstellen doe je hetzelfde als wat je zojuist voor de 'Pending requests' lijst hebt gedaan.

Tip

Loop je nou vaak tegen dit probleem aan, dan kan het ophalen van de GUID's ook wat eenvoudiger. Je kunt hier een tool downloaden welke na het invoeren van de url van je site, de site kan uitlezen. Deze tool leest bijvoorbeeld ook de schema.mxl uit, waarin de GUID is vermeld. Onderstaande stappenplan geldt in dit geval als vervanging voor de stappen 1 t/m 8 van de handleiding eerder in dit artikel.

  1. Open de 'SharePoint 2013 Client Browser' tool.
  2. Geef de url van de getroffen site op.
  3. Klik in het linker venster op de + bij 'Lists' en selecteer (EN) Access Requests/(NL) Toegangsaanvragen.
  4. Klik in het rechter venster op de tab Schema XML.
  5. In de code die je nu ziet, staat een <List> tag met een property genaamd 'ID', dat is de GUID. Voorbeeld: ID="{AE3ED198-0E26-47F2-B611-27E89FE00831}" Kopieer de GUID (Inc {}) en ga verder met stap 9 van de handleiding eerder in dit artikel.

25 april 2014

SharePoint 2013 List Item Attachment

Het heeft even een tijdje geduurd, maar hier ben ik dan weer met een nieuwe blogpost. Het onderwerp waar ik het dit keer over ga hebben is de functionaliteit in SharePoint waarbij een document als attachment bij een list item gevoegd kan worden.

Het probleem waar ik mee geconfronteerd werd, was dat men de attachments graag als lijstje van korte klikbare links in een SharePoint 2013 app wilde weergeven. Omdat dit ietwat ingewikkelder is dan het weergeven van een willekeurige column/field uit een lijst, vind ik het de moeite waard om in een blog uit te leggen hoe je bovenstaande voor elkaar krijgt. SharePoint apps bestaan uitsluitend uit client side code, dus we gaan met javascript aan de slag.

Voor ik begin wil ik nog even vermelden dat ik deze blogpost heb kunnen schrijven dankzij mijn collega’s Mirjam van Olst en Laurens Ruijtenberg, die mij hebben geholpen het mysterie op te lossen.

Wat je nodig hebt

In deze blogpost ga ik er vanuit dat je al een werkende SharePoint omgeving hebt en een SharePoint solution, waarin je (onder andere) de list item attachments wil gaan tonen. Zelf werk ik met Visual Studio 2013, dat is het makkelijkst om SharePoint code te debuggen. De gratis versie heet Visual Studio Express en is hier verkrijgbaar.

Om list item attachments op te halen, heb je vanzelfsprekend een SharePoint list nodig. Zelf heb ik een list met de naam ‘My Items’. Deze naam zal ik, voor de duidelijkheid, ook blijven gebruiken in de code. Je kan de list aanmaken via de solution (best practice), of een list aanmaken via de user interface van SharePoint. Vul je SharePoint list alvast met wat list items en voeg wat attachments aan die items toe. Dat scheelt later weer een hoop hersenkraken over waarom het niet werkt (als er geen attachments zijn, kunnen ze immers ook niet worden weergegeven).

Verder heb je jQuery en de ‘jQuery library for SharePoint Web Services’ nodig. De laatste vind je hier. jQuery zelf kan je via Visual Studio installeren (Tools -> Extensions and Updates).

We gaan in deze blogpost uit van 3 bestanden:

  • Een HTML template waarin we onze content gaan weergeven.
  • Een Javascript bestand welke bepaald wat we gaan weergeven, hoe we het gaan weergeven en waar we het gaan weergeven. Omdat dat ik zelf met de MVC methode heb gewerkt, is in mijn geval de Javascript verdeeld over 2 lossen bestanden. Doe wat je het prettigst vind.
  • Een CSS bestand om de noodzakelijke opmaak toe te voegen.

Beginnen

In het HTML template gaan we een korte structuur aanmaken, zodat we straks een plekje hebben om de list item attachments weer te geven. Ik schrijf niet de complete SharePoint pagina uit, alleen het stukje waar het hier om gaat.

<div class="container">
<h3 id="title">Attachments</h3>
<h3 id="listAttachments"></ul>
</div>

Het is niet veel: Een div om het geheel te positioneren, een header en een lijst waarin ieder attachment straks als list item wordt ingevoegd.

Data aanroepen

Dan komen we nu toe aan het grootste en moeilijkste deel, de javascript code. Er zijn een aantal dingen die we met javascript moeten gaan regelen:

  • We moeten de applicatie gaan vertellen wat we willen zien
    De query bestaat uit XML en die moeten we gaan opbouwen via Javascript.
  • We moeten de applicatie gaan vertellen hoe we het willen weergeven.

Query

De eerste stap is het aanroepen van de SharePoint list en definiëren wat we willen zien van die list.

var MyItemsRepository = function () {
this.QueryListOptions = function (query, viewFields, options) {
var result;

$().SPServices({
operation: "GetListItems",
async: false,
listName: "My Items",
webURL: commonSiteCollectionUrl,
CAMLQuery: query,
CAMLViewFields: viewFields,
CAMLQueryOptions: options,
completefunc: function (xData, Status) {
result = xData;
}
});

return result;
}

// Verderop in de tutorial maken we nog een stukje code aan dat nodig is om de attachments op te roepen, zet die code hier
}

var myItemsRepository = new MyItemsRepository();

In dit stukje code wordt een call gemaakt naar SharePoint en wordt de list met de naam ‘My Items’ aangeroepen. Hier wordt achter de schermen de ‘jQuery library for SharePoint Web Services’ voor gebruikt. Vervolgens zien we een aantal parameters Waar de lettercombinatie CAML in staat. CAML staat voor ‘Collaborative Application Markup Language’. Dit is een op XML gebaseerde taal die in SharePoint wordt gebruikt om fields en views te definiëren. De waarden van deze CAML parameters die je in de bovenstaande code ziet, zijn variabelen die we later gaan definiëren.

CamlDesigner2013

Als je veel verstand hebt van CAML en/of XML, zou je de queries zelf kunnen schrijven. Dit is echter niet nodig, want er bestaat een heel handige tool voor: CamlDesigner2013. Deze vind je hier.

Omdat ik niet zo goed ben in CAML en XML, ga ik deze tool gebruiken om de juiste query te genereren. Als je de tool wilt gebruiken is het van belang dat je hem installeert op een machine die toegang heeft tot je SharePoint site. De CamlDesigner2013 gaat namelijk een connectie maken met de site, om de site (en dus ook onze list) uit te kunnen lezen.

Omdat deze tutorial niet over CamlDesigner gaat, ga ik niet uitgebreid beschrijven hoe het werkt. Ik vertel slechts kort welke stappen je moet nemen in de tool om het voor nu gewenste resultaat te bereiken.

  1. Verbind CamlDesigner als eerste met de SharePoint site waarin de betreffende list staat.
    Rechts bovenin staat een knop ‘Connection’.
  2. Selecteer links in het menu de juiste list (in mijn geval ‘My Items’).
  3. Nu gaan we de code genereren:
    1. Klik, in het middelste venster bovenin, op ‘Where’.
      Sleep het field ‘ID’ naar de rechterkant en typ ‘id’ in de input.
      Je ziet nu onderin de code verschijnen die je hebt gegenereerd.
    2. Klik, weer in het middelste venster bovenin, op ‘ViewFields’.
      Sleep nu het field ‘Attachments’ naar de rechterkant.
      (zie de code weer verschijnen onderin)
    3. Klik, weer in het middelste venster bovenin, op ‘Query Options’.
      Vink ‘Include Attachments URL’s’ aan.

De XML code die we nodig hebben is nu gegenereerd. We kunnen het nu gaan toepassen in het volgende stukje javascript.

Query Part II

In de Javascript code hieronder komt de code die we met de CamlDesigner hebben gemaakt weer terug. De output van de CamlDesigner is hier als waarde toegevoegd aan de variabelen (query, viewFields en options) die we in het volgende stukje code hebben aangeroepen.

Vergeleken met wat de CamlDesigner heeft uitgespuugd, maken we nog een paar kleine wijzigingen:

  1. Rondom de <Where> tag zetten we nog een <Query> tag. Dat heeft de CamlDesigner namelijk nog niet voor ons gedaan.
  2. In de <Value> tag (binnen de <Where> tag), heeft de CamlDesigner een foutje gemaakt. Die heeft er namelijk Type='Counter' van gemaakt, maar wij willen graag Type='Text' zien.
  3. In dezelfde <Value> tag willen we niet ‘id’ als onderdeel van de XML, maar we willen dat daar via Javascript de id wordt aangeroepen. Verander daarom ‘id’ naar ‘" + id + "’ zoals in onderstaande code.

Je uiteindelijke code moet dus vergelijkbaar zijn met onderstaande code.

// Plaats onderstaande code op de plaats waar de comment in het stuk code eerder in deze tutorial dat aangeeft

this.GetById = function (id) {
var query = "<Query>
<Where>
<Eq>
<FieldRef Name='ID'/>
<Value Type='Text'>" + id + "</Value>
</Eq>
</Where>
</Query>";

var viewFields = "<ViewFields>
<FieldRef Name='Attachments' />
</ViewFields>";

var options = "<QueryOptions>
<IncludeAttachmentUrls>TRUE</IncludeAttachmentUrls>
</QueryOptions>";

var data = this.QueryListOptions(query, viewFields, options);

var count = $(data.responseXML).SPFilterNode("rs:data").attr("ItemCount");

if (count > 0) {
return data;
}

return null;
}

Voor de leesbaarheid van deze tutorial heb ik wat extra linebreaks en spacing aan de code toegevoegd. Normaal gesproken staat de gehele XML code op één regel.

Back to the point: Wat doet deze code nou precies?
Als eerste wordt een anonieme function gedefinieerd, die verwijst naar de variabele ‘MyItemsRepository’ (zie het stukje javascript eerder in deze tutorial) via het this keyword. this.GetById roept dus een item uit de list ‘My Items’ aan, aan de hand van de ID’s van de items. Vervolgens zorgen de variabelen ‘query’, ‘viewFields’ en ‘options’ dat de CAML query wordt samengesteld, die de juiste gegevens uit onze SharePoint list haalt.
Hierna maken we een variabele ‘ data’ aan, die de bovenstaande 3 variabelen samenvoegt en als laatste wordt van het geselecteerde item ieder (kunnen er meerdere zijn) attachment opgehaald. Het if statement op het eind kijkt of er überhaupt attachments zijn. Als dat zo is worden ze opgehaald en als er geen attachments zijn is de output null.

Data weergeven

Nu dat we de data hebben opgehaald, gaan we door middel van Javascript bepalen hoe we die data willen weergeven. We gaan de attachments in HTML list items weergeven, we gaan er klikbare links van maken en we gaan de URL’s inkorten.

De code die dat doet ziet er als volgt uit:

var MyItemsController = function () {

var MyItem;

this.showMyItemDetails = function (I_ID) {
if (myItem != "0") {

var xData = MyItemsRepository.GetById(I_ID);

if (xData != null) {
$(xData.responseXML).SPFilterNode("z:row").each(function () {

// Attachments weergeven
if ($(this).attr("ows_Attachments")) {
liHtml = [];

var attachments = $(this).attr("ows_Attachments").split(';#');

if (attachments !== undefined && attachments != null) {
for (var i = 0; i < attachments.length; i++) {
if (attachments[i] !== undefined && attachments[i] != null) {
if (attachments[i].length > 1) {
var fileName = attachments[i].split('/');

liHtml.push('<li><a href="' + attachments[i] + '">' + fileName
[fileName.length - 1] + '<a/></li>');

}
}
};
}

$('#listAttachments').html(liHtml.join(""));
}
});
}
}
}
}

Deze code begint met het kijken of er list items zijn, en als dat zo is worden die list items opgeroepen. Vervolgens begint onder de comment ‘Attachments weergeven’ de code die de Attachments aanroept en weergeeft. Als eerste wordt het field ‘ows_Attachments’ (zo heet het attachments field in de SharePoint list) opgezocht, waarvan de inhoud vervolgens wordt omgezet in een array.

Daarna wordt de variabele ‘attachments’ aangemaakt, waarin de opgehaalde attachments worden gesplitst. Dat is nodig omdat de attachments standaard gezamenlijk als één lange string worden weergegeven. Ieder attachment begint met ;#, dus daar gaan we op splitsen. Omdat de ;# voor ieder attachment staat, inclusief voor het eerste attachment, denkt de code dat ook voor het eerste attachment nog een item is. Die moeten we eruit filteren om te voorkomen dat het resulteert in een leeg eerste (HTML) list item in de uiteindelijke output.

De volgende stap is dat we met de for loop door de attachments heen gaan. In attachments[i] zit nu de URL van het attachment. De code vervolgt zich met twee if statements. De eerste kijkt of er überhaupt attachments aanwezig zijn, de tweede kijkt of de lengte van de attachment URL (attachments[i]) groter is dan 1. Met deze laatste if filteren we het eerste lege array item dat door de split in de array terecht is gekomen.

Vervolgens gebruiken we var fileName = attachments[i].split('/'); welke zorgt dat de totale URL van het attachment wordt gesplit op de /. De fileName variabele bevat nu een array met in elk item van de array een stukje van de URL. Met behulp van fileName[fileName.length - 1] vragen we het laatste stukje van de URL van het attachment op, de echte bestandsnaam.

Hoe werkt dit dan?
Nou, fileName.length geeft het totaal aantal items in de array terug. Als de array 1 item zou bevatten dan zou fileName.length ‘1’ zijn. Omdat de index van een array bij ‘0’ begint, verwijst fileName[fileName.length] naar een niet bestaand item en dus ontstaat er een foutmelding.

Ter illustratie:

laten we nu de ‘- 1’ weg, dan zou de code, in het geval van 4 items in de array, gaan zoeken naar item nummer 4 terwijl hij nummer 3 moet hebben.

Enfin, Door bovenstaande kan je dus in plaats van een hele lange URL, alleen de bestandsnaam weergeven, wat het lijstje attachments een stuk leesbaarder maakt.

In de regel erna worden de attachments in een <li> en een <a> gestopt, zodat de output een keurige HTML list wordt met een klikbare link in ieder list item. De laatste regel vervolgens, koppelt de output aan de <ul> met het id (#) listAttachments, zodat onze output in dat element wordt weergegeven.

Afronden

We zijn er bijna. Het laatste wat we nu nog gaan doen is een paar regeltjes CSS toevoegen, zodat onze HTML output toonbaar is. Natuurlijk kan je dit zelf naar wens aanpassen.

#container {
float:left;
height:200px;
width:300px;
}

#container h3 {
float:left;
width:100%;
}

#listAttachments {
float:left;
width:100%;
margin:10px 0;
padding:0;
}

#listAttachments li {
float:left;
width:100%;
margin:5px 0;
}

Bovenstaande CSS zorgt voor een box van 200x300 pixels met bovenin de header ‘Attachments’ en daaronder de lijst met attachments, keurig onder elkaar weergegeven met wat ruimte tussen ieder element.

Nu is het tijd om je solution te deployen. Als je solution is geïnstalleerd en de feature(s) is/zijn geactiveerd, zie je nu je attachments op de pagina waarop je de HTML hebt toegevoegd.