pnp / powershell

PnP PowerShell
https://pnp.github.io/powershell
MIT License
681 stars 348 forks source link

Write access to term store using App-only Service Principal[BUG] #131

Closed AndersRask closed 3 years ago

AndersRask commented 3 years ago

Reporting an Issue or Missing Feature

When doing write operations on Term Store using PnP cmdlet I am getting access denied when using a SharePoint App-only service principal that has Taxonomy write (and also tenant admin) permissions. I am seeing this issue on both SharePointPnPPowerShellOnline and PnP.PowerShell The permissions for everything else works, so permissions are not the issue here. Eg. i can do a Set-PnPTenant -SharingCapability Disabled without getting errors.

When I use a cloud-id user instead and give that user Termstore Administrator permissions everything works as expected (as long as I connect to root of tenant, not admin), so issue is only for App-only service principals.

I also have tried any combination of setting permissions using AppPermissionRequests:

Expected behavior

It should be possible to update term store with a service principal.

Actual behavior

All cmdlets that do write operations fail. Here are an example where I try to import term set using CSV file

# here using root of tenant, but also tried with tenant-admin url
$connection = Connect-PnPOnline -ReturnConnection -Url "https://tenant.sharepoint.com" -ClientSecret $clientSecret -ClientId $clientId
Import-PnPTermSet -GroupName Organisation -Path .\test.csv -SynchronizeDeletions -Connection $connection

Example of exception (this one from PnP.PowerShell but getting same error on old framework)

> $Error[0] | fl * -force

PSMessageDetails      :
Exception             : System.Net.WebException: The remote server returned an error: (401) Unauthorized.
                           at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
                           at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction,
                        Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
                        --- End of stack trace from previous location ---
                           at Microsoft.SharePoint.Client.SPWebRequestExecutor.ExecuteAsync()
                           at Microsoft.SharePoint.Client.ClientRequest.ExecuteQueryToServerAsync(ChunkStringBuilder sb)
                           at Microsoft.SharePoint.Client.ClientRequest.ExecuteQueryAsync()
                           at Microsoft.SharePoint.Client.ClientRuntimeContext.ExecuteQueryAsync()
                           at Microsoft.SharePoint.Client.ClientContext.ExecuteQueryAsync()
                           at
                        Microsoft.SharePoint.Client.ClientContextExtensions.ExecuteQueryImplementation(ClientRuntimeContext
                        clientContext, Int32 retryCount, String userAgent)
                           at
                        Microsoft.SharePoint.Client.ClientContextExtensions.ExecuteQueryImplementation(ClientRuntimeContext
                        clientContext, Int32 retryCount, String userAgent)
                           at Microsoft.SharePoint.Client.ClientContextExtensions.ExecuteQueryRetry(ClientRuntimeContext
                        clientContext, Int32 retryCount, String userAgent)
                           at PnP.PowerShell.Commands.Base.PnPConnection.CloneContext(String url) in
                        D:\a\powershell\powershell\src\Commands\Base\PnPConnection.cs:line 653
                           at PnP.PowerShell.Commands.Base.PnPAdminCmdlet.BeginProcessing() in
                        D:\a\powershell\powershell\src\Commands\Base\PnPAdminCmdlet.cs:line 76
                           at System.Management.Automation.Cmdlet.DoBeginProcessing()
                           at System.Management.Automation.CommandProcessorBase.DoBegin()
TargetObject          :
CategoryInfo          : NotSpecified: (:) [Get-PnPTenant], WebException
FullyQualifiedErrorId : System.Net.WebException,PnP.PowerShell.Commands.Admin.GetTenant
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}

Steps to reproduce behavior

Create an app-only service principal using https://tenant.sharepoint.com/_layouts/15/appregnew.aspx Assign and grant Taxonomy Write permissions (and more) on either https://tenant.sharepoint.com/_layouts/15/appinv.aspx or https://tenant.sharepoint.com/_layouts/15/appinv.aspx depending on access

Connect to either admin or root of tenant with Connect-PnPOnline using ClientID/-secret Try and Set-PnPTermSet, Import-PnPTermSet or any write operation and it will fail.

What is the version of the Cmdlet module you are running?

PnP.PowerShell 0.3.25 SharePointPnPPowerShellOnline 3.28.2012.0

Which operating system/environment are you running PnP PowerShell on?

Tried with both PowerShell 7.1, 7.1.1 and 5.1

erwinvanhunen commented 3 years ago

We have noticed a similar thing, it seems that MS has changed something behind the scenes, but we're still investigating. What you can do is the following:

navigate to the classic termstore administration page. Then add the following user as an administrator

app@sharepoint

notice, not app@sharepoint.com. After that it should work again. In the mean time we will keep investigating.

AndersRask commented 3 years ago

Hi,

thanx for a fast response. I can confirm that it works if i add app@sharepoint as term store administrator.

I guess milage may vary depending on the age of the tenant and what DisableCustomAppAuthentication defaults to, but it was $false on my older Dev tenant.

Do we leave issue open until you have a more permanent solution?

erwinvanhunen commented 3 years ago

It will be automatically closed if not activity for 14 days, but hopefully we will now more before that.

martinlingstuyl commented 3 years ago

Does anyone have more info on this?

I'm suddenly running into this issue as well. Since somewhere in december, code that used to run fine, has started throwing Access Denieds.

Our global term store already has app@sharepoint as admin. But in my case I'm trying to access a sitecollection specific term store.. and these sites have just been created using app principal as well.

Seems like I can't circumvent this.

AndersRask commented 3 years ago

@martinlingstuyl you could add a cloud is user as admin and use that instead of an app principal, this works fine.

