Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.

Commit d8910b3

Browse files
author
Isaiah Williams
authored
Authentication feature update (#231)
1 parent ba8b731 commit d8910b3

18 files changed

+452
-209
lines changed

ChangeLog.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020

2121
# Change Log
2222

23-
## Upcoming Release
23+
## 3.0.2 - December 2019
24+
25+
* Authentication
26+
* Addressed issue [#230](https://github.com/microsoft/Partner-Center-PowerShell/issues/230) that was caused by a deadlock
27+
28+
## 3.0.1 - December 2019
2429

2530
* Authentication
2631
* Updating the [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) command to make the `CertificateThumbprint` parameter required for the `ServicePrincipalCertificate` parameter set

src/PowerShell/Authenticators/DefaultAuthenticatorBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ public void AppendAuthenticator(Func<IAuthenticator> constructor)
5252
return;
5353
}
5454
}
55-
}
55+
}

src/PowerShell/Authenticators/DelegatingAuthenticator.cs

Lines changed: 139 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Authenticators
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Extensions;
10-
using Factories;
1110
using Identity.Client;
1211
using Models.Authentication;
12+
using Utilities;
1313

1414
/// <summary>
1515
/// Provides a chain of responsibility pattern for authenticators.
@@ -39,38 +39,166 @@ internal abstract class DelegatingAuthenticator : IAuthenticator
3939
public abstract bool CanAuthenticate(AuthenticationParameters parameters);
4040

4141
/// <summary>
42-
///
42+
/// Gets an aptly configured client.
4343
/// </summary>
44-
/// <param name="account"></param>
45-
/// <param name="environment"></param>
46-
/// <param name="redirectUri"></param>
47-
/// <returns></returns>
48-
public IClientApplicationBase GetClient(PartnerAccount account, PartnerEnvironment environment, string redirectUri = null)
44+
/// <param name="account">The account information to be used when generating the client.</param>
45+
/// <param name="environment">The environment where the client is connecting.</param>
46+
/// <param name="redirectUri">The redirect URI for the client.</param>
47+
/// <returns>An aptly configured client.</returns>
48+
public async Task<IClientApplicationBase> GetClientAsync(PartnerAccount account, PartnerEnvironment environment, string redirectUri = null)
4949
{
5050
IClientApplicationBase app;
5151

5252
if (account.IsPropertySet(PartnerAccountPropertyType.CertificateThumbprint) || account.IsPropertySet(PartnerAccountPropertyType.ServicePrincipalSecret))
5353
{
54-
app = SharedTokenCacheClientFactory.CreateConfidentialClient(
54+
app = await CreateConfidentialClientAsync(
5555
$"{environment.ActiveDirectoryAuthority}{account.Tenant}",
5656
account.GetProperty(PartnerAccountPropertyType.ApplicationId),
5757
account.GetProperty(PartnerAccountPropertyType.ServicePrincipalSecret),
5858
GetCertificate(account.GetProperty(PartnerAccountPropertyType.CertificateThumbprint)),
5959
redirectUri,
60-
account.Tenant);
60+
account.Tenant).ConfigureAwait(false);
6161
}
6262
else
6363
{
64-
app = SharedTokenCacheClientFactory.CreatePublicClient(
64+
app = await CreatePublicClient(
6565
$"{environment.ActiveDirectoryAuthority}{account.Tenant}",
6666
account.GetProperty(PartnerAccountPropertyType.ApplicationId),
6767
redirectUri,
68-
account.Tenant);
68+
account.Tenant).ConfigureAwait(false);
6969
}
7070

7171
return app;
7272
}
7373

