November 28, 2024

How to use Files.SelectedOperations.Selected permission for SharePoint and OneDrive content

Problem

I needed to grant permission for an Entra Id application to access SharePoint Online and OneDrive folder. I didn't want to grant the broadest Sites.ReadWrite.All permission to all site collections, nor the second broadest permission Sites.Selected to specific site collection. Instead I needed to go very granular, so limit access to specific folder and use the Files.SelectedOperations.Selected permission.

Backgound: Granting permission to whole site collection

Before realizing Sites.Selected was too broad, I tested using it, and it worked great. I order to use the following commands, I used Graph Explorer to which I granted Sites.FullControl.All permission so that it could modify the permissions for SPO items.

1. Get the site collection

First I queried the site (collection) ID for my SharePoint site collection:

HTTP GET https://graph.microsoft.com/v1.0/sites/xyz.sharepoint.com:/sites/jussi


Then with the site ID (it looks something like this: "xyz.sharepoint.com,8e099463-6a83-48c3-9c0e-59c3ca071054,15a706b9-af56-49dd-97ad-51325cee3b66"), I queried current permissions for the site:

HTTP GET https://graph.microsoft.com/v1.0/sites/[SITE_ID]/permissions

No permissions were found, just as expected.

2. Add permissions

To add permissions, you use the permissions endpoint:

HTTP POST https://graph.microsoft.com/v1.0/sites/[SITE_ID]/permissions

with the following payload in the body section of Graph Explorer:

{
  "roles": [
    "write"
  ],
  "grantedToIdentities": [
    {
      "application": {
        "id": "10facd40-ec88-4c62-b5dc-8170a2ccaba1"
      }
    }
  ]
}

3. Removing permissions

You can remove the permissions too, but for that you need the permission ID. You got the permission ID from the result of the query you used when you added the permission earlier, but in case you missed it, you can list the permissions:

HTTP GET https://graph.microsoft.com/v1.0/sites/[SITE_ID]/permissions

Permission ID is the long non-GUID string like this:

"id": "aTowaS50fG1zLnNwLmV4dHwyMGZhY2Q0NC1lYzg4LTRjNjItYjVkYy04MTcwYTJjY2FiYTFAOTJlNTE2MTUtNDkwOS00OGUzLWJhMDYtMmE1ZmMyMzNiNGJi"

So in order to remove the permission:

HTTP DELETE https://graph.microsoft.com/v1.0/sites/[SITE_ID]/permissions/[PERMISSION_ID]


Granting permission to folder level

Alright, I removed the site collection level permissions and started to grant folder level permissions. 

1. Get the drive ID for the library

https://graph.microsoft.com/v1.0/sites/[SITE_ID]/drives

Drive ID is the non-GUID string like:

"id": "b!Y6QJjoNqw0icDlnDygcQVLkGpxVWr91Jl61RMlzuO2a6mn6WqINZRLEDcG_BOOKl"

2. Get the folder ID from the drive by querying all the children items of the folder

HTTP GET https://graph.microsoft.com/v1.0/sites/[SITE_ID]/drives/[DRIVE_ID]/root/children

So, what I now got was the id 01QHBNZNLH6DKOS3GO3FHKHKIC6AGODYML of the folder MyTemplates inside the library Shared Documents, that's the one.

            "createdDateTime": "2024-11-27T07:26:15Z",
            "eTag": "\"{E9D4F067-CE6C-4ED9-A3A9-02F00CE1E18B},1\"",
            "id": "01QHBNZNLH6DKOS3GO3FHKHKIC6AGODYML",
            "lastModifiedDateTime": "2024-11-27T07:26:15Z",
            "name": "MyTemplates",
            "webUrl": "https://xyz.sharepoint.com/sites/jussi/Shared%20Documents/MyTemplates",
            "cTag": "\"c:{E9D4F067-CE6C-4ED9-A3A9-02F00CE1E18B},0\"",
            "size": 20558,
            "createdBy": {
                "user": {
                    "email": "admin@xyz.onmicrosoft.com",
                    "id": "45a44407-f372-45f5-a69d-9f4dd4f704fa",
                    "displayName": "xyz Admin"
                }
            },


3. Add permissions

Following my earlier steps when granting permission to site level, I made call:

HTTP POST https://graph.microsoft.com/v1.0/sites/[SITE_ID]/drives/[DRIVE_ID]/items/[FOLDER_ID]/permissions

using same payload as earlier

