Skip to content

Commit

Permalink
Add Authentication Parameter to Web Cmdlets for Basic and OAuth (#5052)
Browse files Browse the repository at this point in the history
Closes #4274

Adds an -Authentication parameter to Invoke-RestMethod and Invoke-WebRequest
Adds an -Token parameter to Invoke-RestMethod and Invoke-WebRequest
Adds an -AllowUnencryptedAuthentication parameter to Invoke-RestMethod and Invoke-WebRequest
Adds tests for various -Authorization uses
-Authentication Parameter has 3 options: Basic, OAuth, and Bearer
Basic requires -Credential and provides RFC-7617 Basic Authorization credentials to the remote server
OAuth and Bearer require the -Token which is a SecureString containing the bearer token to send to the remote server
If any authentication is provided for any transport scheme other than HTTPS, the request will result in an error. A user may use the -AllowUnencryptedAuthentication switch to bypass this behavior and send their secrets unencrypted at their own risk.
-Authentication does not work with -UseDefaultCredentials and will result in an error.
The existing behavior with -Credential is left untouched. When not supplying -Authentication, A user will not receive an error when using -Credential over unencrypted connections.

Code design choice is meant to accommodate more Authentication types in the future.

Documentation Needed

The 3 new parameters will need to be added to the Invoke-RestMethod and Invoke-WebRequest documentation along with examples. Syntax will need to be updated.
  • Loading branch information
markekraus authored and TravisEz13 committed Oct 18, 2017
1 parent 2cc0911 commit 7c9bddf
Show file tree
Hide file tree
Showing 3 changed files with 344 additions and 1 deletion.
Expand Up @@ -10,6 +10,7 @@
using System.Text;
using System.Collections;
using System.Globalization;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
#if !CORECLR
Expand All @@ -19,6 +20,32 @@

namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// The valid values for the -Authentication parameter for Invoke-RestMethod and Invoke-WebRequest
/// </summary>
public enum WebAuthenticationType
{
/// <summary>
/// No authentication. Default.
/// </summary>
None,

/// <summary>
/// RFC-7617 Basic Authentication. Requires -Credential
/// </summary>
Basic,

/// <summary>
/// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token
/// </summary>
Bearer,

/// <summary>
/// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token
/// </summary>
OAuth,
}

/// <summary>
/// Base class for Invoke-RestMethod and Invoke-WebRequest commands.
/// </summary>
Expand Down Expand Up @@ -61,6 +88,22 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet

#region Authorization and Credentials

/// <summary>
/// Gets or sets the AllowUnencryptedAuthentication property
/// </summary>
[Parameter]
public virtual SwitchParameter AllowUnencryptedAuthentication { get; set; }

/// <summary>
/// Gets or sets the Authentication property used to determin the Authentication method for the web session.
/// Authentication does not work with UseDefaultCredentials.
/// Authentication over unencrypted sessions requires AllowUnencryptedAuthentication.
/// Basic: Requires Credential
/// OAuth/Bearer: Requires Token
/// </summary>
[Parameter]
public virtual WebAuthenticationType Authentication { get; set; } = WebAuthenticationType.None;

/// <summary>
/// gets or sets the Credential property
/// </summary>
Expand Down Expand Up @@ -94,6 +137,12 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet
[Parameter]
public virtual SwitchParameter SkipCertificateCheck { get; set; }

/// <summary>
/// Gets or sets the Token property. Token is required by Authentication OAuth and Bearer.
/// </summary>
[Parameter]
public virtual SecureString Token { get; set; }

#endregion

#region Headers
Expand Down Expand Up @@ -274,6 +323,38 @@ internal virtual void ValidateParameters()
ThrowTerminatingError(error);
}