74+
/// <summary>
75+
/// Determine if this request can be authenticated using the given authenticator, and authenticate if it can.
76+
/// </summary>
77+
/// <param name="parameters">The complex object containing authentication specific information.</param>
78+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
79+
/// <returns><c>true</c> if the request can be authenticated; otherwise <c>false</c>.</returns>
80+
public async Task<AuthenticationResult> TryAuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default)
81+
{
82+
if (CanAuthenticate(parameters))
83+
{
84+
return await AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
85+
}
86+
87+
if (Next != null)
88+
{
89+
return await Next.TryAuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
90+
}
91+
92+
return null;
93+
}
94+
95+
/// <summary>
96+
/// Creates a confidential client used for generating tokens.
97+
/// </summary>
98+
/// <param name="authority">Address of the authority to issue the token</param>
99+
/// <param name="clientId">Identifier of the client requesting the token.</param>
100+
/// <param name="certificate">Certificate used by the client requesting the token.</param>
101+
/// <param name="clientSecret">Secret of the client requesting the token.</param>
102+
/// <param name="redirectUri">The redirect URI for the client.</param>
103+
/// <param name="tenantId">Identifier of the tenant requesting the token.</param>
104+
/// <returns>An aptly configured confidential client.</returns>
105+
private static async Task<IConfidentialClientApplication> CreateConfidentialClientAsync(
106+
string authority = null,
107+
string clientId = null,
108+
string clientSecret = null,
109+
X509Certificate2 certificate = null,
110+
string redirectUri = null,
111+
string tenantId = null)
112+
{
113+
ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder.Create(clientId);
114+
115+
if (!string.IsNullOrEmpty(authority))
116+
{
117+
builder = builder.WithAuthority(authority);
118+
}
119+
120+
if (!string.IsNullOrEmpty(clientSecret))
121+
{
122+
builder = builder.WithClientSecret(clientSecret);
123+
}
124+
125+
if (certificate != null)
126+
{
127+
builder = builder.WithCertificate(certificate);
128+
}
129+
130+
if (!string.IsNullOrEmpty(redirectUri))
131+
{
132+
builder = builder.WithRedirectUri(redirectUri);
133+
}
134+
135+
if (!string.IsNullOrEmpty(tenantId))
136+
{
137+
builder = builder.WithTenantId(tenantId);
138+
}
139+
140+
IConfidentialClientApplication client = builder.WithLogging((level, message, pii) =>
141+
{
142+
PartnerSession.Instance.DebugMessages.Enqueue($"[MSAL] {level} {message}");
143+
}).Build();
144+
145+
146+
PartnerTokenCache tokenCache = new PartnerTokenCache(clientId);
147+
148+
client.UserTokenCache.SetAfterAccess(tokenCache.AfterAccessNotification);
149+
client.UserTokenCache.SetBeforeAccess(tokenCache.BeforeAccessNotification);
150+
151+
await Task.CompletedTask.ConfigureAwait(false);
152+
153+
return client;
154+
}
155+
156+
/// <summary>
157+
/// Creates a public client used for generating tokens.
158+
/// </summary>
159+
/// <param name="authority">Address of the authority to issue the token</param>
160+
/// <param name="clientId">Identifier of the client requesting the token.</param>
161+
/// <param name="redirectUri">The redirect URI for the client.</param>
162+
/// <param name="tenantId">Identifier of the tenant requesting the token.</param>
163+
/// <returns>An aptly configured public client.</returns>
164+
private static async Task<IPublicClientApplication> CreatePublicClient(
165+
string authority = null,
166+
string clientId = null,
167+
string redirectUri = null,
168+
string tenantId = null)
169+
{
170+
PublicClientApplicationBuilder builder = PublicClientApplicationBuilder.Create(clientId);
171+
172+
if (!string.IsNullOrEmpty(authority))
173+
{
174+
builder = builder.WithAuthority(authority);
175+
}
176+
177+
if (!string.IsNullOrEmpty(redirectUri))
178+
{
179+
builder = builder.WithRedirectUri(redirectUri);
180+
}
181+
182+
if (!string.IsNullOrEmpty(tenantId))
183+
{
184+
builder = builder.WithTenantId(tenantId);
185+
}
186+
187+
IPublicClientApplication client = builder.WithLogging((level, message, pii) =>
188+
{
189+
PartnerSession.Instance.DebugMessages.Enqueue($"[MSAL] {level} {message}");
190+
}).Build();
191+
192+
PartnerTokenCache tokenCache = new PartnerTokenCache(clientId);
193+
194+
client.UserTokenCache.SetAfterAccess(tokenCache.AfterAccessNotification);
195+
client.UserTokenCache.SetBeforeAccess(tokenCache.BeforeAccessNotification);
196+
197+
await Task.CompletedTask.ConfigureAwait(false);
198+
199+
return client;
200+
}
201+
74202
/// <summary>
75203
/// Gets the specified certificate.
76204
/// </summary>
@@ -121,27 +249,5 @@ private bool FindCertificateByThumbprint(string thumbprint, StoreLocation storeL
121249
store?.Close();
122250
}
123251
}
124-
125-
/// <summary>
126-
/// Determine if this request can be authenticated using the given authenticator, and authenticate if it can.
127-
/// </summary>
128-
/// <param name="parameters">The complex object containing authentication specific information.</param>
129-
/// <param name="token">The token based authentication information.</param>
130-
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
131-
/// <returns><c>true</c> if the request can be authenticated; otherwise <c>false</c>.</returns>
132-
public async Task<AuthenticationResult> TryAuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default)
133-
{
134-
if (CanAuthenticate(parameters))
135-
{
136-
return await AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
137-
}
138-
139-
if (Next != null)
140-
{
141-
return await Next.TryAuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
142-
}
143-
144-
return null;
145-
}
146252
}
147253
}