{
  "roles": [
    "read"
  ],
  "grantedToIdentities": [
    {
      "application": {
        "id": "10facd40-ec88-4c62-b5dc-8170a2ccaba1"
      }
    }
  ]
}

╯︿╰   I get "Invalid request" error:

{
    "error": {
        "code": "invalidRequest",
        "message": "Invalid request",
        "innerError": {
            "date": "2024-11-28T08:12:45",
            "request-id": "62a82827-5c26-4883-934e-7eabeceead88",
            "client-request-id": "f6cd7714-2199-3a94-3a4b-5df804f1ec37"
        }
    }
}

What is this? Documentation is rather brief. There was a great article by Vasil that discusses permissions that indicated that this should (or at least was) possible, so I started testing further, and finally found a solution.

Solution

When granting application permissions to folder objects (probably same goes for files and lists), the payload you send is slightly different. You don't use grantedToIdentities array, but instead grantedTo object:

{
  "roles": [
    "read"
  ],
  "grantedTo": {
    "application": {
      "id": "10facd40-ec88-4c62-b5dc-8170a2ccaba1"
    }
  }
}

NOTE! This payload doesn't work when granting permission on a site level.


December 14, 2023

SharePoint: How to find out if you have legacy add-ins that are affected by the SharePoint Add-in model retirement

Question

How to find out if my SharePoint Online tenant has legacy add-ins that stop working when the legacy add-in model is retiring?

Answer

In order to list add-ins, you can use few techniques:

Easiest in my opinion is the CLI for Microsoft 365, as it will give you the result in just a two commands.  If you wish to build further automation, you will want to pick the PnP PowerShell library to use in your PowerShell script or the PnP CSOM if you're using .NET.

To get a quick list or add-ins using the CLI for Microsoft 365, first login to your tenant with command:

m365 login

then list the add-ins with command:

m365 spo app list

in order to output the add-in JSON array to a file, use command:

m365 spo app list > add-ins.json

The output will contain array of add-ins, and the property IsClientSideSolution will tell you if the application is legacy and will be retiring. 

If IsClientSideSolution is false, it is legacy and will be retired.

If IsClientSideSolution is true, it is modern and will NOT be retired.



January 12, 2022

Kuluttajariitalautakunnan päätös: Puutteet auton varusteissa

Kuluttajariitalautakunta käsitteli tapaukseni kun tammikuussa 2020 ostamastani autosta puuttui varusteita, jotka tilaushetkellä oli varustelistauksessa. Lautakunta päätyi suosittamaan hyvitystä. Hyvitysvaateissanne voitte viitata Kuluttajariitalautakunnan julkiseen päätökseen Dnro 6279/33/2020.

Päätöksen PDF:n saa Kuluttajariitalautakunnalta tai allekirjoittaneelta pyydettäessä. Alla copy&paste julkisen päätöksen tekstisisällöstä. 


KULUTTAJARIITALAUTAKUNTA PÄÄTÖS Dnro 6279/33/2020

Esitelty 28.10.2021

IVa jaosto 


Myyjän vastuu auton puuttuvista varusteista


Lautakunnan ratkaisu 

Kuluttajariitalautakunta suosittaa, että Autokeskus Oy maksaa N.Nlle 300 euroa. 


Asiaselostus 

Kuluttaja osti 26.10.2019 myyjäliikkeeltä uuden Skoda Superb henkilöauton 53 136,95 

eurolla. Autolle annettiin kahden vuoden pituinen takuu. Auto toimitettiin kuluttajalle 

29.1.2020.

Kuluttajan mukaan autosta puuttuu sellaisia varusteita, joiden kuuluisi olla siinä kaupassa 

sovitun mukaisesti. Lisäksi Infotainment-järjestelmä toimii hitaasti. Osapuolet ovat eri mieltä 

siitä, onko kyseessä myyjän vastuulla oleva kaupan kohteen virhe, ja mikä on 

hinnanalennuksen määrä.


Ostajan vaatimukset perusteluineen

Kuluttaja vaatii myyjäliikkeeltä hyvityksenä 3 000 euroa. Vaatimus perustuu kaupan 

kohteessa ilmenneeseen varusteiden ongelmiin ja siihen sisältyy 2 500 euroa puuttuvista 

varusteista, 400 euroa puutteellisesti toimivista varusteista ja 100 euroa puhelinkuluista ja 

huoltokäynneistä. 

Joitain varusteita puuttuu ja jotkin toimivat puutteellisesti. Kuluttaja totesi virheen keväällä 