// Authentication
if (UseDefaultCredentials && (Authentication != WebAuthenticationType.None))
{
ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict,
"WebCmdletAuthenticationConflictException");
ThrowTerminatingError(error);
}
if ((Authentication != WebAuthenticationType.None) && (null != Token) && (null != Credential))
{
ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict,
"WebCmdletAuthenticationTokenConflictException");
ThrowTerminatingError(error);
}
if ((Authentication == WebAuthenticationType.Basic) && (null == Credential))
{
ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied,
"WebCmdletAuthenticationCredentialNotSuppliedException");
ThrowTerminatingError(error);
}
if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && (null == Token))
{
ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied,
"WebCmdletAuthenticationTokenNotSuppliedException");
ThrowTerminatingError(error);
}
if (!AllowUnencryptedAuthentication && (Authentication != WebAuthenticationType.None) && (Uri.Scheme != "https"))
{
ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired,
"WebCmdletAllowUnencryptedAuthenticationRequiredException");
ThrowTerminatingError(error);
}

// credentials
if (UseDefaultCredentials && (null != Credential))
{
Expand Down Expand Up @@ -389,7 +470,7 @@ internal virtual void PrepareSession()
//
// handle credentials
//
if (null != Credential)
if (null != Credential && Authentication == WebAuthenticationType.None)
{
// get the relevant NetworkCredential
NetworkCredential netCred = Credential.GetNetworkCredential();
Expand All @@ -398,6 +479,10 @@ internal virtual void PrepareSession()
// supplying a credential overrides the UseDefaultCredentials setting
WebSession.UseDefaultCredentials = false;
}
else if ((null != Credential || null!= Token) && Authentication != WebAuthenticationType.None)
{
ProcessAuthentication();
}
else if (UseDefaultCredentials)
{
WebSession.UseDefaultCredentials = true;
Expand Down Expand Up @@ -666,6 +751,34 @@ private bool IsCustomMethodSet()
return (ParameterSetName == "CustomMethod");
}

private string GetBasicAuthorizationHeader()
{
string unencoded = String.Format("{0}:{1}", Credential.UserName, Credential.GetNetworkCredential().Password);
Byte[] bytes = Encoding.UTF8.GetBytes(unencoded);
return String.Format("Basic {0}", Convert.ToBase64String(bytes));
}

private string GetBearerAuthorizationHeader()
{
return String.Format("Bearer {0}", new NetworkCredential(String.Empty, Token).Password);
}

private void ProcessAuthentication()
{
if(Authentication == WebAuthenticationType.Basic)
{
WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader();
}
else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth)
{
WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader();
}
else
{
Diagnostics.Assert(false, String.Format("Unrecognized Authentication value: {0}", Authentication));
}
}

#endregion Helper Methods
}
}
Expand Up @@ -120,6 +120,21 @@
<data name="AccessDenied" xml:space="preserve">
<value>Access to the path '{0}' is denied.</value>
</data>
<data name="AllowUnencryptedAuthenticationRequired" xml:space="preserve">
<value>The cmdlet cannot protect plain text secrets sent over unencrypted connections. To supress this warning and send plain text secrets over unencrypted networks, reissue the command specifying the AllowUnencryptedAuthentication parameter.</value>
</data>
<data name="AuthenticationConflict" xml:space="preserve">
<value>The cmdlet cannot run because the following conflicting parameters are specified: Authentication and UseDefaultCredentials. Authentication does not support Default Credentials. Specify either Authentication or UseDefaultCredentials, then retry.</value>
</data>
<data name="AuthenticationCredentialNotSupplied" xml:space="preserve">
<value>The cmdlet cannot run because the following parameter is not specified: Credential. The supplied Authentication type requires a Credential. Specify Credential, then retry.</value>
</data>
<data name="AuthenticationTokenNotSupplied" xml:space="preserve">
<value>The cmdlet cannot run because the following parameter is not specified: Token. The supplied Authentication type requires a Token. Specify Token, then retry.</value>
</data>
<data name="AuthenticationTokenConflict" xml:space="preserve">
<value>The cmdlet cannot run because the following conflicting parameters are specified: Credential and Token. Specify either Credential or Token, then retry.</value>
</data>
<data name="BodyConflict" xml:space="preserve">
<value>The cmdlet cannot run because the following conflicting parameters are specified: Body and InFile. Specify either Body or Infile, then retry. </value>
</data>
Expand Down

0 comments on commit 7c9bddf

Please sign in to comment.