Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using Microsoft.Graph.Auth;
using Microsoft.Graph.PowerShell.Authentication;
using Microsoft.Graph.PowerShell.Authentication.Helpers;

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

using Xunit;
public class AuthenticationHelpersTests
{
Expand Down Expand Up @@ -78,7 +80,7 @@ public void ShouldUseClientCredentialProviderWhenAppOnlyContextIsProvided()
CertificateName = "cn=dummyCert",
ContextScope = ContextScope.Process
};
CreateSelfSignedCert(appOnlyAuthContext.CertificateName);
CreateAndStoreSelfSignedCert(appOnlyAuthContext.CertificateName);

// Act
IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(appOnlyAuthContext);
Expand All @@ -89,10 +91,50 @@ public void ShouldUseClientCredentialProviderWhenAppOnlyContextIsProvided()
// reset
DeleteSelfSignedCert(appOnlyAuthContext.CertificateName);
GraphSession.Reset();

}

private void CreateSelfSignedCert(string certName)
[Fact]
public void ShouldUseInMemoryCertificateWhenProvided()
{
// Arrange
var certificate = CreateSelfSignedCert("cn=inmemorycert");
AuthContext appOnlyAuthContext = new AuthContext
{
AuthType = AuthenticationType.AppOnly,
ClientId = Guid.NewGuid().ToString(),
Certificate = certificate,
ContextScope = ContextScope.Process
};
// Act
IAuthenticationProvider authProvider = AuthenticationHelpers.GetAuthProvider(appOnlyAuthContext);

// Assert
Assert.IsType<ClientCredentialProvider>(authProvider);
var clientCredentialProvider = (ClientCredentialProvider) authProvider;
// Assert: That the certificate created and set above is the same as used here.
Assert.Equal(clientCredentialProvider.ClientApplication.AppConfig.ClientCredentialCertificate, certificate);
GraphSession.Reset();
}
/// <summary>
/// Create and Store a Self Signed Certificate
/// </summary>
/// <param name="certName"></param>
private void CreateAndStoreSelfSignedCert(string certName)
{
var cert = CreateSelfSignedCert(certName);
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
}
}
/// <summary>
/// Create a Self Signed Certificate
/// </summary>
/// <param name="certName"></param>
/// <returns></returns>
private X509Certificate2 CreateSelfSignedCert(string certName)
{
ECDsa ecdsaKey = ECDsa.Create();
CertificateRequest certificateRequest = new CertificateRequest(certName, ecdsaKey, HashAlgorithmName.SHA256);
Expand All @@ -108,11 +150,8 @@ private void CreateSelfSignedCert(string certName)
{
dummyCert = new X509Certificate2(cert.Export(X509ContentType.Pfx, "P@55w0rd"), "P@55w0rd", X509KeyStorageFlags.PersistKeySet);
}
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.Add(dummyCert);
}

return dummyCert;
}

private void DeleteSelfSignedCert(string certificateName)
Expand Down
13 changes: 9 additions & 4 deletions src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
using System.Globalization;
using Microsoft.Graph.PowerShell.Authentication.Interfaces;
using Microsoft.Graph.PowerShell.Authentication.Common;
using System.Security.Cryptography.X509Certificates;

[Cmdlet(VerbsCommunications.Connect, "MgGraph", DefaultParameterSetName = Constants.UserParameterSet)]
[Alias("Connect-Graph")]
Expand Down Expand Up @@ -45,7 +46,7 @@ public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssem
Position = 3,
HelpMessage = "The thumbprint of your certificate. The Certificate will be retrieved from the current user's certificate store.")]
public string CertificateThumbprint { get; set; }

[Parameter(ParameterSetName = Constants.AccessTokenParameterSet,
Position = 1,
HelpMessage = "Specifies a bearer token for Microsoft Graph service. Access tokens do timeout and you'll have to handle their refresh.")]
Expand All @@ -69,6 +70,9 @@ public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssem
[Alias("EnvironmentName", "NationalCloud")]
public string Environment { get; set; }

[Parameter(ParameterSetName = Constants.AppParameterSet, Mandatory = false, HelpMessage = "An x509 Certificate supplied during invocation")]
public X509Certificate2 Certificate { get; set; }

private CancellationTokenSource cancellationTokenSource;

private IGraphEnvironment environment;
Expand Down Expand Up @@ -125,6 +129,7 @@ protected override void ProcessRecord()
authContext.ClientId = ClientId;
authContext.CertificateThumbprint = CertificateThumbprint;
authContext.CertificateName = CertificateName;
authContext.Certificate = Certificate;
// Default to Process but allow the customer to change this via `ContextScope` param.
authContext.ContextScope = this.IsParameterBound(nameof(ContextScope)) ? ContextScope : ContextScope.Process;
}
Expand Down Expand Up @@ -256,10 +261,10 @@ private void ValidateParameters()
this.ThrowParameterError(nameof(ClientId));
}