2020 ja ilmoitti siitä 9.9.2020. Kuluttaja viittaa vastaukseen ja toteaa, että Personalisointi ei 

toimi eikä ole toiminut syyskuusta 2020 alkaen.

Kuluttaja on myöhemmin todennut, että mahdollisesti schuko-latausjohdon ongelma saatiin 

poistettua 28.12.2020 eli 11 kuukautta luovutuksesta. Kuluttaja ei pidä järkevinä ratkaisuna 

varusteiden ongelmiin korvata integroidut järjestelmät irrallisilla laitteilla tai manuaalisilla 

toiminnoilla. Muuten ongelmiin ei ole tarjottu ratkaisua. GPS-antenni uusittiin 12.1.2021, 

mikä ei poistanut ongelmia. Infotainment järjestelmälle on lupailtu päivitystä useita kertoja 

turhaan.

Ratkaisupyynnön liitteenä on ajoneuvon käyttöohjekirja.


Myyjän vastaus perusteluineen

Myyjäliike ja maahantuoja ovat antaneet asiassa yhteisen vastauksen, ja toteavat voivansa 

maksaa 300 euroa hyvityksenä puuttuvista varusteista.


Maahantuojan toimittamaan myyntimateriaaliin oli jäänyt virheellisesti aiemman

Infotaiment järjestelmän tiedot. Puuttuvia varusteita ovat DVD-soitin 2 SD-korttipaikkaa In 

Car Commonication ja Personalisation 1.1. 


Autossa on uuden sukupolven Infotainment järjestelmä, jossa on lataushybridijärjestelmälle 

tärkeitä ominaisuuksia kuten akun lataukseen liittyvät toiminnot. Järjestelmässä 

karttapäivitykset tehdään internetyhteydellä. In Car Communication vahvistaa kuljettajan 

ääntä takapenkille ja on tarpeen etenkin seitsenpaikkaisessa autossa. Personalisointi 1.1 

tallentaa kuljettajan istuimen säädöt ym. avaimeen. Tässä autossa tallennus voidaan tehdä 

istuimen muistipaikkapainikkeisiin ja se on ollut käytettävissä Connect-palvelun kautta 

viimeistään syyskuusta 2020 alkaen.


Mode 2 -latausjohtoa varten on olemassa ohjelmistopäivitys veloituksetta. Sen jälkeen 

voidaan käyttää täyttä 8A latausvirtaa (n. 1,8 kW). Ennen päivitystä latausvirta oli enintään 

6A ja muut lataustavat olivat normaalisti käytettävissä. Asiakkaalle on lähetetty tästä tieto 

3.12.2020 ja 11.12.2020. Mahdollisten Infotainment-järjestelmän ongelmien vuoksi asiakasta

on pyydetty tuomaan auto huoltoon tarkistettavaksi 16.9. ja 3.12.2020 sekä myös 

11.12.2020. GPS-antenni on uusittu, koska Connect-yhteys oli pätkinyt. Infotainmentjärjestelmälle on tulossa päivitys viikkoon 29 mennessä vuonna 2021.

Vastauksen liitteenä on Autokeskuksen työmääräyksiä. 


Ratkaisun perustelut

Virheellisyyden arviointi 

Virheen arvioinnin lähtökohta on osapuolten välisen sopimuksen sisältö. Uuden auton 

kaupassa virheen olemassa oloa arvioidaan kuluttajansuojalain 5 luvun 12 §:n mukaan. 

Virhearvioinnin perustana ovat ostajan aiheelliset odotukset.

Asiassa on riidatonta, että autosta on puuttunut siihen kuuluneita varusteita. Kuluttaja on 

perustellusti voinut edellyttää, että autossa on hänelle ilmoitetut varusteet, joten kaupassa 

on tällä perusteella kuluttajansuojalain 5 luvun 12 §:n mukainen virhe. Kauppa ei ole kaikilta 

osin vastannut sitä, mistä asiassa on sovittu.


Infotainment-järjestelmän osalta autoon on tehty päivityksiä, ja tältä osin asiassa ei ole 

tarkempaa tietoa siitä, mikä on järjestelmän tilanne päivitysten jälkeen. Asiassa ei ole 

esitetty ulkopuolista selvitystä siitä, voidaanko kuluttajan esiin nostama ongelma katsoa 

kuluttajansuojalain mukaiseksi virheeksi. Tältä osin asiassa ei esitetyn näytön valossa ole 

todettavissa virhettä.


Virheen seuraamukset

Myyjäliike saa omalla kustannuksellaan korjata virheen, jos se tarjoutuu tekemään

