Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions documentation/Get-PnPManagedAppId.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ online version: https://pnp.github.io/powershell/cmdlets/Get-PnPManagedAppId.htm
# Get-PnPManagedAppId

## SYNOPSIS
Retrieve an App Id associated with a Url from either the Windows Credential Manager, the MacOS Key chain or if you use the Microsoft.PowerShell.SecretManagement module, a default vault.
Retrieve an App Id associated with a URL from the Windows Credential Manager, macOS Keychain, Linux Secret Service, or a default vault configured through Microsoft.PowerShell.SecretManagement.

## SYNTAX

Expand All @@ -19,7 +19,7 @@ Get-PnPManagedAppId -Url <String>
```

## DESCRIPTION
Returns an associated App Id from the Windows Credential Manager or Mac OS Key Chain Entry.
Returns an associated App Id from the Windows Credential Manager, macOS Keychain, Linux Secret Service, or a default vault configured through Microsoft.PowerShell.SecretManagement.

## EXAMPLES

Expand Down
2 changes: 1 addition & 1 deletion documentation/Remove-PnPManagedAppId.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Remove-PnPManagedAppId -Url <String> [-Force]
```

## DESCRIPTION
Removes an App Id from the Credential Manager
Removes an App Id from the Windows Credential Manager, macOS Keychain, Linux Secret Service, or a default vault configured through Microsoft.PowerShell.SecretManagement.

## EXAMPLES

Expand Down
6 changes: 3 additions & 3 deletions documentation/Set-PnPManagedAppId.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ title: Set-PnPManagedAppId
# Set-PnPManagedAppId

## SYNOPSIS
Sets/Adds an App Id for use with Connect-PnPOnline to the Windows Credential Manager or Mac OS Key Chain Entry. If you the PowerShell Module Microsoft.PowerShell.SecretsStore and Microsoft.PowerShell.SecretsManagement installed and you have defined a default vault without a password than that will be used to store the App Id.
Sets or adds an App Id for use with Connect-PnPOnline in the Windows Credential Manager, macOS Keychain, Linux Secret Service, or a default vault configured through Microsoft.PowerShell.SecretManagement.

## SYNTAX

Expand All @@ -20,7 +20,7 @@ Set-PnPManagedAppId -Url <String> -AppId <String> [-Overwrite]
```

## DESCRIPTION
Adds an App Id entry to the Windows Credential Manager or Mac OS Key Chain Entry. PnP PowerShell will check if an App Id is available when you connect using Connect-PnPOnline -Interactive. If it finds a matching URL it will use the associated App Id. You do not need to specify the -ClientId parameter then.
Adds an App Id entry to the Windows Credential Manager, macOS Keychain, Linux Secret Service, or a default vault configured through Microsoft.PowerShell.SecretManagement. PnP PowerShell will check if an App Id is available when you connect using Connect-PnPOnline -Interactive. If it finds a matching URL it will use the associated App Id. You do not need to specify the -ClientId parameter then.

If you add a Credential with a name of "https://yourtenant.sharepoint.com" it will find a match when you connect to "https://yourtenant.sharepoint.com" but also when you connect to "https://yourtenant.sharepoint.com/sites/demo1". Of course you can specify more granular entries, allow you to automatically provide App Ids for different URLs.

Expand Down Expand Up @@ -49,7 +49,7 @@ Accept wildcard characters: False
```

### -Overwrite
Use parameter to overwrite existing Mac OS Key Chain Entry. Not required on Windows.
Use parameter to overwrite existing macOS Keychain Entry. Not required on Windows or Linux.