// Certificate Thumbprint or name
if (string.IsNullOrEmpty(CertificateThumbprint) && string.IsNullOrEmpty(CertificateName))
// Certificate Thumbprint, Name or Actual Certificate
if (string.IsNullOrEmpty(CertificateThumbprint) && string.IsNullOrEmpty(CertificateName) && this.Certificate == null)
{
this.ThrowParameterError($"{nameof(CertificateThumbprint)} or {nameof(CertificateName)}");
this.ThrowParameterError($"{nameof(CertificateThumbprint)} or {nameof(CertificateName)} or {nameof(Certificate)}");
}

// Tenant Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ namespace Microsoft.Graph.PowerShell.Authentication.Helpers
using Microsoft.Graph.PowerShell.Authentication.Models;
using Microsoft.Graph.PowerShell.Authentication.TokenCache;
using Microsoft.Identity.Client;

using System;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

using AuthenticationException = System.Security.Authentication.AuthenticationException;

internal static class AuthenticationHelpers
Expand All @@ -40,7 +42,8 @@ internal static IAuthenticationProvider GetAuthProvider(IAuthContext authContext
.Build();

ConfigureTokenCache(publicClientApp.UserTokenCache, authContext);
authProvider = new DeviceCodeProvider(publicClientApp, authContext.Scopes, async (result) => {
authProvider = new DeviceCodeProvider(publicClientApp, authContext.Scopes, async (result) =>
{
await Console.Out.WriteLineAsync(result.Message);
});
break;
Expand All @@ -51,7 +54,7 @@ internal static IAuthenticationProvider GetAuthProvider(IAuthContext authContext
.Create(authContext.ClientId)
.WithTenantId(authContext.TenantId)
.WithAuthority(authorityUrl)
.WithCertificate(string.IsNullOrEmpty(authContext.CertificateThumbprint) ? GetCertificateByName(authContext.CertificateName) : GetCertificateByThumbprint(authContext.CertificateThumbprint))
.WithCertificate(GetCertificate(authContext))
.Build();

ConfigureTokenCache(confidentialClientApp.AppTokenCache, authContext);
Expand All @@ -61,7 +64,8 @@ internal static IAuthenticationProvider GetAuthProvider(IAuthContext authContext
}
case AuthenticationType.UserProvidedAccessToken:
{
authProvider = new DelegateAuthenticationProvider((requestMessage) => {
authProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer",
new NetworkCredential(string.Empty, GraphSession.Instance.UserProvidedToken).Password);
return Task.CompletedTask;
Expand All @@ -71,6 +75,27 @@ internal static IAuthenticationProvider GetAuthProvider(IAuthContext authContext
}
return authProvider;
}
/// <summary>
/// CGet a
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static X509Certificate2 GetCertificate(IAuthContext context)
{
if (!string.IsNullOrWhiteSpace(context.CertificateThumbprint))
{
return GetCertificateByThumbprint(context.CertificateThumbprint);
}

else if (!string.IsNullOrWhiteSpace(context.CertificateName))
{
return GetCertificateByName(context.CertificateName);
}
else
{
return context.Certificate;
}
}

private static string GetAuthorityUrl(IAuthContext authContext)
{
Expand Down Expand Up @@ -108,7 +133,8 @@ internal static void Logout(IAuthContext authConfig)

private static void ConfigureTokenCache(ITokenCache tokenCache, IAuthContext authContext)
{
tokenCache.SetBeforeAccess((TokenCacheNotificationArgs args) => {
tokenCache.SetBeforeAccess((TokenCacheNotificationArgs args) =>
{
try
{
_cacheLock.EnterReadLock();
Expand All @@ -120,7 +146,8 @@ private static void ConfigureTokenCache(ITokenCache tokenCache, IAuthContext aut
}
});

tokenCache.SetAfterAccess((TokenCacheNotificationArgs args) => {
tokenCache.SetAfterAccess((TokenCacheNotificationArgs args) =>
{
if (args.HasStateChanged)
{
try
Expand Down Expand Up @@ -164,8 +191,8 @@ private static X509Certificate2 GetCertificateByThumbprint(string CertificateThu
.FirstOrDefault();
}
return xCertificate;
}
}

/// <summary>
/// Gets unexpired certificate of the specified certificate subject name for the current user in My store..
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Graph.PowerShell.Authentication
{
public enum AuthenticationType
Expand All @@ -28,5 +30,6 @@ public interface IAuthContext
string Account { get; set; }
string AppName { get; set; }
ContextScope ContextScope { get; set; }
X509Certificate2 Certificate { get; set; }
}
}
4 changes: 4 additions & 0 deletions src/Authentication/Authentication/Models/AuthContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Graph.PowerShell.Authentication
{
public class AuthContext: IAuthContext
Expand All @@ -15,6 +18,7 @@ public class AuthContext: IAuthContext
public string Account { get; set; }
public string AppName { get; set; }
public ContextScope ContextScope { get ; set ; }
public X509Certificate2 Certificate { get; set; }

public AuthContext()
{
Expand Down