korjauksen viipymättä saatuaan tietää virheestä. Myyjällä on oikeus osoittaa ostajalle myyjän

lukuun tehtävän korjauksen paikka. Ostaja saa kieltäytyä virheen korjaamisesta vain

erityisestä syystä, esimerkiksi, jos siitä aiheutuisi hänelle olennaista haittaa. 

Tässä tapauksessa virheen korjaaminen ei ole mahdollista, koska ajoneuvoon ei ole

asennettu puuttuvia varusteita, vaan ne on tarjottu käytettäviksi ulkoisina järjestelminä, joita

ei ole integroitu autoon. Näin ollen tarjottu korjaustapa ei ole asianmukainen, ja asiaa on

arvioitava hinnanalennukseen perustuen.


Hyvityksen määrän arviointi perustuu ilmenneen virheen laadun ja merkityksen ohella

kauppahintaan sekä ostajan aiheellisiin odotuksiin.

Nämä seikat huomioon otettuna lautakunta katsoo, että myyjäliikkeen tulee suorittaa

ostajalle hinnanalennuksena ja korvauksena tarpeellisista kuluista yhteensä 300 euroa.

Päätös oli yksimielinen.

November 2, 2021

SharePoint: Transport-level error has occurred when receiving results from the server

Problem

SharePoint 2016 (on-prem) farm had weird issues on few application and WFE servers. Namely, the servers couldn't connect to SQL Server, but ULS logs showed error:

Unknown SQL Exception 64 occurred. Additional error information from SQL Server is included below.  A transport-level error has occurred when receiving results from the server. (provider: TCP Provider, error: 0 - The specified network name is no longer available.)

When testing the connection with UDL file, the behavior was also strange, i.e., when testing the connection to server without defining any specific database, the test succeeded:


Also when defining a specific database, the test succeeded:



However, when attempting to list the databases by expanding the "Select the database on the server" dropdown, there was first "Unspesified error" error:


Followed by error "Microsoft Data Link
Login failed. Catalog information cannot be retrieved.":


Solution

Solution was (eventually) to fix the Jumbo Packet setting on the servers having the issue. Other servers had value 1514, while the problematic servers had value 9014 and changing value to 1514 made all the SharePoint servers immediately connect to SQL Server, and also the UDL DB listing started working.



September 29, 2021

Edge: Reverting to classic authentication dialog a.k.a disable Windows Hello for HTTP authentication

Problem

In recent Microsoft Edge browser versions 90+, the classic authentication dialog (or NTLM authentication dialog, or Windows authentication prompt) has been replaced by Windows Hello authentication prompt. It's all nice and secure, but at the moment at least, browser password vault extensions such as 1Password cannot fill in the credentials to that modern prompt. What it means is that you need to close the Windows Hello prompt, open password extension, copy username/password to notepad, refresh browser window, paste credentials from notepad to Windows Hello prompt. *yawn*

This is cumbersome in enterprise scenarios with various internal systems such as SharePoint that may require you to login with different credentials from the one you're currently logged into Windows.



Solution

For now the only solution is to disable the Windows Hello prompt in Edge. It will require using Group Policies either on AD level, or on individual machine. The following steps are for individual machine, but if you're an AD admin, you can pick the essential pieces from the instructions and do the same on AD level policy.

  1. First download MS Edge policy file from https://aka.ms/EdgeEnterprise, from the drop-downs, select the version of your Edge, then press GET POLICY FILES


  2. Extract the .cab, and .zip 🙄
  3. Navigate to .\MicrosoftEdgePolicyTemplates\windows\admx folder
  4. Copy msedge.admx to C:\Windows\PolicyDefinitions
  5. Navigate to .\MicrosoftEdgePolicyTemplates\windows\admx\en-US folder (NOTE! or the language of your Windows installation, if not en-US)
  6. Copy msedge.adml to C:\Windows\PolicyDefinitions\en-US
  7. Open Local Group Policy Editor, and navigate to Computer Configuration / Administrative Templates / Microsoft Edge / HTTP Authentication
  8. Edit Windows Hello For HTTP Auth Enabled setting, and set it to Disabled


  9. Click OK to confirm policy setting, and refresh page in Edge - no restart needed
  10. Applauds! Classic authentication prompt is back and you can also access the browser extension

September 20, 2021

Auth0: Invalid RSAES-OAEP padding

Problem

After configuring Auth0 with custom certificates via API, you get Access Denied error when attempting to login.

