This commit is contained in:
Isaiah Williams 2019-12-27 10:24:51 -06:00 коммит произвёл GitHub
Родитель 3da96db226
Коммит 676ad42097
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 199 добавлений и 243 удалений

Просмотреть файл

@ -20,15 +20,18 @@
# Change Log
## Upcoming Release
## 3.0.3 - December 2019
* Authentication
* 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
* 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
* 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
* 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
* 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
* Invoice
* Added the [Get-PartnerUnbilledInvoiceLineItem](https://docs.microsoft.com/powershell/module/partnercenter/Get-PartnerUnbilledInvoiceLineItem) command to get unbilled invoice line items
* 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
* Network
* 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
* Product Upgrades
* Addressed an issue with starting the upgrade process for an Azure Plan
* Subscription

Просмотреть файл

@ -1,3 +1,3 @@
# Code of Conduct
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.
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.

Просмотреть файл

@ -54,13 +54,13 @@ chances of your issue being dealt with quickly:
- **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
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).
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).
### Submitting a Pull Request
Before you submit your Pull Request (PR) consider the following guidelines:
- [Search the repository](https://github.com/Microsoft/Partner-Center-PowerShell/pulls) for an open or closed PR
- [Search the repository](https://github.com/microsoft/Partner-Center-PowerShell/pulls) for an open or closed PR
that relates to your submission. You don't want to duplicate effort.
- Make your changes in a new git fork:
@ -77,4 +77,4 @@ Before you submit your Pull Request (PR) consider the following guidelines:
git push -f
```
That is it! Thank you for your contribution!
That is it! Thank you for your contribution!

Просмотреть файл

@ -5,6 +5,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
@ -30,7 +31,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Factories
GraphClientFactory.CreateDefaultHandlers(null),
new ClientTracingHandler
{
InnerHandler = new HttpClientHandler()
InnerHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }
}), false, null));
/// <summary>

Просмотреть файл

@ -12,5 +12,10 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication
/// Key value for the token cache component.
/// </summary>
public const string TokenCache = "TokenCache";
/// <summary>
/// Key value for the write warning component.
/// </summary>
public const string WriteWarning = "WriteWarning";
}
}

Просмотреть файл

@ -7,24 +7,25 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Extensions;
using Identity.Client.Extensibility;
using Microsoft.Store.PartnerCenter.PowerShell.Models;
using Microsoft.Store.PartnerCenter.PowerShell.Models.Authentication;
using Models;
using Models.Authentication;
/// <summary>
/// Provide a custom Web UI for public client applications to sign-in users and have them consent part of the Authorization code flow.
/// Provide a custom Web UI for client applications to sign-in users and have them consent part of the authorization code flow.
/// </summary>
internal class DefaultOsBrowserWebUi : ICustomWebUi
public class DefaultOsBrowserWebUi : ICustomWebUi
{
/// <summary>
/// The HTML returned after failed authentication.
/// </summary>
private const string CloseWindowFailureHtml = @"<html>
private const string DefaultFailureHtml = @"<html>
<head><title>Authentication Failed</title></head>
<body>
Authentication failed. You can return to the application. Feel free to close this browser tab.
@ -36,7 +37,7 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network
/// <summary>
/// The HTML returned after successful authentication.
/// </summary>
private const string CloseWindowSuccessHtml = @"<html>
private const string DefaultSuccessHtml = @"<html>
<head><title>Authentication Complete</title></head>
<body>
Authentication complete. You can return to the application. Feel free to close this browser tab.
@ -44,18 +45,17 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network
</html>";
/// <summary>
/// The message written to the console.
/// The message to be written to the console.
/// </summary>
private readonly string message;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultOsBrowserWebUi" /> class.
/// </summary>
/// <param name="message">The message written to the console.</param>
/// <param name="message">The message to be written to the console.</param>
public DefaultOsBrowserWebUi(string message)
{
message.AssertNotEmpty(nameof(message));
this.message = message;
}
@ -77,20 +77,40 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network
WriteWarning(message);
using (SingleMessageTcpListener listener = new SingleMessageTcpListener(redirectUri.Port))
return await new HttpListenerInterceptor().ListenToSingleRequestAndRespondAsync(
redirectUri.Port,
GetResponseMessage,
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Gets the response message to be sent to the browser.
/// </summary>
/// <param name="authCodeUri">The URI that contains the authorization code.</param>
/// <returns>The response message to be sent to the browser.</returns>
private MessageAndHttpCode GetResponseMessage(Uri authCodeUri)
{
// 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.
NameValueCollection authCodeQueryKeyValue = HttpUtility.ParseQueryString(authCodeUri.Query);
string errorValue = authCodeQueryKeyValue.Get("error");
if (!string.IsNullOrEmpty(errorValue))
{
Uri authCodeUri = null;
string errorDescription = authCodeQueryKeyValue.Get("error_description");
await listener.ListenToSingleRequestAndRespondAsync(
(uri) =>
{
authCodeUri = uri;
return GetMessageToShowInBroswerAfterAuth(uri);
},
cancellationToken).ConfigureAwait(false);
WriteWarning($"Default OS browser intercepted an URI with an error: {errorValue} {errorDescription}");
return authCodeUri;
string errorMessage = string.Format(
CultureInfo.InvariantCulture,
DefaultFailureHtml,
errorValue,
errorDescription);
return new MessageAndHttpCode(HttpStatusCode.OK, errorMessage);
}
return new MessageAndHttpCode(HttpStatusCode.OK, DefaultSuccessHtml);
}
/// <summary>
@ -130,31 +150,14 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Network
}
/// <summary>
/// Gets the HTML that will be shown in the browser.
/// Writes the warning message to the pipeline.
/// </summary>
/// <param name="uri">The URI returned back from the STS authorization endpoint.</param>
/// <returns>The HTML to be shown in the browser.</returns>
private static string GetMessageToShowInBroswerAfterAuth(Uri uri)
{
NameValueCollection authCodeQueryKeyValue = HttpUtility.ParseQueryString(uri.Query);
string errorString = authCodeQueryKeyValue.Get("error");
if (!string.IsNullOrEmpty(errorString))
{
return string.Format(
CultureInfo.InvariantCulture,
CloseWindowFailureHtml,
errorString,
authCodeQueryKeyValue.Get("error_description"));
}
return CloseWindowSuccessHtml;
}
/// <param name="text">The message to be written to the pipeline.</param>
private void WriteWarning(string message)
{
if (PartnerSession.Instance.TryGetComponent("WriteWarning", out EventHandler<StreamEventArgs> writeWarningEvent))
message.AssertNotEmpty(nameof(message));
if (PartnerSession.Instance.TryGetComponent(ComponentKey.WriteWarning, out EventHandler<StreamEventArgs> writeWarningEvent))
{
writeWarningEvent(this, new StreamEventArgs { Resource = message });
}

Просмотреть файл

@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Store.PartnerCenter.PowerShell.Network
{
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Rest;
/// <summary>
/// Provides the ability to listen and intercept HTTP operations.
/// </summary>
internal class HttpListenerInterceptor
{
/// <summary>
/// Listens for a single request and responds.
/// </summary>
/// <param name="port">The port where to listen for requests.</param>
/// <param name="responseProducer">The function responsible for responding.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns></returns>
public async Task<Uri> ListenToSingleRequestAndRespondAsync(int port, Func<Uri, MessageAndHttpCode> responseProducer, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
HttpListener httpListener = null;
try
{
string urlToListenTo = "http://localhost:" + port + "/";
httpListener = new HttpListener();
httpListener.Prefixes.Add(urlToListenTo);
httpListener.Start();
ServiceClientTracing.Information($"[HttpListenerInterceptor] Listening for authorization code on {urlToListenTo}");
using (cancellationToken.Register(() =>
{
TryStopListening(httpListener);
}))
{
HttpListenerContext context = await httpListener.GetContextAsync().ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
Respond(responseProducer, context);
ServiceClientTracing.Information($"[HttpListenerInterceptor] Received a message on {urlToListenTo}");
// the request URL should now contain the auth code and pkce
return context.Request.Url;
}
}
catch (ObjectDisposedException)
{
cancellationToken.ThrowIfCancellationRequested();
throw;
}
finally
{
TryStopListening(httpListener);
}
}
private void Respond(Func<Uri, MessageAndHttpCode> responseProducer, HttpListenerContext context)
{
MessageAndHttpCode messageAndCode = responseProducer(context.Request.Url);
ServiceClientTracing.Information($"[HttpListenerInterceptor] Processing a response message to the browser. HttpStatus: {messageAndCode.HttpCode}");
switch (messageAndCode.HttpCode)
{
case HttpStatusCode.Found:
context.Response.StatusCode = (int)HttpStatusCode.Found;
context.Response.RedirectLocation = messageAndCode.Message;
break;
case HttpStatusCode.OK:
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(messageAndCode.Message);
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
break;
default:
throw new NotImplementedException("HttpCode not supported" + messageAndCode.HttpCode);
}
context.Response.OutputStream.Close();
}
private static void TryStopListening(HttpListener httpListener)
{
try
{
httpListener?.Abort();
}
catch
{
}
}
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Store.PartnerCenter.PowerShell.Network
{
using System;
using System.Net;
/// <summary>
/// Represents the message and status code used to respond to HTTP requests.
/// </summary>
internal class MessageAndHttpCode
{
/// <summary>
/// Initializes a new instance of the <see cref="MessageAndHttpCode" /> class.
/// </summary>
/// <param name="httpCode">The status code for the HTTP operation.</param>
/// <param name="message">The message that will be sent with the response.</param>
public MessageAndHttpCode(HttpStatusCode httpCode, string message)
{
HttpCode = httpCode;
Message = message ?? throw new ArgumentNullException(nameof(message));
}
/// <summary>
/// Gets the status code for the HTTP operation.
/// </summary>
public HttpStatusCode HttpCode { get; }
/// <summary>
/// Gets the message that will be sent with the response.
/// </summary>
public string Message { get; }
}
}

Просмотреть файл

@ -1,176 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Store.PartnerCenter.PowerShell.Network
{
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Extensions;
/// <summary>
/// Provides the ability to listen for a single message and respond.
/// </summary>
internal class SingleMessageTcpListener : IDisposable
{
/// <summary>
/// Listens for connections from TCP network clients.
/// </summary>
private readonly TcpListener listener;
/// <summary>
/// The port on which to listen for incoming connection attempts.
/// </summary>
private readonly int port;
/// <summary>
/// A flag indicating if the object has already been disposed.
/// </summary>
private bool disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="SingleMessageTcpListener" /> class
/// </summary>
/// <param name="port">The port on which to listen for incoming connection attempts.</param>
public SingleMessageTcpListener(int port)
{
port.AssertPositive(nameof(port));
this.port = port;
listener = new TcpListener(IPAddress.Loopback, this.port);
}
/// <summary>
/// Listens for a single request and responds.
/// </summary>
/// <param name="producer"></param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns></returns>
public async Task ListenToSingleRequestAndRespondAsync(Func<Uri, string> producer, CancellationToken cancellationToken)
{
TcpClient tcpClient = null;
cancellationToken.Register(() => listener.Stop());
listener.Start();
try
{
tcpClient = await AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
await ExtractUriAndRespondAsync(tcpClient, producer, cancellationToken).ConfigureAwait(false);
}
finally
{
tcpClient?.Close();
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
listener?.Stop();
}
disposed = true;
}
private async Task<TcpClient> AcceptTcpClientAsync(CancellationToken token)
{
try
{
return await listener.AcceptTcpClientAsync().ConfigureAwait(false);
}
catch (Exception ex) when (token.IsCancellationRequested)
{
throw new OperationCanceledException("Cancellation was requested while awaiting TCP client connection.", ex);
}
}
private static async Task<string> GetTcpResponseAsync(TcpClient client, CancellationToken cancellationToken)
{
NetworkStream networkStream = client.GetStream();
byte[] readBuffer = new byte[1024];
StringBuilder stringBuilder = new StringBuilder();
int numberOfBytesRead;
do
{
numberOfBytesRead = await networkStream.ReadAsync(readBuffer, 0, readBuffer.Length, cancellationToken)
.ConfigureAwait(false);
string s = Encoding.ASCII.GetString(readBuffer, 0, numberOfBytesRead);
stringBuilder.Append(s);
}
while (networkStream.DataAvailable);
return stringBuilder.ToString();
}
private async Task ExtractUriAndRespondAsync(TcpClient tcpClient, Func<Uri, string> responseProducer, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
string httpRequest = await GetTcpResponseAsync(tcpClient, cancellationToken).ConfigureAwait(false);
Uri uri = ExtractUriFromHttpRequest(httpRequest);
await WriteResponseAsync(responseProducer(uri), tcpClient.GetStream(), cancellationToken).ConfigureAwait(false);
}
private Uri ExtractUriFromHttpRequest(string httpRequest)
{
Regex r1 = new Regex(@"GET \/\?(.*) HTTP");
Match match = r1.Match(httpRequest);
string getQuery;
if (!match.Success)
{
throw new InvalidOperationException("Not a GET query.");
}
getQuery = match.Groups[1].Value;
UriBuilder uriBuilder = new UriBuilder
{
Query = getQuery,
Port = port
};
return uriBuilder.Uri;
}
private static async Task WriteResponseAsync(string message, NetworkStream stream, CancellationToken cancellationToken)
{
string fullResponse = $"HTTP/1.1 200 OK\r\n\r\n{message}";
byte[] response = Encoding.ASCII.GetBytes(fullResponse);
await stream.WriteAsync(response, 0, response.Length, cancellationToken).ConfigureAwait(false);
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -3,7 +3,7 @@
#
# Generated by: Microsoft Corporation
#
# Generated on: 01/10/2020
# Generated on: 12/27/2019
#
@{

Просмотреть файл

@ -3,7 +3,7 @@
#
# Generated by: Microsoft Corporation
#
# Generated on: 01/10/2020
# Generated on: 12/27/2019
#
$PSDefaultParameterValues.Clear()

Просмотреть файл

@ -87,25 +87,8 @@ namespace Microsoft.Store.PartnerCenter.PowerShell.Validations
resource.PhoneNumber));
}
//if (string.IsNullOrEmpty(resource.Country) && !string.IsNullOrEmpty(resource.Region))
//{
// debugAction("The country parameter must be specified to perform address validation. Since it was not provided this operation will not be performed a default value of true will be returned.");
// return true;
//}
//if (string.IsNullOrEmpty(resource.Country))
//{
// debugAction("The country parameter must be specifed to perform address validation. Since it was not provided this operation will be performed and a value of false will be returned.");
// return false;
//}
//if (resource.Country.Equals(ChinaCountryCode, StringComparison.InvariantCultureIgnoreCase) ||
// resource.Country.Equals(MexicoCountryCode, StringComparison.InvariantCultureIgnoreCase) ||
// resource.Country.Equals(UnitedStatesCountryCode, StringComparison.InvariantCultureIgnoreCase))
//{
debugAction("Checking if the address is valid using the partner service.");
return partner.Validations.IsAddressValidAsync(resource).ConfigureAwait(false).GetAwaiter().GetResult();
// }
}
}
}