Skip to content

Commit

Permalink
Add ability to do cert based authentication and app supersedening (#118)
Browse files Browse the repository at this point in the history
* Fix issue with superseding app having been superseded.
---------

Co-authored-by: Crcobb <[email protected]>
  • Loading branch information
crcobb and crcobb authored Sep 18, 2024
1 parent 8f2041a commit 491ea9f
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 1 deletion.
79 changes: 79 additions & 0 deletions src/Svrooij.WinTuner.CmdLets/Commands/ConnectWtWinTuner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Kiota.Abstractions;
using System.Security.Cryptography.X509Certificates;

namespace Svrooij.WinTuner.CmdLets.Commands;
/// <summary>
Expand All @@ -33,6 +34,10 @@ namespace Svrooij.WinTuner.CmdLets.Commands;
/// <para type="description">Let's say you have a token from another source, just hand us to token and we'll use it to connect to Intune. This token has a limited lifetime, so you'll be responsible for refreshing it.</para>
/// </parameterSet>
/// <parameterSet>
/// <para type="name">ClientCertificateCredentials</para>
/// <para type="description">Client credentials flow using a certificate in the user or local computer store.\r\n\r\nMake sure to mark the certificate as not exportable, this helps in keeping the certificate secure.</para>
/// </parameterSet>
/// <parameterSet>
/// <para type="name">ClientCredentials</para>
/// <para type="description">:::warning Last resort\r\nUsing client credentials is not recommended because you'll have to keep the secret, **secret**!\r\n\r\nPlease let us know if you have to use this method, we might be able to help you with a better solution.\r\n:::</para>
/// </parameterSet>
Expand All @@ -59,6 +64,7 @@ public class ConnectWtWinTuner : DependencyCmdlet<Startup>
private const string DefaultClientCredentialScope = "https://graph.microsoft.com/.default";
private const string ParamSetInteractive = "Interactive";
private const string ParamSetClientCredentials = "ClientCredentials";
private const string ParamSetClientCertificateCredentials = "ClientCertificateCredentials";

/// <summary>
/// Used default scopes
Expand Down Expand Up @@ -144,6 +150,13 @@ public class ConnectWtWinTuner : DependencyCmdlet<Startup>
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the tenant ID. Loaded from `AZURE_TENANT_ID`")]
[Parameter(
Mandatory = true,
Position = 2,
ParameterSetName = ParamSetClientCertificateCredentials,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the tenant ID. Loaded from `AZURE_TENANT_ID`")]
public string? TenantId { get; set; } = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");

/// <summary>
Expand All @@ -156,6 +169,13 @@ public class ConnectWtWinTuner : DependencyCmdlet<Startup>
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the client ID, mandatory for Client Credentials flow. Loaded from `AZURE_CLIENT_ID`")]
[Parameter(
Mandatory = true,
Position = 0,
ParameterSetName = ParamSetClientCertificateCredentials,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the client ID, mandatory for Client Certificate flow. Loaded from `AZURE_CLIENT_ID`")]
[Parameter(
Mandatory = false,
Position = 3,
Expand All @@ -178,6 +198,17 @@ public class ConnectWtWinTuner : DependencyCmdlet<Startup>
HelpMessage = "Specify the client secret. Loaded from `AZURE_CLIENT_SECRET`")]
public string? ClientSecret { get; set; } = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");

/// <summary>
/// Certificate Thumbprint for client authentication
/// </summary>
[Parameter(
Mandatory = true,
Position = 1,
ParameterSetName = ParamSetClientCertificateCredentials,
ValueFromPipeline = false,
HelpMessage = "Specify the thumbprint of the certificate. Loaded from `AZURE_CLIENT_CERT_THUMBPRINT`")]
public string? ClientCertificateThumbprint { get; set; } = Environment.GetEnvironmentVariable("AZURE_CLIENT_CERT_THUMBPRINT");

/// <summary>
/// Specify scopes to use
/// </summary>
Expand All @@ -202,6 +233,13 @@ public class ConnectWtWinTuner : DependencyCmdlet<Startup>
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the scopes to request, default is `https://graph.microsoft.com/.default`")]
[Parameter(
Mandatory = false,
Position = 10,
ParameterSetName = ParamSetClientCertificateCredentials,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the scopes to request, default is `https://graph.microsoft.com/.default`")]
[Parameter(
Mandatory = false,
Position = 10,
Expand Down Expand Up @@ -278,6 +316,47 @@ private IAuthenticationProvider CreateAuthenticationProvider(CancellationToken c
}
}

if (ParameterSetName == ParamSetClientCertificateCredentials)
{
if (!string.IsNullOrEmpty(ClientId) && !string.IsNullOrEmpty(TenantId) &&
!string.IsNullOrEmpty(ClientCertificateThumbprint))
{
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(cert => cert.Thumbprint == ClientCertificateThumbprint);
store.Close();
if (certificate == null)
{
using var storeLocal = new X509Store(StoreName.My, StoreLocation.LocalMachine);
storeLocal.Open(OpenFlags.ReadOnly);
certificate = storeLocal.Certificates.Cast<X509Certificate2>().FirstOrDefault(cert => cert.Thumbprint == ClientCertificateThumbprint);
storeLocal.Close();
}
if (certificate == null)
{
throw new ArgumentException("Cannot find cert thumbprint in User or Machine store");
}

return new Microsoft.Graph.Authentication.AzureIdentityAuthenticationProvider(
new Azure.Identity.ClientCertificateCredential(TenantId, ClientId, certificate,
new Azure.Identity.ClientCertificateCredentialOptions
{
TokenCachePersistenceOptions = new Azure.Identity.TokenCachePersistenceOptions
{
Name = "WinTuner-PowerShell-CC",
UnsafeAllowUnencryptedStorage = true,
}
}
), isCaeEnabled: false, scopes: DefaultClientCredentialScope);

}
else
{
throw new ArgumentException("Not all parameters for client certificate are specified",
nameof(ClientId));
}

}
if (ParameterSetName == ParamSetInteractive)
{
if (NoBroker || RuntimeInformation.IsOSPlatform(OSPlatform.Windows) == false)
Expand Down
2 changes: 1 addition & 1 deletion src/Svrooij.WinTuner.CmdLets/Commands/RemoveWtWin32App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ protected override async Task ProcessAuthenticatedAsync(IAuthenticationProvider
var parentRelationShips = await graphServiceClient.DeviceAppManagement.MobileApps[relationship.TargetId].Relationships.GetAsync(cancellationToken: cancellationToken);
await graphServiceClient.DeviceAppManagement.MobileApps[relationship.TargetId].UpdateRelationships.PostAsync(new Microsoft.Graph.Beta.DeviceAppManagement.MobileApps.Item.UpdateRelationships.UpdateRelationshipsPostRequestBody
{
Relationships = parentRelationShips?.Value?.Where(r => r.TargetId != AppId).ToList() ?? new List<Microsoft.Graph.Beta.Models.MobileAppRelationship>()
Relationships = parentRelationShips?.Value?.Where(r => r.TargetId != AppId && r.TargetType != Microsoft.Graph.Beta.Models.MobileAppRelationshipType.Parent).ToList() ?? new List<Microsoft.Graph.Beta.Models.MobileAppRelationship>()
}, cancellationToken: cancellationToken);
}

Expand Down
58 changes: 58 additions & 0 deletions src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,53 @@
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
</command:syntaxItem>
<command:syntaxItem>
<maml:name>Connect-WtWinTuner</maml:name>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="0" aliases="none">
<maml:name>ClientId</maml:name>
<maml:description>
<maml:para>Specify the client ID, mandatory for Client Certificate flow. Loaded from `AZURE_CLIENT_ID`</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="1" aliases="none">
<maml:name>ClientCertificateThumbprint</maml:name>
<maml:description>
<maml:para>Specify the thumbprint of the certificate. Loaded from `AZURE_CLIENT_CERT_THUMBPRINT`</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="2" aliases="none">
<maml:name>TenantId</maml:name>
<maml:description>
<maml:para>Specify the tenant ID. Loaded from `AZURE_TENANT_ID`</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="10" aliases="none">
<maml:name>Scopes</maml:name>
<maml:description>
<maml:para>Specify the scopes to request, default is `https://graph.microsoft.com/.default`</maml:para>
</maml:description>
<command:parameterValue required="false" variableLength="true">String[]</command:parameterValue>
<dev:type>
<maml:name>String[]</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
</command:syntaxItem>
<command:syntaxItem>
<maml:name>Connect-WtWinTuner</maml:name>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="0" aliases="none">
Expand Down Expand Up @@ -316,6 +363,17 @@
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="False" position="1" aliases="none">
<maml:name>ClientCertificateThumbprint</maml:name>
<maml:description>
<maml:para>Specify the thumbprint of the certificate. Loaded from `AZURE_CLIENT_CERT_THUMBPRINT`</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="10" aliases="none">
<maml:name>Scopes</maml:name>
<maml:description>
Expand Down

0 comments on commit 491ea9f

Please sign in to comment.