{ "error": "access_denied", "error_description": "Invalid RSAES-OAEP padding." }


Solution

Add an additional  decryptionKey to the connection's options with the following format.

options: {
  //... other options
  "decryptionKey" : {
        "key": "-----BEGIN PRIVATE KEY-----\n...",
        "cert": "-----BEGIN CERTIFICATE-----\n..."
    }
}

Keep in mind that options are replaced, not merged - so you'll need to send the whole options object to the PATCH call.

April 28, 2021

Azure B2C: Adding missing translations on Page Layouts

Problem

After starting to use Azure B2C custom Page Layout versions newer than 2.0.0, you will find translations are missing on many controls. Documentation is lacking behind, so it will take some trial and error to figure out some of the translation IDs. In the following pictures, you see missing translations marked with beautiful hand drawn red arrows.



Solution

As B2C _should_ already include these translations, the only workaround currently is to manually provide the missing strings in your Custom Policy. The following will work for urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.4 and urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.4.

So first of all, add LocalizedResourceReference elements in the ContentDefinition elements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<ContentDefinition Id="api.signuporsignin">
  <LoadUri>https://xyz.blob.core.windows.net/customui/ocean_blue/unified.html</LoadUri>
  <RecoveryUri>https://xyz.blob.core.windows.net/customui/ocean_blue/exception.html</RecoveryUri>
  <DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.4</DataUri>
  <Metadata>
    <Item Key="DisplayName">Signin and Signup</Item>
  </Metadata>
  <LocalizedResourcesReferences MergeBehavior="Prepend">
    <LocalizedResourcesReference Language="fi" LocalizedResourcesReferenceId="api.signuporsignin.fi" />
  </LocalizedResourcesReferences>
</ContentDefinition>
<ContentDefinition Id="api.selfasserted">
  <LoadUri>https://xyz.blob.core.windows.net/customui/ocean_blue/selfAsserted.html</LoadUri>
  <RecoveryUri>https://xyz.blob.core.windows.net/customui/ocean_blue/exception.html</RecoveryUri>
  <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.4</DataUri>
  <Metadata>
    <Item Key="DisplayName">Collect information from user page</Item>
  </Metadata>
  <LocalizedResourcesReferences MergeBehavior="Prepend">
    <LocalizedResourcesReference Language="fi" LocalizedResourcesReferenceId="api.localaccountpasswordreset.fi" />
  </LocalizedResourcesReferences>
</ContentDefinition>

Then the actual strings you will add in the Localization element.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<LocalizedResources Id="api.signuporsignin.fi">
  <LocalizedStrings>
    <LocalizedString ElementType="ClaimType" ElementId="signInName" StringId="DisplayName">Sähköpostiosoite</LocalizedString>
    <LocalizedString ElementType="ClaimType" ElementId="password" StringId="DisplayName">Salasana</LocalizedString>
    <LocalizedString ElementType="UxElement" StringId="local_intro_generic">Kirjaudu sisään aiemmin luodulla tililläsi</LocalizedString>
  </LocalizedStrings>
</LocalizedResources>
<LocalizedResources Id="api.localaccountpasswordreset.fi">
  <LocalizedStrings>
    <LocalizedString ElementType="ClaimType" ElementId="email" StringId="DisplayName">Sähköpostiosoite</LocalizedString>
    <LocalizedString ElementType="ClaimType" ElementId="VerificationCode" StringId="DisplayName">Vahvistuskoodi</LocalizedString>
    <LocalizedString ElementType="ClaimType" ElementId="signInNames.emailAddress" StringId="DisplayName">Sähköpostiosoite</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="email">Sähköpostiosoite</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="ver_input">Vahvistuskoodi</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="verificationcode">Vahvistuskoodi</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="intro_msg">Syötä sähköpostiosoitteesi ja paina Lähetä vahvistuskoodi -painiketta.</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_send_code">Lähetä vahvistuskoodi</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_verify_code">Vahvista koodi</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="but_send_new_code">Lähetä uusi koodi</LocalizedString>
    <LocalizedString ElementType="DisplayControl" ElementId="emailVerificationSSPRControl" StringId="success_send_code_msg">Vahvistuskoodi on lähetetty sähköpostiisi. Kopioi se alla olevaan syöteruutuun ja paina Vahvista koodi -painiketta.</LocalizedString>
  </LocalizedStrings>
</LocalizedResources>

Please note that this is not a comprehensive list of missing translations, so feel free to comment below if you happen to have a full tested list of translations that will work with the new Page Layouts.