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

Commit 676ad42

Browse files
author
Isaiah Williams
authored
Core feature update (#245)
1 parent 3da96db commit 676ad42

12 files changed

Lines changed: 199 additions & 243 deletions

ChangeLog.md renamed to CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@
2020

2121
# Change Log
2222

23-
## Upcoming Release
23+
## 3.0.3 - December 2019
2424

2525
* Authentication
2626
* Added the [Register-PartnerTokenCache](https://docs.microsoft.com/powershell/module/partnercenter/Register-PartnerTokenCache) to create, and delete, the control file that determines if a in-memory token cache should be used instead of the default persistent token cache
27-
* Addressed an issue where an InvalidOperationException exception was being encountering with the [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerAccessToken) and [New-PartnerAccessToken](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerAccessToken) commands when specifying an environment
27+
* Addressed an issue where an InvalidOperationException exception was being encountering with the [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) and [New-PartnerAccessToken](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerAccessToken) commands when specifying an environment
28+
* Addressed an issue where an InvalidOperationException exception was being encountered under certain circumstances when invoking [Connect-PartnerCenter](https://docs.microsoft.com/powershell/module/partnercenter/Connect-PartnerCenter) and attempting to authenticate interactively
2829
* Addressed issue [#234](https://github.com/microsoft/Partner-Center-PowerShell/issues/234) that was preventing the [New-PartnerAccessToken](https://docs.microsoft.com/powershell/module/partnercenter/New-PartnerAccessToken) command from executing successfully when being invoked through an Azure Function app
2930
* Invoice
3031
* Added the [Get-PartnerUnbilledInvoiceLineItem](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUnbilledInvoiceLineItem) command to get unbilled invoice line items
3132
* Removed the `Period` parameter from the [Get-PartnerInvoiceLineItem](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerInvoiceLineItem) command because the functionality it enabled has been replaced with the [Get-PartnerUnbilledInvoiceLineItem](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUnbilledInvoiceLineItem) command
33+
* Network
34+
* Addressed an issue where the HTTP response from [Get-PartnerUser](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUser) and [Get-PartnerUserSignInActivity](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUserSignInActivity) was not being correctly written to the debug pipeline
3235
* Product Upgrades
3336
* Addressed an issue with starting the upgrade process for an Azure Plan
3437
* Subscription

CODE_OF_CONDUCT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Code of Conduct
22

3-
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
3+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ chances of your issue being dealt with quickly:
5454
- **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
5555
causing the problem (line of code or commit)
5656

57-
You can file new issues by providing the above information at the [corresponding repository's issues link](https://github.com/Microsoft/Partner-Center-PowerShell/issues/new).
57+
You can file new issues by providing the above information at the [corresponding repository's issues link](https://github.com/microsoft/Partner-Center-PowerShell/issues/new).
5858

5959
### Submitting a Pull Request
6060

6161
Before you submit your Pull Request (PR) consider the following guidelines:
6262

63-
- [Search the repository](https://github.com/Microsoft/Partner-Center-PowerShell/pulls) for an open or closed PR
63+
- [Search the repository](https://github.com/microsoft/Partner-Center-PowerShell/pulls) for an open or closed PR
6464
that relates to your submission. You don't want to duplicate effort.
6565

6666
- Make your changes in a new git fork:
@@ -77,4 +77,4 @@ Before you submit your Pull Request (PR) consider the following guidelines:
7777
git push -f
7878
```
7979

80-
That is it! Thank you for your contribution!
80+
That is it! Thank you for your contribution!

src/PowerShell/Factories/ClientFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories
55
{
66
using System;
77
using System.Collections.Generic;
8+
using System.Net;
89
using System.Net.Http;
910
using System.Reflection;
1011
using System.Threading.Tasks;
@@ -30,7 +31,7 @@ public class ClientFactory : IClientFactory
3031
GraphClientFactory.CreateDefaultHandlers(null),
3132
new ClientTracingHandler
3233
{
33-
InnerHandler = new HttpClientHandler()
34+
InnerHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }
3435
}), false, null));
3536

3637
/// <summary>

src/PowerShell/Models/Authentication/ComponentKey.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@ public static class ComponentKey
1212
/// Key value for the token cache component.
1313
/// </summary>
1414
public const string TokenCache = "TokenCache";
15+
16+
/// <summary>
17+
/// Key value for the write warning component.
18+
/// </summary>
19+
public const string WriteWarning = "WriteWarning";
1520
}
1621
}

src/PowerShell/Network/DefaultOsBrowserWebUi.cs

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,25 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network
77
using System.Collections.Specialized;
88
using System.Diagnostics;
99
using System.Globalization;
10+
using System.Net;
1011
using System.Runtime.InteropServices;
1112
using System.Threading;
1213
using System.Threading.Tasks;
1314
using System.Web;
1415
using Extensions;
1516
using Identity.Client.Extensibility;
16-
using Microsoft.Store.PartnerCenter.PowerShell.Models;
17-
using Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication;
17+
using Models;
18+
using Models.Authentication;
1819

1920
/// <summary>
20-
/// Provide a custom Web UI for public client applications to sign-in users and have them consent part of the Authorization code flow.
21+
/// Provide a custom Web UI for client applications to sign-in users and have them consent part of the authorization code flow.
2122
/// </summary>
22-
internal class DefaultOsBrowserWebUi : ICustomWebUi
23+
public class DefaultOsBrowserWebUi : ICustomWebUi
2324
{
2425
/// <summary>
2526
/// The HTML returned after failed authentication.
2627
/// </summary>
27-
private const string CloseWindowFailureHtml = @"<html>
28+
private const string DefaultFailureHtml = @"<html>
2829
<head><title>Authentication Failed</title></head>
2930
<body>
3031
Authentication failed. You can return to the application. Feel free to close this browser tab.
@@ -36,26 +37,25 @@ internal class DefaultOsBrowserWebUi : ICustomWebUi
3637
/// <summary>
3738
/// The HTML returned after successful authentication.
3839
/// </summary>
39-
private const string CloseWindowSuccessHtml = @"<html>
40+
private const string DefaultSuccessHtml = @"<html>
4041
<head><title>Authentication Complete</title></head>
4142
<body>
4243
Authentication complete. You can return to the application. Feel free to close this browser tab.
4344
</body>
4445
</html>";
4546

4647
/// <summary>
47-
/// The message written to the console.
48+
/// The message to be written to the console.
4849
/// </summary>
4950
private readonly string message;
5051

5152
/// <summary>
5253
/// Initializes a new instance of the <see cref="DefaultOsBrowserWebUi" /> class.
5354
/// </summary>
54-
/// <param name="message">The message written to the console.</param>
55+
/// <param name="message">The message to be written to the console.</param>
5556
public DefaultOsBrowserWebUi(string message)
5657
{
5758
message.AssertNotEmpty(nameof(message));
58-
5959
this.message = message;
6060
}
6161

@@ -77,20 +77,40 @@ public async Task<Uri> AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri r
7777

7878
WriteWarning(message);
7979

80-
using (SingleMessageTcpListener listener = new SingleMessageTcpListener(redirectUri.Port))
80+
return await new HttpListenerInterceptor().ListenToSingleRequestAndRespondAsync(
81+
redirectUri.Port,
82+
GetResponseMessage,
83+
cancellationToken).ConfigureAwait(false);
84+
}
85+
86+
/// <summary>
87+
/// Gets the response message to be sent to the browser.
88+
/// </summary>
89+
/// <param name="authCodeUri">The URI that contains the authorization code.</param>
90+
/// <returns>The response message to be sent to the browser.</returns>
91+
private MessageAndHttpCode GetResponseMessage(Uri authCodeUri)
92+
{
93+
// Parse the URI to understand if an error was returned. This is done just to show the user a nice error message in the browser.
94+
NameValueCollection authCodeQueryKeyValue = HttpUtility.ParseQueryString(authCodeUri.Query);
95+
96+
string errorValue = authCodeQueryKeyValue.Get("error");
97+
98+
if (!string.IsNullOrEmpty(errorValue))
8199
{
82-
Uri authCodeUri = null;
100+
string errorDescription = authCodeQueryKeyValue.Get("error_description");
83101

84-
await listener.ListenToSingleRequestAndRespondAsync(
85-
(uri) =>
86-
{
87-
authCodeUri = uri;
88-
return GetMessageToShowInBroswerAfterAuth(uri);
89-
},
90-
cancellationToken).ConfigureAwait(false);
102+
WriteWarning($"Default OS browser intercepted an URI with an error: {errorValue} {errorDescription}");
91103

92-
return authCodeUri;
104+
string errorMessage = string.Format(
105+
CultureInfo.InvariantCulture,
106+
DefaultFailureHtml,
107+
errorValue,
108+
errorDescription);
109+
110+
return new MessageAndHttpCode(HttpStatusCode.OK, errorMessage);
93111
}
112+
113+
return new MessageAndHttpCode(HttpStatusCode.OK, DefaultSuccessHtml);
94114
}
95115

96116
/// <summary>
@@ -130,31 +150,14 @@ private bool OpenBrowser(string url)
130150
}
131151

132152
/// <summary>
133-
/// Gets the HTML that will be shown in the browser.
153+
/// Writes the warning message to the pipeline.
134154
/// </summary>
135-
/// <param name="uri">The URI returned back from the STS authorization endpoint.</param>
136-
/// <returns>The HTML to be shown in the browser.</returns>
137-
private static string GetMessageToShowInBroswerAfterAuth(Uri uri)
138-
{
139-
NameValueCollection authCodeQueryKeyValue = HttpUtility.ParseQueryString(uri.Query);
140-
141-
string errorString = authCodeQueryKeyValue.Get("error");
142-
143-
if (!string.IsNullOrEmpty(errorString))
144-
{
145-
return string.Format(
146-
CultureInfo.InvariantCulture,
147-
CloseWindowFailureHtml,
148-
errorString,
149-
authCodeQueryKeyValue.Get("error_description"));
150-
}
151-
152-
return CloseWindowSuccessHtml;
153-
}
154-
155+
/// <param name="text">The message to be written to the pipeline.</param>
155156
private void WriteWarning(string message)
156157
{
157-
if (PartnerSession.Instance.TryGetComponent("WriteWarning", out EventHandler<StreamEventArgs> writeWarningEvent))
158+
message.AssertNotEmpty(nameof(message));
159+
160+
if (PartnerSession.Instance.TryGetComponent(ComponentKey.WriteWarning, out EventHandler<StreamEventArgs> writeWarningEvent))
158161
{
159162
writeWarningEvent(this, new StreamEventArgs { Resource = message });
160163
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.Store.PartnerCenter.PowerShell.Network
5+
{
6+
using System;
7+
using System.Net;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Rest;
11+
12+
/// <summary>
13+
/// Provides the ability to listen and intercept HTTP operations.
14+
/// </summary>
15+
internal class HttpListenerInterceptor
16+
{
17+
/// <summary>
18+
/// Listens for a single request and responds.
19+
/// </summary>
20+
/// <param name="port">The port where to listen for requests.</param>
21+
/// <param name="responseProducer">The function responsible for responding.</param>
22+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
23+
/// <returns></returns>
24+
public async Task<Uri> ListenToSingleRequestAndRespondAsync(int port, Func<Uri, MessageAndHttpCode> responseProducer, CancellationToken cancellationToken)
25+
{
26+
cancellationToken.ThrowIfCancellationRequested();
27+
28+
HttpListener httpListener = null;
29+
30+
try
31+
{
32+
string urlToListenTo = "http://localhost:" + port + "/";
33+
34+
httpListener = new HttpListener();
35+
httpListener.Prefixes.Add(urlToListenTo);
36+
37+
httpListener.Start();
38+
ServiceClientTracing.Information($"[HttpListenerInterceptor] Listening for authorization code on {urlToListenTo}");
39+
40+
using (cancellationToken.Register(() =>
41+
{
42+
TryStopListening(httpListener);
43+
}))
44+
{
45+
HttpListenerContext context = await httpListener.GetContextAsync().ConfigureAwait(false);
46+
47+
cancellationToken.ThrowIfCancellationRequested();
48+
49+
Respond(responseProducer, context);
50+
ServiceClientTracing.Information($"[HttpListenerInterceptor] Received a message on {urlToListenTo}");
51+
52+
// the request URL should now contain the auth code and pkce
53+
return context.Request.Url;
54+
}
55+
}
56+
catch (ObjectDisposedException)
57+
{
58+
cancellationToken.ThrowIfCancellationRequested();
59+
60+
throw;
61+
}
62+
finally
63+
{
64+
TryStopListening(httpListener);
65+
}
66+
}
67+
68+
private void Respond(Func<Uri, MessageAndHttpCode> responseProducer, HttpListenerContext context)
69+
{
70+
MessageAndHttpCode messageAndCode = responseProducer(context.Request.Url);
71+
ServiceClientTracing.Information($"[HttpListenerInterceptor] Processing a response message to the browser. HttpStatus: {messageAndCode.HttpCode}");
72+
73+
switch (messageAndCode.HttpCode)
74+
{
75+
case HttpStatusCode.Found:
76+
context.Response.StatusCode = (int)HttpStatusCode.Found;
77+
context.Response.RedirectLocation = messageAndCode.Message;
78+
break;
79+
case HttpStatusCode.OK:
80+
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(messageAndCode.Message);
81+
context.Response.ContentLength64 = buffer.Length;
82+
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
83+
break;
84+
default:
85+
throw new NotImplementedException("HttpCode not supported" + messageAndCode.HttpCode);
86+
}
87+
88+
context.Response.OutputStream.Close();
89+
}
90+
91+
private static void TryStopListening(HttpListener httpListener)
92+
{
93+
try
94+
{
95+
httpListener?.Abort();
96+
}
97+
catch
98+
{
99+
}
100+
}
101+
}
102+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace Microsoft.Store.PartnerCenter.PowerShell.Network
5+
{
6+
using System;
7+
using System.Net;
8+
9+
/// <summary>
10+
/// Represents the message and status code used to respond to HTTP requests.
11+
/// </summary>
12+
internal class MessageAndHttpCode
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="MessageAndHttpCode" /> class.
16+
/// </summary>
17+
/// <param name="httpCode">The status code for the HTTP operation.</param>
18+
/// <param name="message">The message that will be sent with the response.</param>
19+
public MessageAndHttpCode(HttpStatusCode httpCode, string message)
20+
{
21+
HttpCode = httpCode;
22+
Message = message ?? throw new ArgumentNullException(nameof(message));
23+
}
24+
25+
/// <summary>
26+
/// Gets the status code for the HTTP operation.
27+
/// </summary>
28+
public HttpStatusCode HttpCode { get; }
29+
30+
/// <summary>
31+
/// Gets the message that will be sent with the response.
32+
/// </summary>
33+
public string Message { get; }
34+
}
35+
}

0 commit comments

Comments
 (0)