src/PowerShell/Authenticators/DeviceCodeAuthenticator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ internal class DeviceCodeAuthenticator : DelegatingAuthenticator
2727
/// </returns>
2828
public override async Task<AuthenticationResult> AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default)
2929
{
30-
IPublicClientApplication app = GetClient(parameters.Account, parameters.Environment).AsPublicClient();
30+
IClientApplicationBase app = await GetClientAsync(parameters.Account, parameters.Environment).ConfigureAwait(false);
3131

3232
ServiceClientTracing.Information($"[DeviceCodeAuthenticator] Calling AcquireTokenWithDeviceCode - Scopes: '{string.Join(", ", parameters.Scopes)}'");
33-
return await app.AcquireTokenWithDeviceCode(parameters.Scopes, deviceCodeResult =>
33+
return await app.AsPublicClient().AcquireTokenWithDeviceCode(parameters.Scopes, deviceCodeResult =>
3434
{
3535
WriteWarning(deviceCodeResult.Message);
3636
return Task.CompletedTask;

src/PowerShell/Authenticators/InteractiveUserAuthenticator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public override async Task<AuthenticationResult> AuthenticateAsync(Authenticatio
5757
}
5858
}
5959

60-
app = GetClient(parameters.Account, parameters.Environment, redirectUri);
60+
app = await GetClientAsync(parameters.Account, parameters.Environment, redirectUri).ConfigureAwait(false);
6161

6262
if (app is IConfidentialClientApplication)
6363
{

src/PowerShell/Authenticators/RefreshTokenAuthenticator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal class RefreshTokenAuthenticator : DelegatingAuthenticator
2525
/// </returns>
2626
public override async Task<AuthenticationResult> AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default)
2727
{
28-
IClientApplicationBase app = GetClient(parameters.Account, parameters.Environment);
28+
IClientApplicationBase app = await GetClientAsync(parameters.Account, parameters.Environment);
2929

3030
ServiceClientTracing.Information("[RefreshTokenAuthenticator] Calling GetAccountsAysnc");
3131
IAccount account = await app.GetAccountAsync(parameters.Account.Identifier).ConfigureAwait(false);

src/PowerShell/Authenticators/ServicePrincipalAuthenticator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ internal class ServicePrincipalAuthenticator : DelegatingAuthenticator
2323
/// </returns>
2424
public override async Task<AuthenticationResult> AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default)
2525
{
26-
IConfidentialClientApplication app = GetClient(parameters.Account, parameters.Environment).AsConfidentialClient();
26+
IClientApplicationBase app = await GetClientAsync(parameters.Account, parameters.Environment).ConfigureAwait(false);
2727

28-
return await app.AcquireTokenForClient(parameters.Scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false);
28+
return await app.AsConfidentialClient().AcquireTokenForClient(parameters.Scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false);
2929
}
3030

3131
/// <summary>

src/PowerShell/Authenticators/SilentAuthenticator.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,16 @@ internal class SilentAuthenticator : DelegatingAuthenticator
2626
/// </returns>
2727
public override async Task<AuthenticationResult> AuthenticateAsync(AuthenticationParameters parameters, CancellationToken cancellationToken = default)
2828
{
29-
IPublicClientApplication app = GetClient(parameters.Account, parameters.Environment).AsPublicClient();
29+
IClientApplicationBase app = await GetClientAsync(parameters.Account, parameters.Environment).ConfigureAwait(false);
3030

3131
ServiceClientTracing.Information(string.Format("[SilentAuthenticator] Calling GetAccountsAsync"));
32-
IEnumerable<IAccount> accounts = await app.GetAccountsAsync().ConfigureAwait(false);
32+
IEnumerable<IAccount> accounts = await app.AsPublicClient().GetAccountsAsync().ConfigureAwait(false);
3333

3434
ServiceClientTracing.Information($"[SilentAuthenticator] Calling AcquireTokenSilent - Scopes: '{string.Join(",", parameters.Scopes)}', UserId: '{((SilentParameters)parameters).UserId}', Number of accounts: '{accounts.Count()}'");
35-
AuthenticationResult authResult = await app.AcquireTokenSilent(
35+
AuthenticationResult authResult = await app.AsPublicClient().AcquireTokenSilent(
3636
parameters.Scopes,
3737
accounts.FirstOrDefault(a => a.HomeAccountId.ObjectId.Equals(((SilentParameters)parameters).UserId)))
38-
.ExecuteAsync(cancellationToken)
39-
.ConfigureAwait(false);
38+
.ExecuteAsync(cancellationToken).ConfigureAwait(false);
4039

4140
return authResult;
4241
}

src/PowerShell/Commands/NewPartnerAccessToken.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Commands
1010
using System.Text;
1111
using Extensions;
1212
using Identity.Client;
13-
using Microsoft.Store.PartnerCenter.PowerShell.Factories;
1413
using Models.Authentication;
1514
using Newtonsoft.Json.Linq;
15+
using Utilities;
1616

1717
[Cmdlet(VerbsCommon.New, "PartnerAccessToken")]
1818
[OutputType(typeof(AuthResult))]
@@ -210,7 +210,7 @@ public override void ExecuteCmdlet()
210210
Message,
211211
CancellationToken).ConfigureAwait(false);
212212

213-
byte[] cacheData = SharedTokenCacheClientFactory.GetMsalCacheStorage(ApplicationId).ReadData();
213+
byte[] cacheData = PartnerTokenCache.GetMsalCacheStorage(ApplicationId).ReadData();
214214

215215
IEnumerable<string> knownPropertyNames = new[] { "AccessToken", "RefreshToken", "IdToken", "Account", "AppMetadata" };
216216

@@ -257,7 +257,7 @@ public override void ExecuteCmdlet()
257257

258258
if (authResult.Account != null)
259259
{
260-
string key = SharedTokenCacheClientFactory.GetTokenCacheKey(authResult, applicationId);
260+
string key = PartnerTokenCache.GetTokenCacheKey(authResult, applicationId);
261261

262262
if (tokens.ContainsKey(key))
263263
{

src/PowerShell/Commands/PartnerAsyncCmdlet.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public abstract class PartnerAsyncCmdlet : PartnerPSCmdlet
6161
/// </summary>
6262
private readonly ConcurrentQueue<Task> outputTasks = new ConcurrentQueue<Task>();
6363

64+
public event EventHandler OnTaskCompleted;
65+
6466
/// <summary>
6567
/// Gets the scheduler used for task execution.
6668
/// </summary>
@@ -73,7 +75,7 @@ protected override void BeginProcessing()
7375
{
7476
base.BeginProcessing();
7577

76-
Scheduler = new ConcurrencyTaskScheduler(1000, CancellationToken);
78+
Scheduler = new ConcurrencyTaskScheduler(100, CancellationToken);
7779

7880
Scheduler.OnError += Scheduler_OnError;
7981

@@ -116,6 +118,8 @@ protected override void EndProcessing()
116118
}
117119
while (!Scheduler.CheckForComplete(500, CancellationToken));
118120

121+
OnTaskCompleted?.Invoke(this, null);
122+
119123
if (!outputTasks.IsEmpty)
120124
{
121125
while (outputTasks.TryDequeue(out Task task))

0 commit comments

Comments
 (0)