```yaml
Type: SwitchParameter
Expand Down
2 changes: 1 addition & 1 deletion pages/articles/defaultclientid.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ To set a client id for tenant with url `https://yourtenant.sharepoint.com`, you
Set-PnPManagedAppId -Url https://yourtenant.sharepoint.com -AppId f0e2b362-8973-4fc7-a293-3c73e2677e79
```

This will add an entry to your Windows Credential Manager or the MacOS keychain if your are on MacOS. Connect-PnPOnline will use this value to match the correct client id with the url you are connecting to and it is not needed use -ClientId anymore, e.g.
This will add an entry to your Windows Credential Manager, the macOS keychain, or the Linux Secret Service. If you have configured a default vault through Microsoft.PowerShell.SecretManagement, that vault will be used instead. Connect-PnPOnline will use this value to match the correct client ID with the URL you are connecting to, so you no longer need to pass -ClientId, e.g.

```powershell
Connect-PnPOnline -Url https://yourtenant.sharepoint.com -Interactive
Expand Down
188 changes: 135 additions & 53 deletions src/Commands/Utilities/CredentialManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
using Microsoft.Win32.SafeHandles;
using PnP.Framework.Modernization.Cache;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

Expand All @@ -17,6 +20,9 @@ namespace PnP.PowerShell.Commands.Utilities
{
internal static class CredentialManager
{
private const string LinuxManagedAppIdSchemaName = "pnp.powershell.managedappid";
private const string LinuxManagedAppIdSecretLabel = "PnP PowerShell managed App Id";
private const string LinuxManagedAppIdCacheDirectory = ".m365pnppowershell";


public static bool AddCredential(string name, string username, SecureString password, bool overwrite)
Expand Down Expand Up @@ -54,27 +60,27 @@ public static bool AddAppId(string name, string appid, bool overwrite)
{
name = $"PnPPSAppId:{name}";
}
if (HasSecretManagement())
{
var defaultVault = GetDefaultVault();

if (!string.IsNullOrEmpty(defaultVault))
{
AddVaultAppId(defaultVault, name, appid);
}
var defaultVault = GetDefaultVaultIfAvailable();
if (!string.IsNullOrEmpty(defaultVault))
{
AddVaultAppId(defaultVault, name, appid);
return true;
}
else

var secureAppId = new NetworkCredential(null, appid).SecurePassword;
if (OperatingSystem.IsWindows())
{
var secureAppId = new NetworkCredential(null, appid).SecurePassword;
if (OperatingSystem.IsWindows())
{

WriteWindowsCredentialManagerEntry(name, null, secureAppId);
}
else if (OperatingSystem.IsMacOS())
{
WriteMacOSKeyChainEntry(name, appid);
}
WriteWindowsCredentialManagerEntry(name, null, secureAppId);
}
else if (OperatingSystem.IsMacOS())
{
WriteMacOSKeyChainEntry(name, appid);
}
else if (OperatingSystem.IsLinux())
{
WriteLinuxAppIdEntry(name, appid);
}
return true;
}
Expand Down Expand Up @@ -122,34 +128,32 @@ public static string GetAppId(string name)
name = $"PnPPSAppId:{name}";
}
// check if Microsoft.PowerShell.SecretManagement is available
if (HasSecretManagement())
var defaultVault = GetDefaultVaultIfAvailable();
if (!string.IsNullOrEmpty(defaultVault))
{
var defaultVault = GetDefaultVault();
return GetVaultAppId(defaultVault, name);
}

if (!string.IsNullOrEmpty(defaultVault))
if (OperatingSystem.IsWindows())
{
var cred = ReadWindowsCredentialManagerEntry(name);
if (cred != null)
{
return GetVaultAppId(defaultVault, name);
return SecureStringToString(cred.Password);
}
}
else
if (OperatingSystem.IsMacOS())
{
if (OperatingSystem.IsWindows())
var cred = ReadMacOSKeyChainEntry(name);
if (cred != null)
{
var cred = ReadWindowsCredentialManagerEntry(name);
if (cred != null)
{
return SecureStringToString(cred.Password);
}
}
if (OperatingSystem.IsMacOS())
{
var cred = ReadMacOSKeyChainEntry(name);
if (cred != null)
{
return SecureStringToString(cred.Password).Trim('"');
}
return SecureStringToString(cred.Password).Trim('"');
}
}
if (OperatingSystem.IsLinux())
{
return ReadLinuxAppIdEntry(name);
}
return null;
}