martinlingstuyl commented 3 years ago

That is impossible in this case, as it concerns third party tooling. (I have no control on how the connection to SharePoint is created) Aside from that, I would not be particularly happy about using such a setup in an unattended process.

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 5 days

wonderplayer commented 3 years ago

Hello @erwinvanhunen ,

Just faced this issue with PnP.PowerShell version 1.5.63.

mohammadamer commented 3 years ago

Hello @erwinvanhunen

I have the same issue with PnP.PowerShell 1.6.0

wonderplayer commented 3 years ago

Oh, and one thing to note is that the term is actually getting added with Add-PnPTerm command, but with error The current user has insufficient permissions to perform this operation.. And if you have term groups in pnp template, then no terms will be added, as template immediately stops upon error.

wonderplayer commented 3 years ago

And actually this is resolved 😃 Apparently, term store is a separate permission section in App Registration image

Sorry for bothering!

jmcanally commented 3 years ago

We are still facing this issue. I've tried adding app@sharepoint as Term Admin but the account doesn't resolve so I can't save the settings for the Term Store. Also confirmed the service principal has Sites.FullControl.All and TermStore.ReadWrite. Anyone else still seeing this issues?

We see the issue when calling Add-PnPTermToTerm, the term is added successfully but we still see the error for insufficient permissions.

wonderplayer commented 3 years ago

@jmcanally try to add the account with PowerShell. Should work

tleclaire commented 1 year ago

I'm having this problem and am not able to solve it. My App Reg has "Sites.FullControl.All" and "TermStore.ReadWrite.All". I added "app@sharepoint" as TermStore Admin. I'm trying to add a new term to an existing term and getting "UnauthorizedAccessException". I can read everything from the termstore just fine. I' using CSOM 16.1 and PnP.Framework.AuthenticationManager().GetACSAppOnlyContext with clientId and secret to obtain a context. Any help is very much appreciated

martinlingstuyl commented 1 year ago

Hi @tleclaire,

Two problems here:

  1. you're using the wrong function. GetACSAppOnlyContext can be used to acquire an older version of authentication: ACS, which belongs to the era of SharePoint Add-Ins with SharePoint service principals that are not coming from Azure AD. Those Service Principals needed to be created using the appregnew.aspx page. These days you should definitely go for Azure AD auth.

  2. SharePoint does not allow using App only communication using the Client Credentials with secret flow. You should either use a client certificate or use managed identity.

tleclaire commented 1 year ago

"you're using the wrong function. " So what's the right function then? Or do I have to migrate to PNP Core?

martinlingstuyl commented 1 year ago

What's your setup @tleclaire? An Azure Function on .NET 6? An App Service on .NET Framework 4.8?

on .NET Core and up, I'd use the Azure Identity and PnP.Framework nuget packages. You can use the ClientCertificateCredential class (from Azure Identity) to get a token. https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/samples/ClientCertificateCredentialSamples.md

Next, you can use the `PnP.Framework.AuthenticationManager().GetAccessTokenContext("someurl", "the token") to get a client context.

tleclaire commented 1 year ago

I'm building an ASP.NET Core 6.0 API and need to access some sharepoint ressources

martinlingstuyl commented 1 year ago

In that case what I described is precisely what you could do. Will you be hosting it on Azure? In that case you could also use Managed Identity.

https://blog.yannickreekmans.be/secretless-applications-use-azure-identity-sdk-to-access-data-with-a-managed-identity/

tleclaire commented 1 year ago

Thanks, I'll check that out, very much appreciated.

martinlingstuyl commented 1 year ago

Feel free to ask again if you're not getting it running!

tleclaire commented 1 year ago

From the docs: "Managed identities can't be used for authentication of locally running apps" If that's the case, it's totally hard to develop without the possibillity to have it running localy. Beside that, the process of setting this up alltogether is way to complicated, if MS wants this to be used, it must be made usable first. Don't get me wrong, I appreciate the help very much but this is getting a mess, especially on a customers Azure.

martinlingstuyl commented 1 year ago

Hi @tleclaire, correct, for dev purposes you need another way of authentication.

I was kind of triggered by your remarks, and I can understand that the total picture can get pretty unclear. So I quickly wrote a blog post on how to loop all these things together:

https://www.blimped.nl/running-dotnet-function-apps-or-app-services-accessing-microsoft-365/

I'm interested to know what you think of it!

tleclaire commented 1 year ago

Hi Martin,

thanks for setting this up, it looks really helpful and I will definitely give it try in the next days. Where would you store the certificate if you where working with multiple devs on the project? Would you just share it?

Best regards Thomas

martinlingstuyl commented 1 year ago

🤙 I'd let all devs create their own certificate, @tleclaire. You can upload all these CER files to Azure AD anyway (see blog) no reason to share it.

Ddv0623 commented 1 year ago

@jmcanally try to add the account with PowerShell. Should work

Can you show me what command you use to update the TermStore Admin?

kachihro commented 3 months ago

I'm having the same issue with a 'managed identity' from within Azure Function (PowerShell). Within my PowerShell, I can connect to the ADMIN site - and create a new site. And also - add a list/content type, and add a SPFeature.

This is using Connect-PnPOnline $siteUrl -ManagedIdentity

I've included "Sites.FullControl.All" and also "TermStore.ReadWrite.All" But - I can 'read' from TermStore - but get access denied for "New-PnPTerm".

YES - I've added app@sharepoint - but it still doesn't work.

JoeyInvictus commented 3 weeks ago

Any updates? I been getting the same errors Get-PnPList : The remote server returned an error: (401) Unauthorized.