Expand Down Expand Up @@ -198,27 +202,26 @@ public static bool RemoveAppid(string name)
}
bool success = false;

if (HasSecretManagement())
var defaultVault = GetDefaultVaultIfAvailable();
if (!string.IsNullOrEmpty(defaultVault))
{
var defaultVault = GetDefaultVault();
RemoveVaultCredential(defaultVault, name);
return true;
}

if (!string.IsNullOrEmpty(defaultVault))
{
RemoveVaultCredential(defaultVault, name);
return true;
}
if (OperatingSystem.IsWindows())
{
success = DeleteWindowsCredentialManagerEntry(name);
}
else
if (OperatingSystem.IsMacOS())
{
if (OperatingSystem.IsWindows())
{
success = DeleteWindowsCredentialManagerEntry(name);
}
if (OperatingSystem.IsMacOS())
{
success = DeleteMacOSKeyChainEntry(name);
return success;
}
success = DeleteMacOSKeyChainEntry(name);
return success;
}
if (OperatingSystem.IsLinux())
{
success = DeleteLinuxAppIdEntry(name);
return success;
}
return success;
}
Expand Down Expand Up @@ -248,6 +251,16 @@ private static bool HasSecretManagement()
}
return false;
}

private static string GetDefaultVaultIfAvailable()
{
if (HasSecretManagement())
{
return GetDefaultVault();
}
return null;
}

private static string GetDefaultVault()
{
var defaultVaultName = "";
Expand Down Expand Up @@ -514,6 +527,75 @@ private static bool DeleteMacOSKeyChainEntry(string name)
// return success;
}

private static Storage CreateLinuxManagedAppIdStorage(string name)
{
var cacheDir = Path.Combine(MsalCacheHelper.UserRootDirectory, LinuxManagedAppIdCacheDirectory);
var cacheFileName = $"pnp.managedappid.{GetSha256Hash(name)}.cache";

var properties = new StorageCreationPropertiesBuilder(cacheFileName, cacheDir)
.WithLinuxKeyring(
schemaName: LinuxManagedAppIdSchemaName,
collection: MsalCacheHelper.LinuxKeyRingDefaultCollection,
secretLabel: LinuxManagedAppIdSecretLabel,
attribute1: new KeyValuePair<string, string>("Product", "PnPPowerShell"),
attribute2: new KeyValuePair<string, string>("Name", name))
.Build();

return Storage.Create(properties);
}

private static void WriteLinuxAppIdEntry(string name, string appId)
{
try
{
var storage = CreateLinuxManagedAppIdStorage(name);
storage.VerifyPersistence();
storage.WriteData(Encoding.UTF8.GetBytes(appId));
}
catch (MsalCachePersistenceException ex)
{
throw new InvalidOperationException("Unable to store the managed App Id in Linux Secret Service. Ensure a Secret Service provider such as GNOME Keyring or KWallet is installed and unlocked, or configure a default vault through Microsoft.PowerShell.SecretManagement.", ex);
}
}
Comment on lines +547 to +559
Comment on lines +547 to +559

private static string ReadLinuxAppIdEntry(string name)
{
try
{
var data = CreateLinuxManagedAppIdStorage(name).ReadData();
return data == null || data.Length == 0 ? null : Encoding.UTF8.GetString(data);
}
catch (MsalCachePersistenceException)
{
return null;
}
}

private static bool DeleteLinuxAppIdEntry(string name)
{
try
{
var storage = CreateLinuxManagedAppIdStorage(name);
var data = storage.ReadData();
if (data == null || data.Length == 0)
{
return false;
}

storage.Clear(false);
return true;
}
catch (MsalCachePersistenceException)
{
return false;
}
}

private static string GetSha256Hash(string value)
{
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(value))).ToLowerInvariant();
}

public static string SecureStringToString(SecureString value)
{
IntPtr valuePtr = IntPtr.Zero;
Expand Down
Loading