Refactor common auth components to new Base library, enable HttpClien… (#9)

* Refactor common auth components to new Base library, enable HttpClient injection or more static usage, add Tests for WCF & OWIN #5 [#154972918, #158446551]

* cleaner separation of client_credentials and authorization_code flows, connect more dots with new params (logging, httpclient, etc)

* Use the redirectUri when exchanging code for token

* Move towards only having one CloudFoundryTokenKeyResolver
This commit is contained in:
Tim Hess 2019-01-17 12:09:23 -06:00 коммит произвёл GitHub
Родитель db97b64848
Коммит 25f17f498e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
80 изменённых файлов: 2811 добавлений и 937 удалений

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

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2010
# Visual Studio Version 16
VisualStudioVersion = 16.0.28315.86
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7608EFF0-BADC-4259-A760-FA312F9887CD}"
EndProject
@ -42,6 +42,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.DataProte
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.DataProtection.CredHubBase.Test", "test\Steeltoe.Security.DataProtection.CredHubBase.Test\Steeltoe.Security.DataProtection.CredHubBase.Test.csproj", "{5F0D59A7-FB7D-4CA6-8DC4-2BDF50D176AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.CloudFoundryBase", "src\Steeltoe.Security.Authentication.CloudFoundryBase\Steeltoe.Security.Authentication.CloudFoundryBase.csproj", "{F5FA179B-3731-485B-B2D3-994AE05166EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Steeltoe.Security.Authentication.CloudFoundryBase.Test", "test\Steeltoe.Security.Authentication.CloudFoundryBaseTest\Steeltoe.Security.Authentication.CloudFoundryBase.Test.csproj", "{350CC18B-D550-4207-9023-E90C9B61BF6C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.Authentication.CloudFoundryOwin.Test", "test\Steeltoe.Security.Authentication.CloudFoundryOwin.Test\Steeltoe.Security.Authentication.CloudFoundryOwin.Test.csproj", "{859C51B8-D6DF-473B-959A-4F0F1533ADD2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steeltoe.Security.Authentication.CloudFoundryWcf.Test", "test\Steeltoe.Security.Authentication.CloudFoundryWcf.Test\Steeltoe.Security.Authentication.CloudFoundryWcf.Test.csproj", "{950B0827-634F-43F9-86C4-4E615ED36A22}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -88,6 +96,22 @@ Global
{5F0D59A7-FB7D-4CA6-8DC4-2BDF50D176AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F0D59A7-FB7D-4CA6-8DC4-2BDF50D176AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F0D59A7-FB7D-4CA6-8DC4-2BDF50D176AF}.Release|Any CPU.Build.0 = Release|Any CPU
{F5FA179B-3731-485B-B2D3-994AE05166EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5FA179B-3731-485B-B2D3-994AE05166EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5FA179B-3731-485B-B2D3-994AE05166EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5FA179B-3731-485B-B2D3-994AE05166EA}.Release|Any CPU.Build.0 = Release|Any CPU
{350CC18B-D550-4207-9023-E90C9B61BF6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{350CC18B-D550-4207-9023-E90C9B61BF6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{350CC18B-D550-4207-9023-E90C9B61BF6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{350CC18B-D550-4207-9023-E90C9B61BF6C}.Release|Any CPU.Build.0 = Release|Any CPU
{859C51B8-D6DF-473B-959A-4F0F1533ADD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{859C51B8-D6DF-473B-959A-4F0F1533ADD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{859C51B8-D6DF-473B-959A-4F0F1533ADD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{859C51B8-D6DF-473B-959A-4F0F1533ADD2}.Release|Any CPU.Build.0 = Release|Any CPU
{950B0827-634F-43F9-86C4-4E615ED36A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{950B0827-634F-43F9-86C4-4E615ED36A22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{950B0827-634F-43F9-86C4-4E615ED36A22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{950B0827-634F-43F9-86C4-4E615ED36A22}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -103,6 +127,10 @@ Global
{B5EDB484-9639-4F81-9E8E-639EA866ECA6} = {BA3B33D3-64A5-4219-B4C2-787C4BD505F7}
{E698B09A-4865-49B4-A132-DD91701CF1C9} = {7608EFF0-BADC-4259-A760-FA312F9887CD}
{5F0D59A7-FB7D-4CA6-8DC4-2BDF50D176AF} = {BA3B33D3-64A5-4219-B4C2-787C4BD505F7}
{F5FA179B-3731-485B-B2D3-994AE05166EA} = {7608EFF0-BADC-4259-A760-FA312F9887CD}
{350CC18B-D550-4207-9023-E90C9B61BF6C} = {BA3B33D3-64A5-4219-B4C2-787C4BD505F7}
{859C51B8-D6DF-473B-959A-4F0F1533ADD2} = {BA3B33D3-64A5-4219-B4C2-787C4BD505F7}
{950B0827-634F-43F9-86C4-4E615ED36A22} = {BA3B33D3-64A5-4219-B4C2-787C4BD505F7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BED45957-EDAD-4686-B959-BD5DA962F7B0}

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

@ -20,6 +20,7 @@
<JwtTokensVersion>5.2.2</JwtTokensVersion>
<TestSdkVersion>15.7.2</TestSdkVersion>
<MockHttp>4.0.0</MockHttp>
<MoqVersion>4.8.2</MoqVersion>
<XunitAnalyzersVersion>0.9.0</XunitAnalyzersVersion>
<XunitVersion>2.3.1</XunitVersion>
<XunitStudioVersion>2.3.1</XunitStudioVersion>

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

@ -20,6 +20,7 @@
<JwtTokensVersion>5.2.2</JwtTokensVersion>
<TestSdkVersion>15.7.2</TestSdkVersion>
<MockHttp>4.0.0</MockHttp>
<MoqVersion>4.8.2</MoqVersion>
<XunitAnalyzersVersion>0.9.0</XunitAnalyzersVersion>
<XunitVersion>2.3.1</XunitVersion>
<XunitStudioVersion>2.3.1</XunitStudioVersion>

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

@ -20,6 +20,7 @@
<JwtTokensVersion>5.2.2</JwtTokensVersion>
<TestSdkVersion>15.7.2</TestSdkVersion>
<MockHttp>4.0.0</MockHttp>
<MoqVersion>4.8.2</MoqVersion>
<XunitAnalyzersVersion>0.9.0</XunitAnalyzersVersion>
<XunitVersion>2.3.1</XunitVersion>
<XunitStudioVersion>2.3.1</XunitStudioVersion>

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

@ -0,0 +1,69 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public class AuthServerOptions
{
/// <summary>
/// Gets or sets the location of the OAuth server
/// </summary>
public string AuthorizationUrl { get; set; }
/// <summary>
/// Gets or sets the location the user is sent to after authentication
/// </summary>
public string CallbackUrl { get; set; }
/// <summary>
/// Gets or sets the application's client id for interacting with the auth server
/// </summary>
public string ClientId { get; set; } = CloudFoundryDefaults.ClientId;
/// <summary>
/// Gets or sets the application's client secret for interacting with the auth server
/// </summary>
public string ClientSecret { get; set; } = CloudFoundryDefaults.ClientSecret;
/// <summary>
/// Gets or sets the name of the authentication type currently in use
/// </summary>
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets the timeout (in ms) for calls to the auth server
/// </summary>
public int ClientTimeout { get; set; } = 3000;
/// <summary>
/// Gets or sets additional scopes beyond 'openid' when requesting tokens
/// </summary>
public string AdditionalTokenScopes { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a scopes to require
/// </summary>
public string[] RequiredScopes { get; set; }
/// <summary>
/// Gets or sets a list of additional audiences to use with token validation
/// </summary>
public string[] AdditionalAudiences { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to validate SSO server certificate
/// </summary>
public bool ValidateCertificates { get; set; } = true;
}
}

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

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public class CloudFoundryDefaults
@ -21,14 +23,30 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
public const string AuthenticationScheme = "CloudFoundry";
public const string DisplayName = "CloudFoundry";
public const string AuthorizationUri = "/oauth/authorize";
public const string AccessTokenUri = "/oauth/token";
public const string UserInfoUri = "/userinfo";
public const string CheckTokenUri = "/check_token";
public const string JwtTokenKey = "/token_keys";
public const string JwtTokenUri = "/token_keys";
[Obsolete("Use JwtTokenUri instead")]
public const string JwtTokenKey = JwtTokenUri;
public const string OAuthServiceUrl = "Default_OAuthServiceUrl";
public const string ClientId = "Default_ClientId";
public const string ClientSecret = "Default_ClientSecret";
public const string CallbackPath = "/signin-cloudfoundry";
public const bool ValidateCertificates = true;
public const string ParamsClientId = "client_id";
public const string ParamsClientSecret = "client_secret";
public const string ParamsResponseType = "response_type";
public const string ParamsScope = "scope";
public const string ParamsRedirectUri = "redirect_uri";
public const string ParamsGrantType = "grant_type";
public const string ParamsTokenFormat = "token_format";
public const string ParamsCode = "code";
}
}

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

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using Steeltoe.Common;
using System;
@ -24,36 +25,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
{
private static DateTime baseTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static DateTime GetIssueTime(JObject payload)
{
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
var time = payload.Value<long>("iat");
return ToAbsoluteUTC(time);
}
public static DateTime GetExpTime(JObject payload)
{
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
var time = payload.Value<long>("exp");
return ToAbsoluteUTC(time);
}
private static DateTime ToAbsoluteUTC(long secondsPastEpoch)
{
return baseTime.AddSeconds(secondsPastEpoch);
}
#pragma warning disable SA1202 // Elements must be ordered by access
public static List<string> GetScopes(JObject user)
#pragma warning restore SA1202 // Elements must be ordered by access
{
if (user == null)
{
@ -67,15 +39,13 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
return result;
}
var asValue = scopes as JValue;
if (asValue != null)
if (scopes is JValue asValue)
{
result.Add(asValue.Value<string>());
return result;
}
var asArray = scopes as JArray;
if (asArray != null)
if (scopes is JArray asArray)
{
foreach (string s in asArray)
{
@ -106,5 +76,62 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
return null;
}
}
public static TokenValidationParameters GetTokenValidationParameters(TokenValidationParameters parameters, string keyUrl, HttpMessageHandler handler, bool validateCertificates, AuthServerOptions options = null)
{
if (parameters == null)
{
parameters = new TokenValidationParameters();
}
var tokenValidator = new CloudFoundryTokenValidator(options ?? new AuthServerOptions());
parameters.ValidateAudience = false;
parameters.ValidateIssuer = true;
parameters.ValidateLifetime = true;
parameters.IssuerValidator = tokenValidator.ValidateIssuer;
parameters.AudienceValidator = tokenValidator.ValidateAudience;
var tkr = new CloudFoundryTokenKeyResolver(keyUrl, handler, validateCertificates);
parameters.IssuerSigningKeyResolver = tkr.ResolveSigningKey;
return parameters;
}
/// <summary>
/// Retrieves the time at which a token was issued
/// </summary>
/// <param name="payload">Contents of a JWT</param>
/// <returns>The <see cref="DateTime"/> representation of a token's issued-at time</returns>
public static DateTime GetIssueTime(JObject payload)
{
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
var time = payload.Value<long>("iat");
return ToAbsoluteUTC(time);
}
/// <summary>
/// Retrieves expiration time property (exp) in a <see cref="JObject"/>
/// </summary>
/// <param name="payload">Contents of a JWT</param>
/// <returns>The <see cref="DateTime"/> representation of a token's expiration</returns>
public static DateTime GetExpTime(JObject payload)
{
if (payload == null)
{
throw new ArgumentNullException(nameof(payload));
}
var time = payload.Value<long>("exp");
return ToAbsoluteUTC(time);
}
private static DateTime ToAbsoluteUTC(long secondsPastEpoch)
{
return baseTime.AddSeconds(secondsPastEpoch);
}
}
}

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

@ -30,9 +30,9 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
{
internal static ConcurrentDictionary<string, SecurityKey> Resolved { get; set; } = new ConcurrentDictionary<string, SecurityKey>();
private string _jwtKeyUrl;
private HttpMessageHandler _httpHandler;
private bool _validateCertificates;
private readonly string _jwtKeyUrl;
private readonly HttpMessageHandler _httpHandler;
private readonly bool _validateCertificates;
public CloudFoundryTokenKeyResolver(string jwtKeyUrl, HttpMessageHandler httpHandler, bool validateCertificates)
{

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

@ -0,0 +1,108 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public class CloudFoundryTokenValidator
{
private readonly AuthServerOptions _options;
public CloudFoundryTokenValidator(AuthServerOptions options = null)
{
_options = options ?? new AuthServerOptions();
}
/// <summary>
/// Validate that a token was issued by UAA
/// </summary>
/// <param name="issuer">Token issuer</param>
/// <param name="securityToken">[Not used] Token to validate</param>
/// <param name="validationParameters">[Not used]</param>
/// <returns>The issuer, if valid, else <see langword="null" /></returns>
public virtual string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
if (issuer.Contains("uaa"))
{
return issuer;
}
return null;
}
/// <summary>
/// Validate that a token was meant for approved audience(s)
/// </summary>
/// <param name="audiences">The list of audiences the token is valid for</param>
/// <param name="securityToken">[Not used] The token being validated</param>
/// <param name="validationParameters">[Not used]</param>
/// <returns><see langword="true"/> if the audience matches the client id or any value in AdditionalAudiences</returns>
public virtual bool ValidateAudience(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
foreach (string audience in audiences)
{
if (audience.Equals(_options.ClientId))
{
return true;
}
if (_options.AdditionalAudiences != null)
{
bool found = _options.AdditionalAudiences.Any(x => x.Equals(audience));
if (found)
{
return true;
}
}
}
return false;
}
/// <summary>
/// This method validates scopes provided in configuration,
/// to perform scope based Authorization
/// </summary>
/// <param name="validJwt">JSON Web token</param>
/// <returns>true if scopes validated</returns>
protected virtual bool ValidateScopes(JwtSecurityToken validJwt)
{
if (_options.RequiredScopes == null || !_options.RequiredScopes.Any())
{
return true; // nocheck
}
if (!validJwt.Claims.Any(x => x.Type.Equals("scope") || x.Type.Equals("authorities")))
{
return false; // no scopes at all
}
bool found = false;
foreach (Claim claim in validJwt.Claims)
{
if (claim.Type.Equals("scope") || (claim.Type.Equals("authorities") && _options.RequiredScopes.Any(x => x.Equals(claim.Value))))
{
return true;
}
}
return found;
}
}
}

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

@ -13,11 +13,10 @@
// limitations under the License.
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public class OpenIDTokenResponse
public class OpenIdTokenResponse
{
[JsonProperty(PropertyName = "id_token")]
public string IdentityToken { get; set; }

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

@ -0,0 +1,17 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundryBase.Test")]

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

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<Description>Base Security Provider for CloudFoundry</Description>
<VersionPrefix>$(SteeltoeVersion)</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Authors>Pivotal;dtillman</Authors>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AssemblyName>Steeltoe.Security.Authentication.CloudFoundryBase</AssemblyName>
<PackageId>Steeltoe.Security.Authentication.CloudFoundryBase</PackageId>
<PackageTags>CloudFoundry;ASPNET;ASPNET Core;Security;OAuth2;SSO;OpenID</PackageTags>
<PackageIconUrl>https://steeltoe.io/images/transparent.png</PackageIconUrl>
<PackageProjectUrl>https://steeltoe.io</PackageProjectUrl>
<PackageLicenseUrl>https://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\Steeltoe.Security.Authentication.CloudFoundryBase.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="$(JwtTokensVersion)" />
<PackageReference Include="Steeltoe.CloudFoundry.ConnectorBase" Version="$(SteeltoeConnectorVersion)" />
<PackageReference Include="Steeltoe.Common.Http" Version="$(SteeltoeCommonVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
</Project>

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

@ -0,0 +1,216 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Steeltoe.Common.Http;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public class TokenExchanger
{
private readonly ILogger _logger;
private readonly AuthServerOptions _options;
private readonly HttpClient _httpClient;
public TokenExchanger(AuthServerOptions options, HttpClient httpclient = null, ILogger logger = null)
{
_options = options;
_httpClient = httpclient ?? HttpClientHelper.GetHttpClient(options.ValidateCertificates, options.ClientTimeout);
_logger = logger;
}
/// <summary>
/// Perform the HTTP call to exchange an authorization code for a token
/// </summary>
/// <param name="code">The auth code to exchange</param>
/// <param name="targetUrl">The full address of the token endpoint</param>
/// <param name="cancellationToken">Your CancellationToken</param>
/// <returns>The response from the remote server</returns>
public async Task<HttpResponseMessage> ExchangeCodeForToken(string code, string targetUrl, CancellationToken cancellationToken)
{
var requestParameters = AuthCodeTokenRequestParameters(code);
HttpRequestMessage requestMessage = GetTokenRequestMessage(requestParameters, targetUrl);
_logger?.LogDebug("Exchanging code {code} for token at {accessTokenUrl}", code, targetUrl);
HttpClientHelper.ConfigureCertificateValidatation(
_options.ValidateCertificates,
out SecurityProtocolType protocolType,
out RemoteCertificateValidationCallback prevValidator);
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(requestMessage, cancellationToken);
}
finally
{
HttpClientHelper.RestoreCertificateValidation(_options.ValidateCertificates, protocolType, prevValidator);
}
return response;
}
/// <summary>
/// Passes an authorization code to OAuth server, maps server's <see cref="OpenIdTokenResponse"/> mapped to <see cref="ClaimsIdentity"/>
/// </summary>
/// <param name="code">Auth code received after user logs in at remote server</param>
/// <returns>The user's ClaimsIdentity</returns>
public async Task<ClaimsIdentity> ExchangeAuthCodeForClaimsIdentity(string code)
{
HttpResponseMessage response = await ExchangeCodeForToken(code, _options.AuthorizationUrl, default(CancellationToken));
if (response.IsSuccessStatusCode)
{
var tokens = await response.Content.ReadAsJsonAsync<OpenIdTokenResponse>();
#if DEBUG
_logger?.LogTrace("Identity token received: {identityToken}", tokens.IdentityToken);
_logger?.LogTrace("Access token received: {accessToken}", tokens.AccessToken);
#endif
JwtSecurityToken securityToken = new JwtSecurityToken(tokens.IdentityToken);
return BuildIdentityWithClaims(securityToken.Claims, tokens.Scope, tokens.AccessToken);
}
else
{
_logger?.LogError("Failed call to exchange code for token : " + response.StatusCode);
_logger?.LogWarning(response.ReasonPhrase);
_logger?.LogInformation(await response.Content.ReadAsStringAsync());
return null;
}
}
/// <summary>
/// Get an access token using client_credentials grant
/// </summary>
/// <param name="targetUrl">full address of the token endpoint at the auth server</param>
/// <returns>HttpResponse from the auth server</returns>
public async Task<HttpResponseMessage> GetAccessTokenWithClientCredentials(string targetUrl)
{
HttpRequestMessage requestMessage = GetTokenRequestMessage(ClientCredentialsTokenRequestParameters(), targetUrl);
HttpClientHelper.ConfigureCertificateValidatation(_options.ValidateCertificates, out SecurityProtocolType protocolType, out RemoteCertificateValidationCallback prevValidator);
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(requestMessage);
}
finally
{
HttpClientHelper.RestoreCertificateValidation(_options.ValidateCertificates, protocolType, prevValidator);
}
return response;
}
/// <summary>
/// Builds an <see cref="HttpRequestMessage"/> that will POST with the params to the target
/// </summary>
/// <param name="parameters">Body of the request to send</param>
/// <param name="targetUrl">Location to send the request</param>
/// <returns>A request primed for receiving a token</returns>
internal HttpRequestMessage GetTokenRequestMessage(List<KeyValuePair<string, string>> parameters, string targetUrl)
{
var requestContent = new FormUrlEncodedContent(parameters);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, targetUrl);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = requestContent;
return requestMessage;
}
/// <summary>
/// Gets request parameters for authorization_code Token request
/// </summary>
/// <param name="code">Authorization code to be exchanged for token</param>
/// <returns>Content for HTTP request</returns>
internal List<KeyValuePair<string, string>> AuthCodeTokenRequestParameters(string code)
{
var parms = CommonTokenRequestParams();
parms.Add(new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsRedirectUri, _options.CallbackUrl));
parms.Add(new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsCode, code));
parms.Add(new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsGrantType, OpenIdConnectGrantTypes.AuthorizationCode));
return parms;
}
internal List<KeyValuePair<string, string>> ClientCredentialsTokenRequestParameters()
{
var parms = CommonTokenRequestParams();
parms.Add(new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsGrantType, OpenIdConnectGrantTypes.ClientCredentials));
return parms;
}
internal List<KeyValuePair<string, string>> CommonTokenRequestParams()
{
var scopes = "openid " + _options.AdditionalTokenScopes;
if (_options.RequiredScopes != null)
{
scopes = string.Join(" ", scopes, _options.RequiredScopes);
}
return new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsClientId, _options.ClientId),
new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsClientSecret, _options.ClientSecret),
new KeyValuePair<string, string>(CloudFoundryDefaults.ParamsScope, scopes)
};
}
internal ClaimsIdentity BuildIdentityWithClaims(IEnumerable<Claim> claims, string tokenScopes, string accessToken)
{
#if DEBUG
foreach (var claim in claims)
{
_logger?.LogTrace(claim.Type + " : " + claim.Value);
}
#endif
var claimsId = new ClaimsIdentity(_options.SignInAsAuthenticationType);
string userName = claims.First(c => c.Type == "user_name").Value;
string email = claims.First(c => c.Type == "email").Value;
string userId = claims.First(c => c.Type == "user_id").Value;
claimsId.AddClaims(new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
});
var additionalScopes = tokenScopes.Split(' ').Where(s => s != "openid");
foreach (var scope in additionalScopes)
{
claimsId.AddClaim(new Claim("scope", scope));
}
claimsId.AddClaim(new Claim(ClaimTypes.Authentication, accessToken));
return claimsId;
}
}
}

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

@ -13,9 +13,7 @@
// limitations under the License.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Steeltoe.CloudFoundry.Connector.Services;
using System.Net.Http;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
@ -30,31 +28,13 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
if (si != null)
{
options.JwtKeyUrl = si.AuthDomain + CloudFoundryDefaults.JwtTokenKey;
options.JwtKeyUrl = si.AuthDomain + CloudFoundryDefaults.JwtTokenUri;
}
jwtOptions.ClaimsIssuer = options.ClaimsIssuer;
jwtOptions.BackchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(options.ValidateCertificates);
jwtOptions.TokenValidationParameters = GetTokenValidationParameters(jwtOptions.TokenValidationParameters, options.JwtKeyUrl, jwtOptions.BackchannelHttpHandler, options.ValidateCertificates);
jwtOptions.TokenValidationParameters = CloudFoundryHelper.GetTokenValidationParameters(jwtOptions.TokenValidationParameters, options.JwtKeyUrl, jwtOptions.BackchannelHttpHandler, options.ValidateCertificates);
jwtOptions.SaveToken = options.SaveToken;
}
internal static TokenValidationParameters GetTokenValidationParameters(TokenValidationParameters parameters, string keyUrl, HttpMessageHandler handler, bool validateCertificates)
{
if (parameters == null)
{
parameters = new TokenValidationParameters();
}
parameters.ValidateAudience = false;
parameters.ValidateIssuer = true;
parameters.ValidateLifetime = true;
parameters.IssuerValidator = CloudFoundryTokenValidator.ValidateIssuer;
var tkr = new CloudFoundryTokenKeyResolver(keyUrl, handler, validateCertificates);
parameters.IssuerSigningKeyResolver = tkr.ResolveSigningKey;
return parameters;
}
}
}

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

@ -22,7 +22,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
{
string authURL = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
ClaimsIssuer = CloudFoundryDefaults.AuthenticationScheme;
JwtKeyUrl = authURL + CloudFoundryDefaults.JwtTokenKey;
JwtKeyUrl = authURL + CloudFoundryDefaults.JwtTokenUri;
SaveToken = true;
}

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

@ -34,7 +34,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
{
public class CloudFoundryOAuthHandler : OAuthHandler<CloudFoundryOAuthOptions>
{
private ILogger<CloudFoundryOAuthHandler> _logger;
private readonly ILogger<CloudFoundryOAuthHandler> _logger;
public CloudFoundryOAuthHandler(
IOptionsMonitor<CloudFoundryOAuthOptions> options,
@ -70,30 +70,6 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
return request;
}
protected internal virtual Dictionary<string, string> GetTokenRequestParameters(string code, string redirectUri)
{
return new Dictionary<string, string>()
{
{ "client_id", Options.ClientId },
{ "redirect_uri", redirectUri },
{ "client_secret", Options.ClientSecret },
{ "code", code },
{ "grant_type", "authorization_code" },
};
}
protected internal virtual HttpRequestMessage GetTokenRequestMessage(string code, string redirectUri)
{
var tokenRequestParameters = GetTokenRequestParameters(code, redirectUri);
var requestContent = new FormUrlEncodedContent(tokenRequestParameters);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Content = requestContent;
return requestMessage;
}
protected internal string GetEncoded(string user, string password)
{
if (user == null)
@ -116,25 +92,13 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
{
_logger?.LogDebug("ExchangeCodeAsync({code},{redirectUri})", code, redirectUri);
_logger?.LogDebug("ExchangeCodeAsync({code}, {redirectUri})", code, redirectUri);
HttpRequestMessage requestMessage = GetTokenRequestMessage(code, redirectUri);
HttpClient client = GetHttpClient();
var options = Options.BaseOptions();
options.CallbackUrl = redirectUri;
HttpClientHelper.ConfigureCertificateValidatation(
Options.ValidateCertificates,
out SecurityProtocolType prevProtocols,
out RemoteCertificateValidationCallback prevValidator);
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(requestMessage, Context.RequestAborted);
}
finally
{
HttpClientHelper.RestoreCertificateValidation(Options.ValidateCertificates, prevProtocols, prevValidator);
}
var tEx = new TokenExchanger(options, GetHttpClient());
HttpResponseMessage response = await tEx.ExchangeCodeForToken(code, Options.TokenEndpoint, Context.RequestAborted);
if (response.IsSuccessStatusCode)
{
@ -210,9 +174,9 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
var queryStrings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "response_type", "code" },
{ "client_id", Options.ClientId },
{ "redirect_uri", redirectUri }
{ CloudFoundryDefaults.ParamsResponseType, "code" },
{ CloudFoundryDefaults.ParamsClientId, Options.ClientId },
{ CloudFoundryDefaults.ParamsRedirectUri, redirectUri }
};
AddQueryString(queryStrings, properties, "scope", scope);

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

@ -36,7 +36,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
ClaimsIssuer = CloudFoundryDefaults.AuthenticationScheme;
ClientId = CloudFoundryDefaults.ClientId;
ClientSecret = CloudFoundryDefaults.ClientSecret;
CallbackPath = new PathString("/signin-cloudfoundry");
CallbackPath = new PathString(CloudFoundryDefaults.CallbackPath);
AuthorizationEndpoint = authURL + CloudFoundryDefaults.AuthorizationUri;
TokenEndpoint = authURL + CloudFoundryDefaults.AccessTokenUri;
UserInformationEndpoint = authURL + CloudFoundryDefaults.UserInfoUri;
@ -52,5 +52,16 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}
internal AuthServerOptions BaseOptions()
{
return new AuthServerOptions
{
ClientId = ClientId,
ClientSecret = ClientSecret,
ValidateCertificates = ValidateCertificates,
AuthorizationUrl = AuthorizationEndpoint
};
}
}
}

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

@ -23,14 +23,13 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.OAuth" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Steeltoe.CloudFoundry.ConnectorBase" Version="$(SteeltoeConnectorVersion)" />
<PackageReference Include="Steeltoe.Common.Http" Version="$(SteeltoeCommonVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
<RootNamespace>Steeltoe.Security.Authentication.CloudFoundry</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
@ -38,4 +37,10 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == ''">
<ProjectReference Include="..\Steeltoe.Security.Authentication.CloudFoundryBase\Steeltoe.Security.Authentication.CloudFoundryBase.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == 'True'">
<PackageReference Include="Steeltoe.Security.Authentication.CloudFoundryBase" Version="$(SteeltoeVersion)$(SteeltoeVersionSuffix)" />
</ItemGroup>
</Project>

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

@ -0,0 +1,113 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Owin;
using Steeltoe.CloudFoundry.Connector;
using Steeltoe.CloudFoundry.Connector.Services;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public static class CloudFoundryExtensions
{
/// <summary>
/// Configures and adds <see cref="OpenIdConnectAuthenticationMiddleware" /> to the OWIN request pipeline />
/// </summary>
/// <param name="appBuilder">Your OWIN AppBuilder</param>
/// <param name="configuration">Your application configuration</param>
/// <param name="authenticationType">The identifier for this authentication type. MUST match the value used in your authentication challenge.</param>
/// <param name="loggerFactory">For logging with SSO interactions</param>
/// <returns>Your <see cref="IAppBuilder"/></returns>
public static IAppBuilder UseCloudFoundryOpenIdConnect(this IAppBuilder appBuilder, IConfiguration configuration, string authenticationType = CloudFoundryDefaults.AuthenticationScheme, LoggerFactory loggerFactory = null)
{
if (appBuilder == null)
{
throw new ArgumentNullException(nameof(appBuilder));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
// get options with defaults
var cloudFoundryOptions = new OpenIdConnectOptions(authenticationType, loggerFactory);
// get and apply config from application
var securitySection = configuration.GetSection(CloudFoundryDefaults.SECURITY_CLIENT_SECTION_PREFIX);
securitySection.Bind(cloudFoundryOptions);
// get and apply service binding info
SsoServiceInfo info = configuration.GetSingletonServiceInfo<SsoServiceInfo>();
OpenIdConnectConfigurer.Configure(info, cloudFoundryOptions);
return appBuilder.Use(typeof(OpenIdConnectAuthenticationMiddleware), appBuilder, cloudFoundryOptions);
}
}
}
#pragma warning disable SA1403 // File may only contain a single namespace
namespace Steeltoe.Security.Authentication.CloudFoundry
#pragma warning restore SA1403 // File may only contain a single namespace
{
#pragma warning disable SA1402 // File may only contain a single class
public static class CloudFoundryExtensions
#pragma warning restore SA1402 // File may only contain a single class
{
/// <summary>
/// Configures and adds JWT bearer token middleware to the OWIN request pipeline
/// </summary>
/// <param name="appBuilder">Your OWIN AppBuilder</param>
/// <param name="configuration">Your application configuration</param>
/// <param name="logger">Include for diagnostic logging during app start</param>
/// <returns>Your <see cref="IAppBuilder"/></returns>
public static IAppBuilder UseCloudFoundryJwtBearerAuthentication(this IAppBuilder appBuilder, IConfiguration configuration, ILogger logger = null)
{
if (appBuilder == null)
{
throw new ArgumentNullException(nameof(appBuilder));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
// get options with defaults
var cloudFoundryOptions = new CloudFoundryJwtBearerAuthenticationOptions();
// get and apply config from application
var securitySection = configuration.GetSection(CloudFoundryDefaults.SECURITY_CLIENT_SECTION_PREFIX);
securitySection.Bind(cloudFoundryOptions);
// get and apply service binding info
SsoServiceInfo si = configuration.GetSingletonServiceInfo<SsoServiceInfo>();
CloudFoundryJwtOwinConfigurer.Configure(si, cloudFoundryOptions);
// REVIEW: return without adding auth middleware if no service binding was found... !?
// - presumably written this way to support local development, but seems like a bad idea
// - added option to disable, but leaving behavior to default this way, for now, to avoid a breaking change
if (si == null && cloudFoundryOptions.SkipAuthIfNoBoundSSOService)
{
logger?.LogWarning("SSO Service binding not detected, JWT Bearer middleware has not been added!");
logger?.LogInformation("To include JWT Bearer middleware when bindings aren't found, set security:oauth2:client:SkipAuthIfNoBoundSSOService=false");
return appBuilder;
}
return appBuilder.UseJwtBearerAuthentication(cloudFoundryOptions);
}
}
}

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

@ -1,66 +0,0 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
using Owin;
using Steeltoe.CloudFoundry.Connector;
using Steeltoe.CloudFoundry.Connector.Services;
using System.Net.Http;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public static class CloudFoundryJwtBearerExtensions
{
public static IAppBuilder UseCloudFoundryJwtBearerAuthentication(this IAppBuilder app, IConfiguration config)
{
var cloudFoundryOptions = new CloudFoundryJwtBearerAuthenticationOptions();
var securitySection = config.GetSection(CloudFoundryDefaults.SECURITY_CLIENT_SECTION_PREFIX);
securitySection.Bind(cloudFoundryOptions);
SsoServiceInfo si = config.GetSingletonServiceInfo<SsoServiceInfo>();
if (si == null)
{
return app;
}
var jwtTokenUrl = si.AuthDomain + CloudFoundryDefaults.JwtTokenKey;
var httpMessageHandler = CloudFoundryHelper.GetBackChannelHandler(cloudFoundryOptions.ValidateCertificates);
var tokenValidationParameters = GetTokenValidationParameters(jwtTokenUrl, httpMessageHandler, cloudFoundryOptions.ValidateCertificates);
return app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
TokenValidationParameters = tokenValidationParameters,
});
}
internal static TokenValidationParameters GetTokenValidationParameters(string keyUrl, HttpMessageHandler handler, bool validateCertificates)
{
var parameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = true,
IssuerValidator = CloudFoundryTokenValidator.ValidateIssuer
};
var tkr = new CloudFoundryTokenKeyResolver(keyUrl, handler, validateCertificates);
parameters.IssuerSigningKeyResolver = tkr.ResolveSigningKey;
return parameters;
}
}
}

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

@ -1,60 +0,0 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
using Steeltoe.CloudFoundry.Connector.Services;
using System.Net.Http;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public static class CloudFoundryJwtOwinConfigurer
{
internal static void Configure(SsoServiceInfo si, JwtBearerAuthenticationOptions jwtOptions, CloudFoundryJwtBearerAuthenticationOptions options)
{
if (jwtOptions == null || options == null)
{
return;
}
if (si != null)
{
options.JwtKeyUrl = si.AuthDomain + CloudFoundryDefaults.JwtTokenKey;
}
// jwtOptions.ClaimsIssuer = options.ClaimsIssuer;
// jwtOptions.BackchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(options.ValidateCertificates);
// jwtOptions.TokenValidationParameters = GetTokenValidationParameters(jwtOptions.TokenValidationParameters, options.JwtKeyUrl, jwtOptions.BackchannelHttpHandler, options.ValidateCertificates);
// jwtOptions.SaveToken = options.SaveToken;
}
internal static TokenValidationParameters GetTokenValidationParameters(TokenValidationParameters parameters, string keyUrl, HttpMessageHandler handler, bool validateCertificates)
{
if (parameters == null)
{
parameters = new TokenValidationParameters();
}
parameters.ValidateAudience = false;
parameters.ValidateIssuer = true;
parameters.ValidateLifetime = true;
parameters.IssuerValidator = CloudFoundryTokenValidator.ValidateIssuer;
var tkr = new CloudFoundryTokenKeyResolver(keyUrl, handler, validateCertificates);
parameters.IssuerSigningKeyResolver = tkr.ResolveSigningKey;
return parameters;
}
}
}

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

@ -12,23 +12,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public static class Constants
{
public const string DefaultAuthenticationType = "PivotalSSO";
public const string EndPointOAuthAuthorize = "oauth/authorize";
public const string EndPointOAuthToken = "oauth/token";
public const string ParamsClientID = "client_id";
public const string ParamsClientSecret = "client_secret";
public const string ParamsResponseType = "response_type";
public const string ParamsScope = "scope";
public const string ParamsRedirectUri = "redirect_uri";
public const string ParamsGrantType = "grant_type";
public const string ParamsTokenFormat = "token_format";
public const string ParamsCode = "code";
[Obsolete("Use CloudFoundryDefaults.AuthorizationUri instead")]
public const string EndPointOAuthAuthorize = CloudFoundryDefaults.AuthorizationUri;
[Obsolete("Use CloudFoundryDefaults.AccessTokenUri instead")]
public const string EndPointOAuthToken = CloudFoundryDefaults.AccessTokenUri;
[Obsolete("Use CloudFoundryDefaults.ParamsClientID instead")]
public const string ParamsClientID = CloudFoundryDefaults.ParamsClientId;
[Obsolete("Use CloudFoundryDefaults.ParamsClientSecret instead")]
public const string ParamsClientSecret = CloudFoundryDefaults.ParamsClientSecret;
[Obsolete("Use CloudFoundryDefaults.ParamsResponseType instead")]
public const string ParamsResponseType = CloudFoundryDefaults.ParamsResponseType;
[Obsolete("Use CloudFoundryDefaults.ParamsScope instead")]
public const string ParamsScope = CloudFoundryDefaults.ParamsScope;
[Obsolete("Use CloudFoundryDefaults.ParamsRedirectUri instead")]
public const string ParamsRedirectUri = CloudFoundryDefaults.ParamsRedirectUri;
[Obsolete("Use CloudFoundryDefaults.ParamsGrantType instead")]
public const string ParamsGrantType = CloudFoundryDefaults.ParamsGrantType;
[Obsolete("Use CloudFoundryDefaults.ParamsTokenFormat instead")]
public const string ParamsTokenFormat = CloudFoundryDefaults.ParamsTokenFormat;
[Obsolete("Use CloudFoundryDefaults.ParamsCode instead")]
public const string ParamsCode = CloudFoundryDefaults.ParamsCode;
public const string ScopeOpenID = "openid";
public const string GrantTypeAuthorizationCode = "authorization_code";

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

@ -21,9 +21,15 @@ namespace Steeltoe.Security.Authentication.CloudFoundry
public CloudFoundryJwtBearerAuthenticationOptions()
{
string authURL = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
JwtKeyUrl = authURL + CloudFoundryDefaults.JwtTokenKey;
JwtKeyUrl = authURL + CloudFoundryDefaults.JwtTokenUri;
}
/// <summary>
/// Gets or sets a value indicating whether auth middleware is added if no service binding is found
/// </summary>
/// <remarks>Is set to 'true' for compatibility with releases prior to Steeltoe 2.2.</remarks>
public bool SkipAuthIfNoBoundSSOService { get; set; } = true;
public string JwtKeyUrl { get; set; }
public bool ValidateCertificates { get; set; } = true;

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

@ -0,0 +1,42 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Steeltoe.CloudFoundry.Connector.Services;
namespace Steeltoe.Security.Authentication.CloudFoundry
{
public static class CloudFoundryJwtOwinConfigurer
{
/// <summary>
/// Apply service binding info to JWT options
/// </summary>
/// <param name="si">Info for bound SSO Service</param>
/// <param name="options">Options to be updated</param>
internal static void Configure(SsoServiceInfo si, CloudFoundryJwtBearerAuthenticationOptions options)
{
if (options == null)
{
return;
}
if (si != null)
{
options.JwtKeyUrl = si.AuthDomain + CloudFoundryDefaults.JwtTokenUri;
}
var backchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(options.ValidateCertificates);
options.TokenValidationParameters = CloudFoundryHelper.GetTokenValidationParameters(options.TokenValidationParameters, options.JwtKeyUrl, backchannelHttpHandler, options.ValidateCertificates);
}
}
}

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

@ -1,58 +0,0 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Owin;
using Microsoft.Owin.Security;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public class OpenIDConnectOptions : AuthenticationOptions
{
public OpenIDConnectOptions()
: base(Constants.DefaultAuthenticationType)
{
Description.Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-oidc");
AuthenticationMode = AuthenticationMode.Passive;
}
public PathString CallbackPath { get; set; }
public string AuthDomain { get; set; }
public string ClientID { get; set; }
public string ClientSecret { get; set; }
public string AppHost { get; set; }
public int AppPort { get; set; }
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets additional scopes beyond 'openid' when requesting tokens
/// </summary>
public string AdditionalScopes { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to validate SSO server certificate
/// </summary>
public bool ValidateCertificates { get; set; } = true;
public string TokenInfoUrl => AuthDomain + "/check_token";
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
}
}

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

@ -13,12 +13,14 @@
// limitations under the License.
using Owin;
using System;
using System.Net;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public static class OpenIDConnectExtension
{
[Obsolete("Use app.UseCloudFoundryOpenIdConnect instead")]
public static IAppBuilder UseOpenIDConnect(this IAppBuilder app, OpenIDConnectOptions options)
{
// In order to talk to Pivotal SSO tile, we must limit the TLS defaults used by the service

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

@ -12,20 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public static class CloudFoundryTokenValidator
[Obsolete("This class will be removed in a future release, use Steeltoe.Security.Authentication.CloudFoundry.OpenIdTokenResponse instead.")]
public class OpenIDTokenResponse : OpenIdTokenResponse
{
public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
if (issuer.Contains("uaa"))
{
return issuer;
}
return null;
}
}
}

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

@ -12,21 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Logging;
using Microsoft.Owin;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System.Diagnostics;
using System;
using System.Threading.Tasks;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public class OpenIDConnectAuthenticationHandler : AuthenticationHandler<OpenIDConnectOptions>
public class OpenIdConnectAuthenticationHandler : AuthenticationHandler<OpenIdConnectOptions>
{
/*
* Invoked for every request. As this is passive middleware, we only want to branch off
* the authentication flow if the request being invoked is the callback path we gave as a redirect
* uri to the auth flow when invoking the IDP
*/
private readonly ILogger _logger;
public OpenIdConnectAuthenticationHandler(ILogger logger = null)
{
_logger = logger;
}
/// <summary>
/// Invoked for every request.As this is passive middleware, we only want to branch off
/// the authentication flow if the request being invoked is the callback path we gave as a redirect
/// uri to the auth flow when invoking the IDP
/// </summary>
/// <returns>A bool used to determine whether or not to continue down the OWIN pipline</returns>
public override async Task<bool> InvokeAsync()
{
if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
@ -48,19 +58,28 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var code = Request.Query["code"];
Debug.WriteLine("Received an authorization code from IDP: " + code);
Debug.WriteLine("== exchanging for token ==");
if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
{
return null;
}
// ASP.Net Identity requires the NameIdentitifer field to be set or it won't
// accept the external login (AuthenticationManagerExtensions.GetExternalLoginInfo)
var identity = await TokenExchanger.ExchangeCodeForToken(code, Options);
var code = Request.Query["code"];
if (code == null)
{
var error = Request.Query["error"];
var error_description = Request.Query["error_description"];
_logger?.LogError("No auth code detected in SSO response. Error: {error}, Description: {error_description}", error, error_description);
}
_logger?.LogDebug("Received an authorization code from IDP: " + code);
_logger?.LogInformation("== exchanging auth code for token ==");
var exchanger = new TokenExchanger(Options.AsAuthServerOptions(HostInfoFromRequest(Request) + Options.CallbackPath), null, _logger);
var identity = await exchanger.ExchangeAuthCodeForClaimsIdentity(code);
var properties = Options.StateDataFormat.Unprotect(Request.Query["state"]);
// return Task.FromResult(new AuthenticationTicket(identity, properties));
var ticket = new AuthenticationTicket(identity, properties);
return ticket;
return new AuthenticationTicket(identity, properties);
}
protected override Task InitializeCoreAsync()
@ -85,15 +104,27 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
state.RedirectUri = Request.Uri.ToString();
}
var stateString = Options.StateDataFormat.Protect(state);
Debug.WriteLine("Challenge redirecting to IDP...");
var idpRedirectUri = UriUtility.CalculateFullRedirectUri(Options, Request);
_logger?.LogInformation("Redirecting to Identity Provider at {idpRedirectUri}", idpRedirectUri);
var idpRedirectUri = UriUtility.CalculateFullRedirectUri(Options);
var stateString = Options.StateDataFormat.Protect(state);
Response.Redirect(WebUtilities.AddQueryString(idpRedirectUri, "state", stateString));
}
}
return Task.FromResult<object>(null);
}
private string HostInfoFromRequest(IOwinRequest request)
{
return request.Scheme + Uri.SchemeDelimiter + request.Host;
}
}
#pragma warning disable SA1402 // File may only contain a single class
[Obsolete("This class has been renamed OpenIdConnectAuthenticationHandler")]
public class OpenIDConnectAuthenticationHandler : OpenIdConnectAuthenticationHandler
#pragma warning restore SA1402 // File may only contain a single class
{
}
}

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

@ -18,12 +18,13 @@ using Microsoft.Owin.Security.DataHandler;
using Microsoft.Owin.Security.DataProtection;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public class OpenIDConnectAuthenticationMiddleware : AuthenticationMiddleware<OpenIDConnectOptions>
public class OpenIdConnectAuthenticationMiddleware : AuthenticationMiddleware<OpenIdConnectOptions>
{
public OpenIDConnectAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIDConnectOptions options)
public OpenIdConnectAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectOptions options)
: base(next, options)
{
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
@ -34,16 +35,27 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
if (options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(
typeof(OpenIDConnectAuthenticationMiddleware).FullName,
typeof(OpenIdConnectAuthenticationMiddleware).FullName,
options.AuthenticationType);
options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
}
protected override AuthenticationHandler<OpenIDConnectOptions> CreateHandler()
protected override AuthenticationHandler<OpenIdConnectOptions> CreateHandler()
{
return new OpenIdConnectAuthenticationHandler(Options.LoggerFactory?.CreateLogger("OpenIdConnectAuthenticationHandler"));
}
}
#pragma warning disable SA1402 // File may only contain a single class
[Obsolete("This class has been renamed OpenIdConnectAuthenticationHandler")]
public class OpenIDConnectAuthenticationMiddleware : OpenIdConnectAuthenticationMiddleware
#pragma warning restore SA1402 // File may only contain a single class
{
public OpenIDConnectAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, OpenIDConnectOptions options)
: base(next, app, options)
{
return new OpenIDConnectAuthenticationHandler();
}
}
}

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

@ -0,0 +1,44 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Steeltoe.CloudFoundry.Connector.Services;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public static class OpenIdConnectConfigurer
{
/// <summary>
/// Apply service binding info to an <see cref="OpenIdConnectOptions"/> instance
/// </summary>
/// <param name="si">Service binding information</param>
/// <param name="options">OpenID options to be updated</param>
internal static void Configure(SsoServiceInfo si, OpenIdConnectOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (si == null)
{
return;
}
options.AuthDomain = si.AuthDomain;
options.ClientId = si.ClientId;
options.ClientSecret = si.ClientSecret;
}
}
}

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

@ -0,0 +1,95 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Logging;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
public class OpenIdConnectOptions : AuthenticationOptions
{
public OpenIdConnectOptions(string authenticationType = CloudFoundryDefaults.DisplayName, LoggerFactory loggerFactory = null)
: base(authenticationType)
{
Description.Caption = authenticationType;
AuthenticationMode = AuthenticationMode.Passive;
LoggerFactory = loggerFactory;
}
public PathString CallbackPath { get; set; } = new PathString(CloudFoundryDefaults.CallbackPath);
public string AuthDomain { get; set; } = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
public string ClientId { get; set; } = CloudFoundryDefaults.ClientId;
[Obsolete("This property will be removed in a future release, use ClientId instead")]
public string ClientID
{
get { return ClientId; }
set { ClientId = value; }
}
public string ClientSecret { get; set; } = CloudFoundryDefaults.ClientSecret;
public string AppHost { get; set; }
public int AppPort { get; set; }
public string SignInAsAuthenticationType { get; set; }
/// <summary>
/// Gets or sets additional scopes beyond 'openid' when requesting tokens
/// </summary>
public string AdditionalScopes { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether to validate SSO server certificate
/// </summary>
public bool ValidateCertificates { get; set; } = true;
public string TokenInfoUrl => AuthDomain + CloudFoundryDefaults.CheckTokenUri;
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
public AuthServerOptions AsAuthServerOptions(string callbackUrl = null)
{
return new AuthServerOptions
{
ClientId = ClientId,
ClientSecret = ClientSecret,
AdditionalTokenScopes = AdditionalScopes,
ValidateCertificates = ValidateCertificates,
SignInAsAuthenticationType = SignInAsAuthenticationType,
AuthorizationUrl = AuthDomain + CloudFoundryDefaults.AccessTokenUri,
CallbackUrl = callbackUrl ?? "https://" + AppHost + (AppPort != 0 ? ":" + AppPort : string.Empty) + CallbackPath
};
}
internal ILoggerFactory LoggerFactory;
}
[Obsolete("This class is being renamed OpenIdConnectOptions")]
#pragma warning disable SA1402 // File may only contain a single class
public class OpenIDConnectOptions : OpenIdConnectOptions
#pragma warning restore SA1402 // File may only contain a single class
{
public OpenIDConnectOptions(string authenticationType = "PivotalSSO")
: base(authenticationType)
{
CallbackPath = new PathString("/signin-oidc");
}
}
}

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

@ -0,0 +1,17 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundryOwin.Test")]

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

@ -28,15 +28,10 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == ''">
<ProjectReference Include="..\Steeltoe.Security.Authentication.CloudFoundryCore\Steeltoe.Security.Authentication.CloudFoundryCore.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == 'True'">
<PackageReference Include="Steeltoe.Security.Authentication.CloudFoundryCore" Version="$(SteeltoeVersion)$(SteeltoeVersionSuffix)" />
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
<RootNamespace>Steeltoe.Security.Authentication.CloudFoundry.Owin</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
@ -44,5 +39,10 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == ''">
<ProjectReference Include="..\Steeltoe.Security.Authentication.CloudFoundryBase\Steeltoe.Security.Authentication.CloudFoundryBase.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == 'True'">
<PackageReference Include="Steeltoe.Security.Authentication.CloudFoundryBase" Version="$(SteeltoeVersion)$(SteeltoeVersionSuffix)" />
</ItemGroup>
</Project>

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

@ -1,119 +0,0 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Steeltoe.Common.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
internal class TokenExchanger
{
internal static async Task<ClaimsIdentity> ExchangeCodeForToken(string code, OpenIDConnectOptions options)
{
string redirect_url = "https://" + options.AppHost;
if (options.AppPort != 0)
{
redirect_url = redirect_url + ":" + options.AppPort + options.CallbackPath;
}
else
{
redirect_url = redirect_url + options.CallbackPath;
}
var hostName = options.AuthDomain;
var pairs = new[]
{
new KeyValuePair<string, string>(Constants.ParamsClientID, options.ClientID),
new KeyValuePair<string, string>(Constants.ParamsClientSecret, options.ClientSecret),
new KeyValuePair<string, string>(Constants.ParamsGrantType, Constants.GrantTypeAuthorizationCode),
new KeyValuePair<string, string>(Constants.ParamsRedirectUri, redirect_url),
new KeyValuePair<string, string>(Constants.ParamsCode, code)
};
var content = new FormUrlEncodedContent(pairs);
var targetUrl = hostName + "/" + Constants.EndPointOAuthToken;
Debug.WriteLine("About to submit request for token to : " + targetUrl);
foreach (var item in pairs)
{
Debug.WriteLine(item.Key + ": " + item.Value);
}
HttpClientHelper.ConfigureCertificateValidatation(options.ValidateCertificates, out SecurityProtocolType protocolType, out RemoteCertificateValidationCallback prevValidator);
using (var client = new HttpClient())
{
var byteArray = Encoding.ASCII.GetBytes(options.ClientID + ":" + options.ClientSecret);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
try
{
var response = await client.PostAsync(targetUrl, content);
if (response.IsSuccessStatusCode)
{
var tokens = await response.Content.ReadAsJsonAsync<OpenIDTokenResponse>();
Debug.WriteLine("Identity token from IDP: " + tokens.IdentityToken);
Debug.WriteLine("Access token from IDP: " + tokens.AccessToken);
JwtSecurityToken securityToken = new JwtSecurityToken(tokens.IdentityToken);
var claimsId = new ClaimsIdentity(options.SignInAsAuthenticationType);
string userName = securityToken.Claims.First(c => c.Type == "user_name").Value;
string email = securityToken.Claims.First(c => c.Type == "email").Value;
string userId = securityToken.Claims.First(c => c.Type == "user_id").Value;
foreach (var claim in securityToken.Claims)
{
Debug.WriteLine(claim.Type + " : " + claim.Value);
}
claimsId.AddClaims(new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
});
var additionalScopes = tokens.Scope.Split(' ').Where(s => s != "openid");
foreach (var scope in additionalScopes)
{
claimsId.AddClaim(new Claim("scope", scope));
}
claimsId.AddClaim(new Claim(ClaimTypes.Authentication, tokens.AccessToken));
return claimsId;
}
else
{
Debug.WriteLine("Failed call to exchange code for token : " + response.StatusCode);
Debug.WriteLine(response.ReasonPhrase);
string resultJson = await response.Content.ReadAsStringAsync();
Debug.WriteLine(resultJson);
return null;
}
}
finally
{
HttpClientHelper.RestoreCertificateValidation(options.ValidateCertificates, protocolType, prevValidator);
}
}
}
}
}

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

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Owin;
using Microsoft.Owin.Infrastructure;
using System;
@ -19,28 +20,30 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Owin
{
internal static class UriUtility
{
internal static string CalculateFullRedirectUri(OpenIDConnectOptions options)
/// <summary>
/// Determine full redirect uri to send user to auth server, include a valid return path
/// </summary>
/// <param name="options">Auth configuration</param>
/// <param name="request">HTTP Request information, for generating a valid return paht</param>
/// <returns>A URL with enough info for the auth server to identify the app and return the user to the right location after auth</returns>
internal static string CalculateFullRedirectUri(OpenIdConnectOptions options, IOwinRequest request)
{
var uri = options.AuthDomain + "/" + Constants.EndPointOAuthAuthorize;
var uri = options.AuthDomain + CloudFoundryDefaults.AuthorizationUri;
var queryString = WebUtilities.AddQueryString(uri, Constants.ParamsClientID, options.ClientID);
queryString = WebUtilities.AddQueryString(queryString, Constants.ParamsResponseType, "code");
queryString = WebUtilities.AddQueryString(queryString, Constants.ParamsScope, $"{Constants.ScopeOpenID} {options.AdditionalScopes}");
queryString = WebUtilities.AddQueryString(queryString, Constants.ParamsRedirectUri, DetermineRedirectUri(options));
var queryString = WebUtilities.AddQueryString(uri, CloudFoundryDefaults.ParamsClientId, options.ClientId);
queryString = WebUtilities.AddQueryString(queryString, CloudFoundryDefaults.ParamsResponseType, "code");
queryString = WebUtilities.AddQueryString(queryString, CloudFoundryDefaults.ParamsScope, $"{Constants.ScopeOpenID} {options.AdditionalScopes}");
queryString = WebUtilities.AddQueryString(queryString, CloudFoundryDefaults.ParamsRedirectUri, DetermineRedirectUri(options, request));
return queryString;
}
private static string DetermineRedirectUri(OpenIDConnectOptions options)
private static string DetermineRedirectUri(OpenIdConnectOptions options, IOwinRequest request)
{
// TODO: determine the right host name and port.
var uri = "https://" + options.AppHost;
if (options.AppPort > 0 && options.AppPort != 443)
{
uri = uri + ":" + options.AppPort.ToString();
}
return uri + options.CallbackPath;
return request.Scheme +
Uri.SchemeDelimiter +
request.Host +
options.CallbackPath;
}
}
}

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

@ -12,55 +12,50 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Steeltoe.Common.Http;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
/// <summary>
/// Get JWTs for WCF Clients
/// </summary>
public class CloudFoundryClientTokenResolver
{
public CloudFoundryOptions Options { get; internal protected set; }
public CloudFoundryClientTokenResolver(CloudFoundryOptions options)
private readonly ILogger<CloudFoundryClientTokenResolver> _logger;
private readonly TokenExchanger _tokenExchanger;
/// <summary>
/// Initializes a new instance of the <see cref="CloudFoundryClientTokenResolver"/> class.
/// This class can be used to get access tokens from an OAuth server
/// </summary>
/// <param name="options">Con</param>
/// <param name="httpClient">For interacting with the OAuth server. A new instance will be created if not provided.</param>
public CloudFoundryClientTokenResolver(CloudFoundryOptions options, HttpClient httpClient = null)
{
Options = options ?? throw new ArgumentNullException("Options are required");
Options = options ?? throw new ArgumentNullException(nameof(options), "Options are required");
_tokenExchanger = new TokenExchanger(options, httpClient, options.LoggerFactory?.CreateLogger<TokenExchanger>());
_logger = Options.LoggerFactory?.CreateLogger<CloudFoundryClientTokenResolver>();
}
/// <summary>
/// Get an access token using the client_credentials grant and the application's oauth credentials
/// </summary>
/// <returns>An access token</returns>
public virtual async Task<string> GetAccessToken()
{
HttpRequestMessage requestMessage = GetTokenRequestMessage();
HttpClientHelper.ConfigureCertificateValidatation(Options.ValidateCertificates, out SecurityProtocolType protocolType, out RemoteCertificateValidationCallback prevValidator);
HttpClient client = new HttpClient
{
BaseAddress = new Uri(Options.OAuthServiceUrl)
};
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(requestMessage);
}
catch (Exception ex)
{
throw new Exception("Error getting access token:" + ex.Message);
}
finally
{
HttpClientHelper.RestoreCertificateValidation(Options.ValidateCertificates, protocolType, prevValidator);
}
HttpResponseMessage response = await _tokenExchanger.GetAccessTokenWithClientCredentials(Options.AuthorizationUrl + Options.AccessTokenEndpoint);
if (response.IsSuccessStatusCode)
{
_logger?.LogTrace("Successfully retrieved access token");
var resp = await response.Content.ReadAsStringAsync();
var payload = JObject.Parse(resp);
@ -68,54 +63,13 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
}
else
{
_logger?.LogError("Failed to retrieve access token with HTTP Status: {HttpStatus}", response.StatusCode);
_logger?.LogWarning("Access token retrieval failure response: {Message}", response.Content.ReadAsStringAsync());
var error = "OAuth token endpoint failure: " + await Display(response);
throw new Exception(error);
}
}
protected internal virtual HttpRequestMessage GetTokenRequestMessage()
{
var tokenRequestParameters = GetTokenRequestParameters();
var requestContent = new FormUrlEncodedContent(tokenRequestParameters);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.AccessTokenEndpoint);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", GetEncoded(Options.ClientId, Options.ClientSecret));
requestMessage.Content = requestContent;
return requestMessage;
}
protected internal virtual Dictionary<string, string> GetTokenRequestParameters()
{
return new Dictionary<string, string>()
{
{ "client_id", Options.ClientId },
{ "client_secret", Options.ClientSecret },
{ "response_type", "token" },
{ "grant_type", "client_credentials" },
{
"scope", Options.RequiredScopes == null ? "openid" :
string.Join(" ", Options.RequiredScopes)
},
};
}
protected internal string GetEncoded(string user, string password)
{
if (user == null)
{
user = string.Empty;
}
if (password == null)
{
password = string.Empty;
}
return Convert.ToBase64String(Encoding.ASCII.GetBytes(user + ":" + password));
}
private static async Task<string> Display(HttpResponseMessage response)
{
var output = new StringBuilder();

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

@ -0,0 +1,64 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Steeltoe.CloudFoundry.Connector;
using Steeltoe.CloudFoundry.Connector.Services;
using System;
using System.Net.Http;
using System.ServiceModel;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
public static class CloudFoundryExtensions
{
/// <summary>
/// Adds the <see cref="JwtAuthorizationManager"/> to a <see cref="ServiceHost"/>
/// </summary>
/// <param name="serviceHost">Your service to be secured with JWT Auth</param>
/// <param name="configuration">Your application configuration, including VCAP_SERVICES</param>
/// <param name="httpClient">Provide your own http client for interacting with the security server</param>
/// <param name="loggerFactory">For logging within the library</param>
/// <returns>Your service</returns>
public static ServiceHost AddJwtAuthorization(this ServiceHost serviceHost, IConfiguration configuration, HttpClient httpClient = null, LoggerFactory loggerFactory = null)
{
if (serviceHost == null)
{
throw new ArgumentNullException(nameof(serviceHost));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
// get options with defaults
var cloudFoundryOptions = new CloudFoundryOptions(loggerFactory);
// get and apply config from application
var securitySection = configuration.GetSection(CloudFoundryDefaults.SECURITY_CLIENT_SECTION_PREFIX);
securitySection.Bind(cloudFoundryOptions);
// get and apply service binding info
SsoServiceInfo info = configuration.GetSingletonServiceInfo<SsoServiceInfo>();
CloudFoundryOptionsConfigurer.Configure(info, cloudFoundryOptions);
var authManager = new JwtAuthorizationManager(cloudFoundryOptions);
serviceHost.Authorization.ServiceAuthorizationManager = authManager;
return serviceHost;
}
}
}

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

@ -12,10 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
@ -25,70 +22,38 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
public static void OnTokenValidatedAddClaims(ClaimsIdentity identity, JwtSecurityToken jwt)
{
var identifier = GetId(identity);
if (!string.IsNullOrEmpty(identifier))
{
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, jwt.Issuer));
}
AddClaimIfNotNullOrEmpty(identity, "user_id", ClaimTypes.NameIdentifier, jwt.Issuer);
AddClaimIfNotNullOrEmpty(identity, "given_name", ClaimTypes.GivenName, jwt.Issuer);
AddClaimIfNotNullOrEmpty(identity, "family_name", ClaimTypes.Surname, jwt.Issuer);
AddClaimIfNotNullOrEmpty(identity, "email", ClaimTypes.Email, jwt.Issuer);
var givenName = GetGivenName(identity);
if (!string.IsNullOrEmpty(givenName))
{
identity.AddClaim(new Claim(ClaimTypes.GivenName, givenName, ClaimValueTypes.String, jwt.Issuer));
}
var familyName = GetFamilyName(identity);
if (!string.IsNullOrEmpty(familyName))
{
identity.AddClaim(new Claim(ClaimTypes.Surname, familyName, ClaimValueTypes.String, jwt.Issuer));
}
var email = GetEmail(identity);
if (!string.IsNullOrEmpty(email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, jwt.Issuer));
}
var name = GetName(identity);
if (!string.IsNullOrEmpty(name))
{
identity.AddClaim(new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, jwt.Issuer));
}
else
if (!AddClaimIfNotNullOrEmpty(identity, "user_name", ClaimTypes.Name, jwt.Issuer))
{
identity.AddClaim(new Claim(ClaimTypes.Name, GetClientId(identity), ClaimValueTypes.String, jwt.Issuer));
}
}
private static string GetGivenName(IIdentity identity)
/// <summary>
/// Add a claim to the identity from another location, if found
/// </summary>
/// <param name="identity">The identity to investicate and modify</param>
/// <param name="claimLocator">The claim we're trying to get</param>
/// <param name="claimType">The claim we're trying to set</param>
/// <param name="jwtIssuer">Issuer of the JWT</param>
/// <returns>True if the claim was not null or empty (and was added)</returns>
private static bool AddClaimIfNotNullOrEmpty(ClaimsIdentity identity, string claimLocator, string claimType, string jwtIssuer)
{
return GetClaim(identity, "given_name");
var claimValue = GetClaim(identity, claimLocator);
if (!string.IsNullOrEmpty(claimValue))
{
identity.AddClaim(new Claim(claimType, claimValue, ClaimValueTypes.String, jwtIssuer));
return true;
}
return false;
}
private static string GetFamilyName(IIdentity identity)
{
return GetClaim(identity, "family_name");
}
private static string GetEmail(IIdentity identity)
{
return GetClaim(identity, "email");
}
private static string GetName(IIdentity identity)
{
return GetClaim(identity, "user_name");
}
private static string GetId(IIdentity identity)
{
return GetClaim(identity, "user_id");
}
private static string GetClientId(IIdentity identity)
{
return GetClaim(identity, "client_id");
}
private static string GetClientId(IIdentity identity) => GetClaim(identity, "client_id");
private static string GetClaim(IIdentity identity, string claim)
{
@ -106,35 +71,5 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
return idClaim.Value;
}
private static Claim[] GetClaims(IIdentity identity, string claim)
{
var claims = identity as ClaimsIdentity;
if (claims == null)
{
return null;
}
var idClaims = claims.FindAll(claim);
if (idClaims == null)
{
return null;
}
return idClaims.ToArray<Claim>();
}
private string GetClaimWithFallback(IEnumerable<Claim> claims, params string[] claimTypes)
{
foreach (var claimType in claimTypes)
{
if (claims.Count(c => c.Type == claimType) > 0)
{
return claims.SingleOrDefault(c => c.Type == claimType).Value;
}
}
return null;
}
}
}

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

@ -13,47 +13,47 @@
// limitations under the License.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Steeltoe.CloudFoundry.Connector;
using Steeltoe.CloudFoundry.Connector.Services;
using System;
using System.Diagnostics;
using System.Net.Http;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
public class CloudFoundryOptions
public class CloudFoundryOptions : AuthServerOptions
{
public const string AUTHENTICATION_SCHEME = "CloudFoundry";
public const string AUTHENTICATION_SCHEME = CloudFoundryDefaults.AuthenticationScheme;
public const string OAUTH_AUTHENTICATION_SCHEME = "CloudFoundry.OAuth";
public string TokenInfoUrl { get; set; }
public string TokenInfoUrl => AuthorizationUrl + CloudFoundryDefaults.CheckTokenUri;
public bool ValidateCertificates { get; set; }
public bool ValidateAudience { get; set; } = true;
public bool ValidateAudience { get; set; }
public bool ValidateIssuer { get; set; } = true;
public bool ValidateIssuer { get; set; }
public bool ValidateLifetime { get; set; } = true;
public bool ValidateLifetime { get; set; }
[Obsolete("This property will be removed in a future release. Use AuthorizationUrl instead")]
public string OAuthServiceUrl { get => AuthorizationUrl; set => AuthorizationUrl = value; }
public string OAuthServiceUrl { get; set; }
public string AuthorizationEndpoint { get; set; } = CloudFoundryDefaults.AuthorizationUri;
public string ClientId { get; set; }
/// <summary>
/// Gets or sets '/oauth/token'
/// </summary>
public string AccessTokenEndpoint { get; set; } = CloudFoundryDefaults.AccessTokenUri;
public string ClientSecret { get; set; }
public string AuthorizationEndpoint { get; set; } = "oauth/authorize";
public string AccessTokenEndpoint { get; set; } = "oauth/token";
public string UserInformationEndpoint { get; set; } = "userinfo";
public string UserInformationEndpoint { get; set; } = CloudFoundryDefaults.UserInfoUri;
[Obsolete("This property will be removed in a future release. Use CloudFoundryDefaults.CheckTokenUri instead")]
public string TokenInfoEndpoint { get; set; } = "check_token";
[Obsolete("This property will be removed in a future release. Use CloudFoundryDefaults.JwtTokenKey instead")]
public string JwtKeyEndpoint { get; set; } = "token_key";
public string[] RequiredScopes { get; set; }
public string[] AdditionalAudiences { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use app credentials or forward users's credentials
/// </summary>
@ -62,73 +62,99 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
public TokenValidationParameters TokenValidationParameters { get; set; }
internal CloudFoundryTokenKeyResolver TokenKeyResolver { get; set; }
internal Steeltoe.Security.Authentication.CloudFoundry.CloudFoundryTokenKeyResolver TokenKeyResolver { get; set; }
internal CloudFoundryTokenValidator TokenValidator { get; set; }
internal CloudFoundryWcfTokenValidator TokenValidator { get; set; }
public CloudFoundryOptions()
: this(System.Environment.GetEnvironmentVariable("sso_auth_domain"))
internal readonly LoggerFactory LoggerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="CloudFoundryOptions"/> class.
/// </summary>
/// <param name="loggerFactory">For logging within the library</param>
public CloudFoundryOptions(LoggerFactory loggerFactory = null)
{
ClientId = System.Environment.GetEnvironmentVariable("sso_client_id");
ClientSecret = System.Environment.GetEnvironmentVariable("sso_client_secret");
LoggerFactory = loggerFactory;
AuthorizationUrl = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
// can't mark this constructor obsolete but want to make these go away
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("sso_auth_domain")) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("sso_client_id")) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("sso_client_secret")))
{
AuthorizationUrl = Environment.GetEnvironmentVariable("sso_auth_domain");
ClientId = Environment.GetEnvironmentVariable("sso_client_id");
ClientSecret = Environment.GetEnvironmentVariable("sso_client_secret");
Console.Error.WriteLine("sso_* variables were detected in your environment! Future releases of Steeltoe will not uses them for configuration");
Debug.WriteLine("sso_* variables were detected in your environment! Future releases of Steeltoe will not uses them for configuration");
}
TokenKeyResolver = TokenKeyResolver ??
new Steeltoe.Security.Authentication.CloudFoundry.CloudFoundryTokenKeyResolver(
AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri,
null,
ValidateCertificates);
TokenValidator = TokenValidator ?? new CloudFoundryWcfTokenValidator(this, LoggerFactory?.CreateLogger<CloudFoundryWcfTokenValidator>());
}
public CloudFoundryOptions(string authDomain, string clientId, string clientSecret)
: this(authDomain)
[Obsolete("This constructor is expected to be removed in a future release. Please reach out if this constructor is important to you!")]
public CloudFoundryOptions(string authDomain, string clientId, string clientSecret, LoggerFactory loggerFactory = null)
: this(loggerFactory)
{
AuthorizationUrl = authDomain;
ClientId = clientId;
ClientSecret = clientSecret;
}
public CloudFoundryOptions(string authUrl)
[Obsolete("This constructor is expected to be removed in a future release. Please reach out if this constructor is important to you!")]
public CloudFoundryOptions(string authUrl, LoggerFactory loggerFactory = null)
: this(loggerFactory)
{
// CallbackPath = new PathString("/signin-cloudfoundry");
OAuthServiceUrl = authUrl;
ValidateCertificates = false;
ValidateAudience = true;
ValidateIssuer = true;
ValidateLifetime = true;
TokenKeyResolver = TokenKeyResolver ?? new CloudFoundryTokenKeyResolver(this);
TokenValidator = TokenValidator ?? new CloudFoundryTokenValidator(this);
TokenValidationParameters = GetTokenValidationParameters(this);
AuthorizationUrl = authUrl;
}
[Obsolete("This constructor is expected to be removed in a future release. Please reach out if this constructor is important to you!")]
public CloudFoundryOptions(IConfiguration config)
{
// CloudFoundryDefaults.SECURITY_CLIENT_SECTION_PREFIX
var securitySection = config.GetSection("security:oauth2:client");
var securitySection = config.GetSection(CloudFoundryDefaults.SECURITY_CLIENT_SECTION_PREFIX);
securitySection.Bind(this);
SsoServiceInfo info = config.GetSingletonServiceInfo<SsoServiceInfo>();
OAuthServiceUrl = info.AuthDomain;
AuthorizationUrl = info.AuthDomain;
ClientId = info.ClientId;
ClientSecret = info.ClientSecret;
TokenKeyResolver = TokenKeyResolver ?? new CloudFoundryTokenKeyResolver(this);
TokenValidator = TokenValidator ?? new CloudFoundryTokenValidator(this);
TokenValidationParameters = TokenValidationParameters ?? GetTokenValidationParameters(this);
TokenKeyResolver = TokenKeyResolver ??
new Steeltoe.Security.Authentication.CloudFoundry.CloudFoundryTokenKeyResolver(
AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri,
null,
ValidateCertificates);
TokenValidator = TokenValidator ?? new CloudFoundryWcfTokenValidator(this);
TokenValidationParameters = TokenValidationParameters ?? GetTokenValidationParameters();
}
internal static TokenValidationParameters GetTokenValidationParameters(CloudFoundryOptions options)
internal TokenValidationParameters GetTokenValidationParameters()
{
if (options.TokenValidationParameters != null)
if (TokenValidationParameters != null)
{
return options.TokenValidationParameters;
return TokenValidationParameters;
}
var parameters = new TokenValidationParameters();
options.TokenKeyResolver = options.TokenKeyResolver ?? new CloudFoundryTokenKeyResolver(options);
options.TokenValidator = options.TokenValidator ?? new CloudFoundryTokenValidator(options);
options.TokenValidationParameters = parameters;
TokenKeyResolver = TokenKeyResolver ??
new Steeltoe.Security.Authentication.CloudFoundry.CloudFoundryTokenKeyResolver(
AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri,
null,
ValidateCertificates);
TokenValidator = TokenValidator ?? new CloudFoundryWcfTokenValidator(this, LoggerFactory?.CreateLogger<CloudFoundryWcfTokenValidator>());
TokenValidationParameters = parameters;
parameters.ValidateAudience = options.ValidateAudience;
parameters.ValidateIssuer = options.ValidateIssuer;
parameters.ValidateLifetime = options.ValidateLifetime;
parameters.IssuerSigningKeyResolver = options.TokenKeyResolver.ResolveSigningKey;
parameters.IssuerValidator = options.TokenValidator.ValidateIssuer;
parameters.AudienceValidator = options.TokenValidator.ValidateAudience;
parameters.ValidateAudience = ValidateAudience;
parameters.AudienceValidator = TokenValidator.ValidateAudience;
parameters.ValidateIssuer = ValidateIssuer;
parameters.IssuerSigningKeyResolver = TokenKeyResolver.ResolveSigningKey;
parameters.ValidateLifetime = ValidateLifetime;
parameters.IssuerValidator = TokenValidator.ValidateIssuer;
return parameters;
}

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

@ -0,0 +1,47 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Steeltoe.CloudFoundry.Connector.Services;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
internal static class CloudFoundryOptionsConfigurer
{
/// <summary>
/// Apply service binding info to an <see cref="CloudFoundryOptions"/> instance
/// </summary>
/// <param name="si">Service binding information</param>
/// <param name="options">CloudFoundryOptions options to be updated</param>
internal static void Configure(SsoServiceInfo si, CloudFoundryOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (si == null)
{
return;
}
options.AuthorizationUrl = si.AuthDomain;
options.ClientId = si.ClientId;
options.ClientSecret = si.ClientSecret;
var backchannelHttpHandler = CloudFoundryHelper.GetBackChannelHandler(options.ValidateCertificates);
options.TokenValidationParameters = CloudFoundryHelper.GetTokenValidationParameters(options.TokenValidationParameters, options.AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri, backchannelHttpHandler, options.ValidateCertificates, options);
}
}
}

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

@ -24,16 +24,20 @@ using System.Threading.Tasks;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
[Obsolete("This class will be removed in favor of Steeltoe.Security.Authentication.CloudFoundry.CloudFoundryTokenKeyResolver")]
public class CloudFoundryTokenKeyResolver
{
private readonly HttpClient _httpClient;
public CloudFoundryOptions Options { get; internal protected set; }
public Dictionary<string, SecurityKey> Resolved { get; internal protected set; }
public CloudFoundryTokenKeyResolver(CloudFoundryOptions options)
public CloudFoundryTokenKeyResolver(CloudFoundryOptions options, HttpClient httpClient = null)
{
Options = options ?? throw new ArgumentNullException("options null");
Options = options ?? throw new ArgumentNullException(nameof(options));
Resolved = new Dictionary<string, SecurityKey>();
_httpClient = httpClient ?? HttpClientHelper.GetHttpClient(options.ValidateCertificates, options.ClientTimeout);
}
public virtual IEnumerable<SecurityKey> ResolveSigningKey(string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters)
@ -68,39 +72,36 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
return key;
}
/// <summary>
/// Fetch the token keys used by the OAuth server
/// </summary>
/// <returns><see cref="JsonWebKeySet"/></returns>
/// <exception cref="ArgumentNullException">From the underlying HttpClient.SendAsync call - request is null</exception>
/// <exception cref="InvalidOperationException">From the underlying HttpClient.SendAsync call - request already sent</exception>
/// <exception cref="HttpRequestException">From the underlying HttpClient.SendAsync call - possibly network, DNS or certificate issues</exception>
public virtual async Task<JsonWebKeySet> FetchKeySet()
{
var requestMessage = new HttpRequestMessage(HttpMethod.Get, Options.JwtKeyEndpoint);
var requestMessage = new HttpRequestMessage(HttpMethod.Get, Options.AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri);
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
using (var handler = new HttpClientHandler())
HttpClientHelper.ConfigureCertificateValidatation(
Options.ValidateCertificates,
out SecurityProtocolType protocolType,
out RemoteCertificateValidationCallback prevValidator);
HttpResponseMessage response = null;
try
{
HttpClientHelper.ConfigureCertificateValidatation(Options.ValidateCertificates, out SecurityProtocolType protocolType, out RemoteCertificateValidationCallback prevValidator);
response = await _httpClient.SendAsync(requestMessage);
}
finally
{
HttpClientHelper.RestoreCertificateValidation(Options.ValidateCertificates, protocolType, prevValidator);
}
HttpClient client = new HttpClient(handler)
{
BaseAddress = new Uri(Options.OAuthServiceUrl)
};
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(requestMessage);
}
catch (Exception ex)
{
throw new Exception("Error getting keys to validate token:" + ex.Message);
}
finally
{
HttpClientHelper.RestoreCertificateValidation(Options.ValidateCertificates, protocolType, prevValidator);
}
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
return GetJsonWebKeySet(result);
}
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsJsonAsync<JsonWebKeySet>();
}
return null;

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

@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.ServiceModel.Web;
@ -24,71 +23,54 @@ using System.Text.RegularExpressions;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
public class CloudFoundryTokenValidator
public class CloudFoundryWcfTokenValidator : CloudFoundryTokenValidator
{
public CloudFoundryOptions Options { get; internal protected set; }
private static ILogger<CloudFoundryTokenValidator> _logger;
private JwtSecurityTokenHandler _handler = new JwtSecurityTokenHandler();
public CloudFoundryTokenValidator(CloudFoundryOptions options)
public CloudFoundryWcfTokenValidator(CloudFoundryOptions options, ILogger<CloudFoundryTokenValidator> logger = null)
: base(options)
{
Options = options ?? throw new ArgumentNullException("options null");
Options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger;
}
public static void ThrowJwtException(string exeptionMessage, string message)
/// <summary>
/// <para>Throws a WebFaultException with HTTP 401. exceptionMessage used for Detail if provided, else uses message</para>
/// Sets WWW-Authenticate value = 'Bearer realm="default",error="{message}",error_description="{exceptionMessage}'"
/// </summary>
/// <param name="exceptionMessage">Falls back to value of message if null or empty</param>
/// <param name="message">Uses value "invalid_token" if not provided</param>
public static void ThrowJwtException(string exceptionMessage, string message)
{
Console.Out.WriteLine("error: " + exeptionMessage + " " + message);
_logger?.LogError("Encountered JWT-related exception: " + exceptionMessage + " " + message);
if (WebOperationContext.Current != null)
{
var headers = WebOperationContext.Current.OutgoingResponse.Headers;
// https://tools.ietf.org/html/rfc6750 - "WWW-Authenticate", "Bearer error=\"insufficient_scope\"");
// https://tools.ietf.org/html/rfc6750#section-3 - "WWW-Authenticate", "Bearer error=\"insufficient_scope\"");
if (string.IsNullOrEmpty(message))
{
message = "invalid_token";
}
if (string.IsNullOrEmpty(exeptionMessage))
if (string.IsNullOrEmpty(exceptionMessage))
{
exeptionMessage = message;
exceptionMessage = message;
}
headers.Add(HttpResponseHeader.WwwAuthenticate, string.Format("Bearer realm=\"default\",error=\"{0}\",error_description=\"{1}\"", message, Regex.Replace(exeptionMessage, @"\s+", " ")));
headers.Add(HttpResponseHeader.WwwAuthenticate, string.Format("Bearer realm=\"default\",error=\"{0}\",error_description=\"{1}\"", message, Regex.Replace(exceptionMessage, @"\s+", " ")));
}
throw new WebFaultException<string>(exeptionMessage ?? message, HttpStatusCode.Unauthorized);
}
public virtual string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
if (issuer.Contains("uaa"))
WebOperationContext ctx = WebOperationContext.Current;
if (ctx != null)
{
return issuer;
ctx.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
}
return null;
}
public virtual bool ValidateAudience(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
foreach (string audience in audiences)
{
if (audience.Equals(Options.ClientId))
{
return true;
}
if (Options.AdditionalAudiences != null)
{
bool found = Options.AdditionalAudiences.Any(x => x.Equals(audience));
if (found)
{
return true;
}
}
}
return false;
throw new WebFaultException<string>(exceptionMessage ?? message, HttpStatusCode.Unauthorized);
}
public virtual ClaimsPrincipal ValidateToken(string token)
@ -109,7 +91,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
}
catch (Exception ex)
{
Console.WriteLine("ValidateToken fails:" + ex.Message);
_logger?.LogError("ValidateToken failed: " + ex.Message);
ThrowJwtException(ex.Message, "invalid_token");
}
@ -128,36 +110,5 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
return principal;
}
/// <summary>
/// This method validate scopes provided in configuration,
/// to perform scope based Authorization
/// </summary>
/// <param name="validJwt">JSON Web token</param>
/// <returns>true if scopes validated</returns>
protected virtual bool ValidateScopes(JwtSecurityToken validJwt)
{
if (Options.RequiredScopes == null || Options.RequiredScopes.Count<string>() == 0)
{
return true; // nocheck
}
if (!validJwt.Claims.Any(x => x.Type.Equals("scope") || x.Type.Equals("authorities")))
{
return false; // no scopes at all
}
bool found = false;
foreach (Claim claim in validJwt.Claims)
{
if (claim.Type.Equals("scope") || (claim.Type.Equals("authorities")
&& Options.RequiredScopes.Any(x => x.Equals(claim.Value))))
{
return true;
}
}
return found;
}
}
}

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

@ -13,9 +13,11 @@
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using System;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
[Obsolete]
public class JsonWebKeySetEx : JsonWebKeySet
{
public JsonWebKeySetEx(string json)

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

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Net;
using System.Security.Claims;
using System.ServiceModel;
using System.ServiceModel.Channels;
@ -36,6 +38,43 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
_options = options;
}
internal ClaimsPrincipal GetPrincipalFromRequestHeaders(WebHeaderCollection headers)
{
// Fail if SSO Config is missing
if (_options?.AuthorizationUrl == null || _options?.AuthorizationUrl?.Length == 0)
{
CloudFoundryWcfTokenValidator.ThrowJwtException("SSO Configuration is missing", null);
}
// check if any auth header is present
if (string.IsNullOrEmpty(headers["Authorization"]))
{
CloudFoundryWcfTokenValidator.ThrowJwtException("No Authorization header", null);
}
// check if the auth header has a bearer token format
if (!headers["Authorization"].StartsWith("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
CloudFoundryWcfTokenValidator.ThrowJwtException("Wrong Token Format", null);
}
// get just the token out of the header value
string jwt;
try
{
jwt = headers["Authorization"].Split(' ')[1];
// Return an identity from validated token
return _options.TokenValidator.ValidateToken(jwt);
}
catch (IndexOutOfRangeException)
{
CloudFoundryWcfTokenValidator.ThrowJwtException("No Token", null);
}
throw new Exception("idk");
}
protected override bool CheckAccessCore(OperationContext operationContext)
{
HttpRequestMessageProperty httpRequestMessage;
@ -43,40 +82,13 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
if (operationContext.RequestContext.RequestMessage.Properties.TryGetValue(HttpRequestMessageProperty.Name, out object httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers["Authorization"]))
var claimsPrincipal = GetPrincipalFromRequestHeaders(httpRequestMessage.Headers);
if (claimsPrincipal != null)
{
CloudFoundryTokenValidator.ThrowJwtException("No Authorization header", null);
// Set the Principal created from token
SetPrincipal(operationContext, claimsPrincipal);
return true;
}
// Get Bearer token
if (!httpRequestMessage.Headers["Authorization"].StartsWith("Bearer "))
{
CloudFoundryTokenValidator.ThrowJwtException("No Token", null);
}
string jwt = httpRequestMessage.Headers["Authorization"].Split(' ')[1];
if (string.IsNullOrEmpty(jwt))
{
CloudFoundryTokenValidator.ThrowJwtException("Wrong Token Format", null);
}
// Get SSO Config
if (_options?.OAuthServiceUrl == null || _options?.OAuthServiceUrl?.Length == 0)
{
CloudFoundryTokenValidator.ThrowJwtException("SSO Configuration is missing", null);
}
// Validate Token
ClaimsPrincipal claimsPrincipal = _options.TokenValidator.ValidateToken(jwt);
if (claimsPrincipal == null)
{
return false;
}
// Set the Principal created from token
SetPrincipal(operationContext, claimsPrincipal);
return true;
}
return false;

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

@ -14,8 +14,10 @@
using System;
using System.Configuration;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
@ -50,16 +52,16 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
get { return typeof(JwtHeaderEndpointBehavior); }
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new JwtHeaderMessageInspector(_options, _userToken));
}
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
@ -70,7 +72,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
protected override object CreateBehavior()
{
// Create the endpoint behavior that will insert the message inspector into the client runtime
return new JwtHeaderEndpointBehavior(_options);
return new JwtHeaderEndpointBehavior(_options, _userToken);
}
}
}

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

@ -13,6 +13,7 @@
// limitations under the License.
using System;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
@ -22,14 +23,16 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
{
public class JwtHeaderMessageInspector : IClientMessageInspector
{
private readonly HttpClient _httpClient;
private readonly CloudFoundryOptions _options;
private string _token;
private CloudFoundryOptions _options;
private string _userToken;
public JwtHeaderMessageInspector(CloudFoundryOptions options, string userToken)
public JwtHeaderMessageInspector(CloudFoundryOptions options, string userToken, HttpClient httpClient = null)
{
_options = options;
_userToken = userToken;
_httpClient = httpClient;
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
@ -78,7 +81,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
private string GetAccessToken()
{
CloudFoundryClientTokenResolver tokenResolver = new CloudFoundryClientTokenResolver(_options);
CloudFoundryClientTokenResolver tokenResolver = new CloudFoundryClientTokenResolver(_options, _httpClient);
try
{

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

@ -46,7 +46,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
string matchACL = Environment.GetEnvironmentVariable(Role);
if (string.IsNullOrEmpty(matchACL))
{
CloudFoundryTokenValidator.ThrowJwtException("Configuration for not provided for Role: " + Role, "insufficient_scope");
CloudFoundryWcfTokenValidator.ThrowJwtException("Configuration for not provided for Role: " + Role, "insufficient_scope");
}
IPrincipal principal = Thread.CurrentPrincipal;
@ -58,7 +58,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
else
{
Console.Out.WriteLine("Access denied user is not in Role: " + Role);
CloudFoundryTokenValidator.ThrowJwtException("Access denied user is not in Role: " + Role, "insufficient_scope");
CloudFoundryWcfTokenValidator.ThrowJwtException("Access denied user is not in Role: " + Role, "insufficient_scope");
return null;
}
}

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

@ -0,0 +1,17 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Steeltoe.Security.Authentication.CloudFoundryWcf.Test")]

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

@ -16,7 +16,6 @@ using System;
using System.Security;
using System.Security.Claims;
using System.Security.Permissions;
using System.Threading;
using System.Web;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
@ -43,7 +42,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf
if (principal == null || !principal.HasClaim("scope", Scope))
{
Console.Out.WriteLine("Access denied token is not in Scope: " + Scope);
CloudFoundryTokenValidator.ThrowJwtException("Access denied token does not have Scope: " + Scope, "insufficient_scope");
CloudFoundryWcfTokenValidator.ThrowJwtException("Access denied token does not have Scope: " + Scope, "insufficient_scope");
}
}

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

@ -21,7 +21,6 @@
<ItemGroup>
<PackageReference Include="Steeltoe.CloudFoundry.ConnectorBase" Version="$(SteeltoeConnectorVersion)" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="$(JwtTokensVersion)" />
<PackageReference Include="System.Net.Http" Version="$(HttpClientVersion)" />
<PackageReference Include="Steeltoe.Common.Http" Version="$(SteeltoeCommonVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
@ -41,6 +40,7 @@
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
<RootNamespace>Steeltoe.Security.Authentication.CloudFoundry</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
@ -48,5 +48,10 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == ''">
<ProjectReference Include="..\Steeltoe.Security.Authentication.CloudFoundryBase\Steeltoe.Security.Authentication.CloudFoundryBase.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(CI_BUILD)' == 'True'">
<PackageReference Include="Steeltoe.Security.Authentication.CloudFoundryBase" Version="$(SteeltoeVersion)$(SteeltoeVersionSuffix)" />
</ItemGroup>
</Project>

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

@ -38,6 +38,19 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
Assert.Null(result2);
}
[Fact]
public void GetTokenValidationParameters_ReturnsExpected()
{
var parameters = CloudFoundryHelper.GetTokenValidationParameters(null, "http://foo.bar.com/keyurl", null, false);
Assert.False(parameters.ValidateAudience);
Assert.True(parameters.ValidateIssuer);
Assert.NotNull(parameters.IssuerValidator);
// Assert.Equal(cftv.ValidateIssuer, parameters.IssuerValidator);
Assert.True(parameters.ValidateLifetime);
Assert.NotNull(parameters.IssuerSigningKeyResolver);
}
[Fact]
public void GetExpTime_FindsTime()
{

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

@ -21,8 +21,16 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
[Fact]
public void ValidateIssuer_ValidatesCorrectly()
{
Assert.NotNull(CloudFoundryTokenValidator.ValidateIssuer("https://uaa.system.testcloud.com/", null, null));
Assert.Null(CloudFoundryTokenValidator.ValidateIssuer("https://foobar.system.testcloud.com/", null, null));
// arrange
var cftv = new CloudFoundryTokenValidator();
// act
var uaaResult = cftv.ValidateIssuer("https://uaa.system.testcloud.com/", null, null);
var foobarResult = cftv.ValidateIssuer("https://foobar.system.testcloud.com/", null, null);
// assert
Assert.NotNull(uaaResult);
Assert.Null(foobarResult);
}
}
}

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

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;net461</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Steeltoe.Security.Authentication.CloudFoundryBase\Steeltoe.Security.Authentication.CloudFoundryBase.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="RichardSzalay.MockHttp" Version="$(MockHttp)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitStudioVersion)" />
<DotNetCliToolReference Include="dotnet-xunit" Version="$(XunitVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreTestVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
<RootNamespace>Steeltoe.Security.Authentication.CloudFoundry.Test</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
</Project>

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

@ -18,32 +18,6 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
{
public class TestHelpers
{
public static string CreateTempFile(string contents)
{
var tempFile = Path.GetTempFileName();
File.WriteAllText(tempFile, contents);
return tempFile;
}
public static Stream StringToStream(string str)
{
var memStream = new MemoryStream();
var textWriter = new StreamWriter(memStream);
textWriter.Write(str);
textWriter.Flush();
memStream.Seek(0, SeekOrigin.Begin);
return memStream;
}
public static string StreamToString(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
public static string GetValidTokenRequestResponse()
{
return @"{'access_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJjNjUxZGI2NjllODM0NGNkOTMwMGUxYjJkYjUxOTgwZCIsInN1YiI6IjEzYmI2ODQxLWU0ZDYtNGE5YS04NzZjLTllZjEzYWE2MWNjNyIsInNjb3BlIjpbIm9wZW5pZCJdLCJjbGllbnRfaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJjaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJhenAiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6IjEzYmI2ODQxLWU0ZDYtNGE5YS04NzZjLTllZjEzYWE2MWNjNyIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6InRlc3Rzc291c2VyIiwiZW1haWwiOiJ0ZXN0c3NvdXNlckB0ZXN0Y2xvdWQuY29tIiwiYXV0aF90aW1lIjoxNDcyNzUxNDUyLCJyZXZfc2lnIjoiNTExMzU4NDIiLCJpYXQiOjE0NzI3NTE0NTIsImV4cCI6MTQ3Mjc5NDY1MiwiaXNzIjoiaHR0cHM6Ly90ZXN0c3NvLnVhYS5zeXN0ZW0udGVzdGNsb3VkLmNvbS9vYXV0aC90b2tlbiIsInppZCI6ImVmMzA0ZWQwLTNhNWEtNDQyZS05YWQ4LWU0Y2EzYWQ1MTIwZSIsImF1ZCI6WyI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJvcGVuaWQiXX0.f6CA7WrGrY__CNh - RvY1pl05 - 2PQjeyE7C_OH48ChfoxsL4hO8BJEofz934_Jox6dXJwp3wyeMPYwyS5fKGA3ASJT5KqdZ8QdHhOj7lbcejwTUUfG_t - K26W39BLT - cEbpTFyHzBUK79fhIfwSmmXJWU0vQaZ3F0dYKRm98rCEQo9s5jr2jrqHep52vL3Xo8mwDJO4GBc85p8zzVpUPOYMkKiWfEujIOvBL0yTqAL64NsYmFupbKyt5dgf - eF - 1GOuP7Rbgbk_llQuFOkwYr4dLzgw045wXeU3uzJzTrGNzIlJQPOijU1w2uBgYi7fT2wgJDKb4toxot2_6wSXpqBw','token_type':'bearer','refresh_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ2FjeS10b2tlbi1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiJjNjUxZGI2NjllODM0NGNkOTMwMGUxYjJkYjUxOTgwZC1yIiwic3ViIjoiMTNiYjY4NDEtZTRkNi00YTlhLTg3NmMtOWVmMTNhYTYxY2M3Iiwic2NvcGUiOlsib3BlbmlkIl0sImlhdCI6MTQ3Mjc1MTQ1MiwiZXhwIjoxNDc1MzQzNDUyLCJjaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJjbGllbnRfaWQiOiI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJpc3MiOiJodHRwczovL3Rlc3Rzc28udWFhLnN5c3RlbS50ZXN0Y2xvdWQuY29tL29hdXRoL3Rva2VuIiwiemlkIjoiZWYzMDRlZDAtM2E1YS00NDJlLTlhZDgtZTRjYTNhZDUxMjBlIiwiZ3JhbnRfdHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsInVzZXJfbmFtZSI6InRlc3Rzc291c2VyIiwib3JpZ2luIjoidWFhIiwidXNlcl9pZCI6IjEzYmI2ODQxLWU0ZDYtNGE5YS04NzZjLTllZjEzYWE2MWNjNyIsInJldl9zaWciOiI1MTEzNTg0MiIsImF1ZCI6WyI0NGY4NTRmZC00OWZlLTQxMDItYjBkMS1hNTI2ZGJmZjkzZDkiLCJvcGVuaWQiXX0.WMEZtwvoJXU8njysozaNglmpYouxoY65AirXC3ypdLsnSVE_OtePQQi3hb3YPQDbrc6JAETnCGcDBJVNUPsnlg4g_O_RPLejB4ZlMioRTZRNv9gzOlELqjgBLAVEkWOiBFuH7hwbR7X7DlmFTcDFSc - gQY5TaLn - wR6PVtQM3oRw_nx1f3 - xgDGcYx7Ktk9rov - xF_C0Pzsoet4dvG78YUlnXgR08KwJnDX_ElV - 0vTOtybqjs_XGSS9N39EWeeykCzciddhqVCfmMM5VMCVaP - 7pr - VDz0N8ArSWfOobI4nPqcj50wvPb595SgId - NlNP4fBX1ibnCeOxWwuzId0w','expires_in':43199,'scope':'openid','jti':'c651db669e8344cd9300e1b2db51980d'}";

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

@ -0,0 +1,137 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using RichardSzalay.MockHttp;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Test
{
public class TokenExchangerTest
{
[Fact]
public void GetTokenRequestMessage_ReturnsCorrectly()
{
// arrange
var opts = new AuthServerOptions { ClientId = "clientId", ClientSecret = "clientSecret" };
var tEx = new TokenExchanger(opts);
// act
var message = tEx.GetTokenRequestMessage(new List<KeyValuePair<string, string>>(), "redirectUri");
// assert
Assert.NotNull(message);
var content = message.Content as FormUrlEncodedContent;
Assert.NotNull(content);
Assert.Equal(HttpMethod.Post, message.Method);
Assert.Contains(new MediaTypeWithQualityHeaderValue("application/json"), message.Headers.Accept);
}
[Fact]
public void AuthCodeTokenRequestParameters_ReturnsCorrectly()
{
// arrange
var opts = new AuthServerOptions { ClientId = "clientId", ClientSecret = "clientSecret", CallbackUrl = "redirect_uri" };
var tEx = new TokenExchanger(opts);
// act
var parameters = tEx.AuthCodeTokenRequestParameters("authcode");
// assert
Assert.NotNull(parameters);
Assert.Equal(opts.ClientId, parameters.First(i => i.Key == "client_id").Value);
Assert.Equal(opts.ClientSecret, parameters.First(i => i.Key == "client_secret").Value);
Assert.Equal("redirect_uri", parameters.First(i => i.Key == "redirect_uri").Value);
Assert.Equal("authcode", parameters.First(i => i.Key == "code").Value);
Assert.Equal(OpenIdConnectGrantTypes.AuthorizationCode, parameters.First(i => i.Key == "grant_type").Value);
}
[Fact]
public void ClientCredentialsTokenRequestParameters_ReturnsCorrectly()
{
// arrange
var opts = new AuthServerOptions { ClientId = "clientId", ClientSecret = "clientSecret", CallbackUrl = "redirect_uri" };
var tEx = new TokenExchanger(opts);
// act
var parameters = tEx.ClientCredentialsTokenRequestParameters();
// assert
Assert.NotNull(parameters);
Assert.Equal(opts.ClientId, parameters.First(i => i.Key == "client_id").Value);
Assert.Equal(opts.ClientSecret, parameters.First(i => i.Key == "client_secret").Value);
Assert.Equal(OpenIdConnectGrantTypes.ClientCredentials, parameters.First(i => i.Key == "grant_type").Value);
}
[Fact]
public async void ExchangeAuthCodeForClaimsIdentity_ExchangesCodeForIdentity()
{
// arrange
var options = new AuthServerOptions()
{
AuthorizationUrl = "http://localhost/tokenUrl"
};
var exchanger = new TokenExchanger(options, GetMockHttpClient());
// act
var identity = await exchanger.ExchangeAuthCodeForClaimsIdentity("goodCode");
// assert
Assert.IsType<ClaimsIdentity>(identity);
}
[Fact]
public async void ExchangeAuthCodeForClaimsIdentity_ReturnsNullOnFailure()
{
// arrange
var options = new AuthServerOptions()
{
AuthorizationUrl = "http://localhost/tokenUrl"
};
var httpClient = new object(); // TODO: replace with mock that does stuff
var exchanger = new TokenExchanger(options, GetMockHttpClient());
// act
var identity = await exchanger.ExchangeAuthCodeForClaimsIdentity("badCode");
// assert
Assert.Null(identity);
}
private readonly string realTokenResponse = "{'access_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI3YTMzYzVhNjhjY2I0YjRiYmQ5N2I4MTRlZWExMTc3MiIsInN1YiI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsInNjb3BlIjpbInRlc3Rncm91cCIsIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJhenAiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImRhdmUiLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsImF1dGhfdGltZSI6MTU0NjY0MjkzMywicmV2X3NpZyI6ImE1ZWY2ODg5IiwiaWF0IjoxNTQ2NjQyOTM1LCJleHAiOjE1NDY2ODYxMzUsImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsImF1ZCI6WyJvcGVuaWQiLCJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiXX0.tGTXZzuuUSObTwdPHSx-zvnld20DH5hlOZlYp5DhjwkMIsZB0uIvVwbVDkPp7H_AmmeJoo6vqa5hbbgfgnYpTrKlCGOypnHoa3yRIKrwcDmLLujaMz6ApZeaJ7sJN-0N1UnPZ9iGcqvt9hNb_198zRnMXGH72oI0e2iGUBV1olCFVdZTnMGT7sUieDFKy7n0ghZYq_gUI8rfvTwiC3lfxv0nDXz4oE9Z-UKhK6q1zkAtQrz61FQ_CHONejz1JnuxQFKMMvm8JLcRkn6OL-EcSi1hkmFw0efO1OqccQacxphlafyHloVPQ3IOtzLjCf8sJ5NgTdCTC3iddT_sYovdrg','token_type':'bearer','id_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJzdWIiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJhdWQiOlsiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il0sImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsImV4cCI6MTU0NjY4NjEzNSwiaWF0IjoxNTQ2NjQyOTM1LCJhbXIiOlsicHdkIl0sImF6cCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsIm9yaWdpbiI6InVhYSIsImp0aSI6IjdhMzNjNWE2OGNjYjRiNGJiZDk3YjgxNGVlYTExNzcyIiwicHJldmlvdXNfbG9nb25fdGltZSI6MTU0NjYzMzU1NjA0NCwiZW1haWxfdmVyaWZpZWQiOnRydWUsImNsaWVudF9pZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImNpZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX25hbWUiOiJkYXZlIiwicmV2X3NpZyI6ImE1ZWY2ODg5IiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsImF1dGhfdGltZSI6MTU0NjY0MjkzM30.KkTVOTg7Bhj1EWO63QmWzjnEAnKesoSLGfGL-2Y19PiK62KRd66dOcVcQEA_nWIE1mJQZsDByQYwcEuVRAiP-mXY0L2MrWUnRlW5yn1fqOc44iSDggMF5VfjGQok8fGfBPQX7va0evfaOaulRMuWsijYvzZtV-KncGUpxGwkzRs2AAEbkAv1_vAD2zSGJ-ji5L7s4a2-Qc_LxDlNANoYllzMTVxZ2DSvVPLfKPNGgSvNC7t053ExfGRXk-6cPxVznkngWDlYALeXsnrbvXdjuk1dw8dcXRhL4PUJDI7EVvTdqzd1fPYRgAQ3KJZOmvzBY7bxFtoq9odmKKHTI4CFUQ','refresh_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI4NjdiNTcxNTBlNmM0ZDQ3OTM0NmE3ZjgwMTJmMGY2My1yIiwic3ViIjoiOTViYmIzNDYtYjY4Yy00ZjE1LWIzNDEtNzBkNjBmOWY0NmJhIiwic2NvcGUiOlsidGVzdGdyb3VwIiwib3BlbmlkIl0sImlhdCI6MTU0NjY0MjkzNSwiZXhwIjoxNTQ5MjM0OTM1LCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJpc3MiOiJodHRwczovL3N0ZWVsdG9lLnVhYS5jZi5iZWV0LnNwcmluZ2FwcHMuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiIzYTNlYWRhZC01YjJmLTQ1MzAtYWY5NS1hNjliYzBhZmQxNWIiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9uYW1lIjoiZGF2ZSIsIm9yaWdpbiI6InVhYSIsInVzZXJfaWQiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJyZXZfc2lnIjoiYTVlZjY4ODkiLCJhdWQiOlsib3BlbmlkIiwiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il19.cOHCiZgmbtN1uyuvEou879du5XVvJqHEmkf6-2G0V9I1qYy0PJbGMHNL32d6L9LVsnXs4iTfi1qpKfdEGbfZbX8rNwNwcRoPndOZBepTTZHJPcU49VEF4t1hZ5icbt_4mSiQwpNy2n_mqwHizV8BAeOeQc_zUE-YFiuQC6V3ac0rTYO4NJGSioSG_y6HFp3refiPZVPT7En-dwhd-Yic1SB1OPxQ5bRNS7AAjGLeeCWOZQVxwQa5Atv9t791yUkH1zX8Psh_LRy2O8E6o7IBLI_yjz5N-YIHRa-BuMPDdKWarcOjy1KKJM27HynAQo1T4J0Ft8hSsIxFlObTd3-FVQ','expires_in':43199,'scope':'testgroup openid','jti':'7a33c5a68ccb4b4bbd97b814eea11772'}";
private HttpClient GetMockHttpClient()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.When("http://localhost/tokenUrl")
.WithFormData("code", "goodCode")
.Respond("application/json", realTokenResponse); // Respond with JSON
mockHttp
.When("http://localhost/tokenUrl")
.WithFormData("code", "badCode")
.Respond(HttpStatusCode.BadRequest); // Respond with JSON
return mockHttp.ToHttpClient();
}
}
}

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

@ -43,24 +43,12 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
JwtBearerOptions jwtOpts = new JwtBearerOptions();
CloudFoundryJwtBearerConfigurer.Configure(info, jwtOpts, opts);
Assert.Equal("http://domain" + CloudFoundryDefaults.JwtTokenKey, opts.JwtKeyUrl);
Assert.Equal("http://domain" + CloudFoundryDefaults.JwtTokenUri, opts.JwtKeyUrl);
Assert.True(opts.ValidateCertificates);
Assert.Equal(opts.ClaimsIssuer, jwtOpts.ClaimsIssuer);
Assert.Null(jwtOpts.BackchannelHttpHandler);
Assert.NotNull(jwtOpts.TokenValidationParameters);
Assert.Equal(opts.SaveToken, jwtOpts.SaveToken);
}
[Fact]
public void GetTokenValidationParameters_ReturnsExpected()
{
var parameters = CloudFoundryJwtBearerConfigurer.GetTokenValidationParameters(null, "http://foo.bar.com/keyurl", null, false);
Assert.False(parameters.ValidateAudience);
Assert.True(parameters.ValidateIssuer);
Assert.NotNull(parameters.IssuerValidator);
Assert.Equal(CloudFoundryTokenValidator.ValidateIssuer, parameters.IssuerValidator);
Assert.True(parameters.ValidateLifetime);
Assert.NotNull(parameters.IssuerSigningKeyResolver);
}
}
}

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

@ -25,7 +25,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
string authURL = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
Assert.Equal(CloudFoundryDefaults.AuthenticationScheme, opts.ClaimsIssuer);
Assert.Equal(authURL + CloudFoundryDefaults.JwtTokenKey, opts.JwtKeyUrl);
Assert.Equal(authURL + CloudFoundryDefaults.JwtTokenUri, opts.JwtKeyUrl);
Assert.True(opts.SaveToken);
}
}

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

@ -33,45 +33,6 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
{
public class CloudFoundryOAuthHandlerTest
{
[Fact]
public void GetTokenRequestParameters_ReturnsCorrectly()
{
var opts = new CloudFoundryOAuthOptions()
{
Backchannel = new HttpClient(new TestMessageHandler())
};
MyTestCloudFoundryHandler testHandler = GetTestHandler(opts);
var parameters = testHandler.GetTokenRequestParameters("code", "redirectUri");
Assert.NotNull(parameters);
Assert.Equal(parameters["client_id"], opts.ClientId);
Assert.Equal("redirectUri", parameters["redirect_uri"]);
Assert.Equal(parameters["client_secret"], opts.ClientSecret);
Assert.Equal("code", parameters["code"]);
Assert.Equal("authorization_code", parameters["grant_type"]);
}
[Fact]
public void GetTokenRequestMessage_ReturnsCorrectly()
{
var opts = new CloudFoundryOAuthOptions()
{
Backchannel = new HttpClient(new TestMessageHandler())
};
MyTestCloudFoundryHandler testHandler = GetTestHandler(opts);
var message = testHandler.GetTokenRequestMessage("code", "redirectUri");
Assert.NotNull(message);
var content = message.Content as FormUrlEncodedContent;
Assert.NotNull(content);
Assert.Equal(HttpMethod.Post, message.Method);
message.Headers.Accept.Contains(new MediaTypeWithQualityHeaderValue("application/json"));
}
[Fact]
public async void ExchangeCodeAsync_SendsTokenRequest_ReturnsValidTokenInfo()
{
@ -121,7 +82,7 @@ namespace Steeltoe.Security.Authentication.CloudFoundry.Test
MyTestCloudFoundryHandler testHandler = GetTestHandler(opts);
var logger = new LoggerFactory().CreateLogger("ExchangeCodeAsync_SendsTokenRequest");
var resp = await testHandler.TestExchangeCodeAsync("code", "redirectUri");
var resp = await testHandler.TestExchangeCodeAsync("code", "http://redirectUri");
Assert.NotNull(handler.LastRequest);
Assert.Equal(HttpMethod.Post, handler.LastRequest.Method);

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
@ -11,6 +11,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Steeltoe.Security.Authentication.CloudFoundryCore\Steeltoe.Security.Authentication.CloudFoundryCore.csproj" />
<ProjectReference Include="..\Steeltoe.Security.Authentication.CloudFoundryBaseTest\Steeltoe.Security.Authentication.CloudFoundryBase.Test.csproj" />
</ItemGroup>
<ItemGroup>

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

@ -0,0 +1,65 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Extensions.Configuration;
using Microsoft.Owin.Builder;
using Owin;
using System;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin.Test
{
public class IAppBuilderExtensionsTest
{
[Fact]
public void UseCloudFoundryOpenIdConnect_ThrowsIfBuilderNull()
{
IAppBuilder app = null;
IConfiguration config = new ConfigurationBuilder().Build();
var exception = Assert.Throws<ArgumentNullException>(() => app.UseCloudFoundryOpenIdConnect(config));
Assert.Equal("appBuilder", exception.ParamName);
}
[Fact]
public void UseCloudFoundryOpenIdConnect_ThrowsIfConfigurationNull()
{
IAppBuilder app = new AppBuilder();
IConfiguration config = null;
var exception = Assert.Throws<ArgumentNullException>(() => app.UseCloudFoundryOpenIdConnect(config));
Assert.Equal("configuration", exception.ParamName);
}
[Fact]
public void UseCloudFoundryJwtBearerAuthentication_ThrowsIfBuilderNull()
{
IAppBuilder app = null;
IConfiguration config = new ConfigurationBuilder().Build();
var exception = Assert.Throws<ArgumentNullException>(() => app.UseCloudFoundryJwtBearerAuthentication(config));
Assert.Equal("appBuilder", exception.ParamName);
}
[Fact]
public void UseCloudFoundryJwtBearerAuthentication_ThrowsIfConfigurationNull()
{
IAppBuilder app = new AppBuilder();
IConfiguration config = null;
var exception = Assert.Throws<ArgumentNullException>(() => app.UseCloudFoundryJwtBearerAuthentication(config));
Assert.Equal("configuration", exception.ParamName);
}
}
}

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

@ -0,0 +1,67 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Steeltoe.CloudFoundry.Connector.Services;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin.Test
{
public class CloudFoundryJwtOwinConfigurerTest
{
[Fact]
public void Configure_NoServiceInfo_ReturnsExpected()
{
// arrange
CloudFoundryJwtBearerAuthenticationOptions opts = new CloudFoundryJwtBearerAuthenticationOptions();
// act
CloudFoundryJwtOwinConfigurer.Configure(null, opts);
// assert
Assert.Equal("http://" + CloudFoundryDefaults.OAuthServiceUrl + CloudFoundryDefaults.JwtTokenUri, opts.JwtKeyUrl);
Assert.True(opts.ValidateCertificates); // <- default value
Assert.NotNull(opts.TokenValidationParameters);
}
[Fact]
public void Configure_NoOptions_ReturnsExpected()
{
// arrange
SsoServiceInfo info = new SsoServiceInfo("foobar", "clientId", "secret", "http://domain");
// act
CloudFoundryJwtOwinConfigurer.Configure(info, null);
// nothing to assert
Assert.True(true, "If we got here, we didn't attempt to set properties on a null object");
}
[Fact]
public void Configure_WithServiceInfo_ReturnsExpected()
{
// arrange
CloudFoundryJwtBearerAuthenticationOptions opts = new CloudFoundryJwtBearerAuthenticationOptions();
Assert.Null(opts.TokenValidationParameters);
SsoServiceInfo info = new SsoServiceInfo("foobar", "clientId", "secret", "http://domain");
// act
CloudFoundryJwtOwinConfigurer.Configure(info, opts);
// assert
Assert.Equal("http://domain" + CloudFoundryDefaults.JwtTokenUri, opts.JwtKeyUrl);
Assert.True(opts.ValidateCertificates); // <- default value
Assert.NotNull(opts.TokenValidationParameters);
}
}
}

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

@ -0,0 +1,91 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Owin;
using Steeltoe.CloudFoundry.Connector.Services;
using System;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin.Test
{
public class OpenIdConnectConfigurerTest
{
[Fact]
public void Configure_NoOptions_Throws()
{
var exception = Assert.Throws<ArgumentNullException>(() => OpenIdConnectConfigurer.Configure(null, null));
Assert.Equal("options", exception.ParamName);
}
[Fact]
public void Configure_NoServiceInfo_ReturnsDefaults()
{
// arrange
var opts = new OpenIdConnectOptions();
string authURL = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
// act
OpenIdConnectConfigurer.Configure(null, opts);
// assert
Assert.Equal(CloudFoundryDefaults.DisplayName, opts.AuthenticationType);
Assert.Equal(CloudFoundryDefaults.ClientId, opts.ClientId);
Assert.Equal(CloudFoundryDefaults.ClientSecret, opts.ClientSecret);
Assert.Equal(new PathString(CloudFoundryDefaults.CallbackPath), opts.CallbackPath);
Assert.Equal(authURL + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl);
Assert.True(opts.ValidateCertificates);
}
[Fact]
public void Configure_ObsoleteVersion_NoServiceInfo_ReturnsDefaults()
{
// arrange
#pragma warning disable CS0618 // Type or member is obsolete
var opts = new OpenIDConnectOptions();
#pragma warning restore CS0618 // Type or member is obsolete
string authURL = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
// act
OpenIdConnectConfigurer.Configure(null, opts);
// assert
Assert.Equal("PivotalSSO", opts.AuthenticationType);
Assert.Equal(CloudFoundryDefaults.ClientId, opts.ClientId);
Assert.Equal(CloudFoundryDefaults.ClientSecret, opts.ClientSecret);
Assert.Equal(new PathString("/signin-oidc"), opts.CallbackPath);
Assert.Equal(authURL + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl);
Assert.True(opts.ValidateCertificates);
}
[Fact]
public void Configure_WithServiceInfo_ReturnsExpected()
{
// arrange
string authURL = "http://domain";
var opts = new OpenIdConnectOptions();
SsoServiceInfo info = new SsoServiceInfo("foobar", "clientId", "secret", "http://domain");
// act
OpenIdConnectConfigurer.Configure(info, opts);
// assert
Assert.Equal(CloudFoundryDefaults.DisplayName, opts.AuthenticationType);
Assert.Equal("clientId", opts.ClientId);
Assert.Equal("secret", opts.ClientSecret);
Assert.Equal(new PathString(CloudFoundryDefaults.CallbackPath), opts.CallbackPath);
Assert.Equal(authURL + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl);
Assert.True(opts.ValidateCertificates);
}
}
}

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

@ -0,0 +1,76 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Owin.Security;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin.Test
{
public class OpenIdConnectOptionsTest
{
[Fact]
public void Options_Use_CloudFoundryDefaults()
{
// act
var options = new OpenIdConnectOptions();
// assert
Assert.Equal(CloudFoundryDefaults.DisplayName, options.AuthenticationType);
Assert.Equal(CloudFoundryDefaults.ClientId, options.ClientId);
Assert.Equal(CloudFoundryDefaults.ClientSecret, options.ClientSecret);
Assert.Equal("http://" + CloudFoundryDefaults.OAuthServiceUrl, options.AuthDomain);
Assert.Equal("http://" + CloudFoundryDefaults.OAuthServiceUrl + CloudFoundryDefaults.CheckTokenUri, options.TokenInfoUrl);
Assert.Equal(CloudFoundryDefaults.ClientId, options.ClientId);
Assert.Equal(CloudFoundryDefaults.ClientId, options.ClientId);
}
[Fact]
public void Options_Can_Change_AuthenticationType()
{
// arrange
var authType = "NotTheDefault";
// act
var options = new OpenIdConnectOptions(authType);
// assert
Assert.Equal(authType, options.AuthenticationType);
Assert.Equal(authType, options.Description.Caption);
Assert.Equal(AuthenticationMode.Passive, options.AuthenticationMode);
}
[Fact]
public void OpenIdOptions_CanProduce_AuthServerOptions()
{
// arrange
var options = new OpenIdConnectOptions
{
ClientId = "notDefault",
ClientSecret = "notDefault",
ValidateCertificates = false,
AdditionalScopes = "banana apple orange"
};
// act
var produce = options.AsAuthServerOptions();
// assert
Assert.Equal(produce.AuthorizationUrl, options.AuthDomain + CloudFoundryDefaults.AccessTokenUri);
Assert.Equal(produce.ClientId, options.ClientId);
Assert.Equal(produce.ClientSecret, options.ClientSecret);
Assert.Equal(produce.ValidateCertificates, options.ValidateCertificates);
Assert.Equal(produce.AdditionalTokenScopes, options.AdditionalScopes);
}
}
}

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

@ -0,0 +1,53 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Owin;
using Moq;
using System.Collections.Generic;
using System.IO;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin.Test
{
public static class OwinTestHelpers
{
public static readonly Dictionary<string, string> Appsettings = new Dictionary<string, string>()
{
};
public static IOwinContext CreateRequest(string method, string path, string scheme = "http", string host = "localhost", int? port = null, Stream bodyStream = null)
{
var context = new Mock<OwinContext>();
bodyStream = bodyStream ?? new MemoryStream();
context.Setup(r => r.Response).Returns(new OwinResponse { Body = bodyStream });
context.Setup(r => r.Request).Returns(new OwinRequest
{
Method = method,
Path = new PathString(path),
Scheme = scheme,
Host = new HostString(host + AddPortIfNotNull(port)),
});
return context.Object;
}
private static string AddPortIfNotNull(int? port)
{
if (port != null)
{
return ":" + port;
}
return string.Empty;
}
}
}

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

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreTestVersion)" />
<PackageReference Include="Microsoft.Owin.Testing" Version="$(OwinOAuthVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitStudioVersion)" />
<DotNetCliToolReference Include="dotnet-xunit" Version="$(XunitVersion)" />
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
<RootNamespace>Steeltoe.Security.Authentication.CloudFoundry.Owin.Test</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Steeltoe.Security.Authentication.CloudFoundryOwin\Steeltoe.Security.Authentication.CloudFoundryOwin.csproj" />
</ItemGroup>
</Project>

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

@ -0,0 +1,80 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.Owin;
using System.Net;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Owin.Test
{
public class UriUtilityTest
{
[Fact]
public void OriginalCallbackUri_Not_Changed()
{
// arrange
#pragma warning disable CS0618 // Type or member is obsolete
var options = new OpenIDConnectOptions();
#pragma warning restore CS0618 // Type or member is obsolete
var requestContext = OwinTestHelpers.CreateRequest("GET", string.Empty);
// act
var redirectUri = UriUtility.CalculateFullRedirectUri(options, requestContext.Request);
// assert
Assert.StartsWith(options.AuthDomain, redirectUri);
Assert.Contains("response_type=code", redirectUri);
Assert.Contains("scope=openid", redirectUri);
Assert.EndsWith("redirect_uri=" + WebUtility.UrlEncode("http://localhost/signin-oidc"), redirectUri);
}
[Fact]
public void Can_CalculateFullRedirectUri_WithDefaults()
{
// arrange
var options = new OpenIdConnectOptions();
var requestContext = OwinTestHelpers.CreateRequest("GET", string.Empty);
// act
var redirectUri = UriUtility.CalculateFullRedirectUri(options, requestContext.Request);
// assert
Assert.StartsWith(options.AuthDomain, redirectUri);
Assert.Contains("response_type=code", redirectUri);
Assert.Contains("scope=openid", redirectUri);
Assert.EndsWith("redirect_uri=" + WebUtility.UrlEncode("http://localhost" + CloudFoundryDefaults.CallbackPath), redirectUri);
}
[Fact]
public void Can_CalculateFullRedirectUri_WithNonDefaults()
{
// arrange
var options = new OpenIdConnectOptions
{
AuthDomain = "my_oauth_server",
CallbackPath = new PathString("/something_else")
};
var requestContext = OwinTestHelpers.CreateRequest("GET", string.Empty, "https", "some_server", 1234);
// act
var redirectUri = UriUtility.CalculateFullRedirectUri(options, requestContext.Request);
// assert
Assert.StartsWith(options.AuthDomain, redirectUri);
Assert.Contains("response_type=code", redirectUri);
Assert.Contains("scope=openid", redirectUri);
Assert.EndsWith("redirect_uri=" + WebUtility.UrlEncode("https://some_server:1234/something_else"), redirectUri);
}
}
}

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

@ -0,0 +1,79 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using RichardSzalay.MockHttp;
using System;
using System.Net;
using System.Net.Http;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class CloudFoundryClientTokenResolverTest
{
[Fact]
public void ClientTokenResolverRequiresOptions()
{
var exception = Assert.Throws<ArgumentNullException>(() => new CloudFoundryClientTokenResolver(null));
Assert.Equal("options", exception.ParamName);
}
[Fact]
public async void ClientTokenResolver_ReturnsAccessToken_OnSuccess()
{
// arrange
var options = new CloudFoundryOptions() { AccessTokenEndpoint = "/tokenUrl", AuthorizationUrl = "http://localhost", ClientId = "validId", ClientSecret = "clientSecret" };
var resolver = new CloudFoundryClientTokenResolver(options, GetMockHttpClient());
var expectedToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI3YTMzYzVhNjhjY2I0YjRiYmQ5N2I4MTRlZWExMTc3MiIsInN1YiI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsInNjb3BlIjpbInRlc3Rncm91cCIsIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJhenAiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImRhdmUiLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsImF1dGhfdGltZSI6MTU0NjY0MjkzMywicmV2X3NpZyI6ImE1ZWY2ODg5IiwiaWF0IjoxNTQ2NjQyOTM1LCJleHAiOjE1NDY2ODYxMzUsImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsImF1ZCI6WyJvcGVuaWQiLCJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiXX0.tGTXZzuuUSObTwdPHSx-zvnld20DH5hlOZlYp5DhjwkMIsZB0uIvVwbVDkPp7H_AmmeJoo6vqa5hbbgfgnYpTrKlCGOypnHoa3yRIKrwcDmLLujaMz6ApZeaJ7sJN-0N1UnPZ9iGcqvt9hNb_198zRnMXGH72oI0e2iGUBV1olCFVdZTnMGT7sUieDFKy7n0ghZYq_gUI8rfvTwiC3lfxv0nDXz4oE9Z-UKhK6q1zkAtQrz61FQ_CHONejz1JnuxQFKMMvm8JLcRkn6OL-EcSi1hkmFw0efO1OqccQacxphlafyHloVPQ3IOtzLjCf8sJ5NgTdCTC3iddT_sYovdrg";
// act
var token = await resolver.GetAccessToken();
// assert
Assert.Equal(expectedToken, token);
}
[Fact]
public async void ClientTokenResolver_Throws_OnRemoteFail()
{
// arrange
var options = new CloudFoundryOptions() { AccessTokenEndpoint = "/tokenUrl", AuthorizationUrl = "http://localhost", ClientId = "badId", ClientSecret = "clientSecret" };
var resolver = new CloudFoundryClientTokenResolver(options, GetMockHttpClient());
// act
var tokenError = await Assert.ThrowsAsync<Exception>(() => resolver.GetAccessToken());
// assert
Assert.Contains("OAuth token endpoint failure: ", tokenError.Message);
}
private readonly string realTokenResponse = "{'access_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI3YTMzYzVhNjhjY2I0YjRiYmQ5N2I4MTRlZWExMTc3MiIsInN1YiI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsInNjb3BlIjpbInRlc3Rncm91cCIsIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJhenAiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImRhdmUiLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsImF1dGhfdGltZSI6MTU0NjY0MjkzMywicmV2X3NpZyI6ImE1ZWY2ODg5IiwiaWF0IjoxNTQ2NjQyOTM1LCJleHAiOjE1NDY2ODYxMzUsImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsImF1ZCI6WyJvcGVuaWQiLCJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiXX0.tGTXZzuuUSObTwdPHSx-zvnld20DH5hlOZlYp5DhjwkMIsZB0uIvVwbVDkPp7H_AmmeJoo6vqa5hbbgfgnYpTrKlCGOypnHoa3yRIKrwcDmLLujaMz6ApZeaJ7sJN-0N1UnPZ9iGcqvt9hNb_198zRnMXGH72oI0e2iGUBV1olCFVdZTnMGT7sUieDFKy7n0ghZYq_gUI8rfvTwiC3lfxv0nDXz4oE9Z-UKhK6q1zkAtQrz61FQ_CHONejz1JnuxQFKMMvm8JLcRkn6OL-EcSi1hkmFw0efO1OqccQacxphlafyHloVPQ3IOtzLjCf8sJ5NgTdCTC3iddT_sYovdrg','token_type':'bearer','id_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJzdWIiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJhdWQiOlsiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il0sImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsImV4cCI6MTU0NjY4NjEzNSwiaWF0IjoxNTQ2NjQyOTM1LCJhbXIiOlsicHdkIl0sImF6cCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsIm9yaWdpbiI6InVhYSIsImp0aSI6IjdhMzNjNWE2OGNjYjRiNGJiZDk3YjgxNGVlYTExNzcyIiwicHJldmlvdXNfbG9nb25fdGltZSI6MTU0NjYzMzU1NjA0NCwiZW1haWxfdmVyaWZpZWQiOnRydWUsImNsaWVudF9pZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImNpZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX25hbWUiOiJkYXZlIiwicmV2X3NpZyI6ImE1ZWY2ODg5IiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsImF1dGhfdGltZSI6MTU0NjY0MjkzM30.KkTVOTg7Bhj1EWO63QmWzjnEAnKesoSLGfGL-2Y19PiK62KRd66dOcVcQEA_nWIE1mJQZsDByQYwcEuVRAiP-mXY0L2MrWUnRlW5yn1fqOc44iSDggMF5VfjGQok8fGfBPQX7va0evfaOaulRMuWsijYvzZtV-KncGUpxGwkzRs2AAEbkAv1_vAD2zSGJ-ji5L7s4a2-Qc_LxDlNANoYllzMTVxZ2DSvVPLfKPNGgSvNC7t053ExfGRXk-6cPxVznkngWDlYALeXsnrbvXdjuk1dw8dcXRhL4PUJDI7EVvTdqzd1fPYRgAQ3KJZOmvzBY7bxFtoq9odmKKHTI4CFUQ','refresh_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI4NjdiNTcxNTBlNmM0ZDQ3OTM0NmE3ZjgwMTJmMGY2My1yIiwic3ViIjoiOTViYmIzNDYtYjY4Yy00ZjE1LWIzNDEtNzBkNjBmOWY0NmJhIiwic2NvcGUiOlsidGVzdGdyb3VwIiwib3BlbmlkIl0sImlhdCI6MTU0NjY0MjkzNSwiZXhwIjoxNTQ5MjM0OTM1LCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJpc3MiOiJodHRwczovL3N0ZWVsdG9lLnVhYS5jZi5iZWV0LnNwcmluZ2FwcHMuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiIzYTNlYWRhZC01YjJmLTQ1MzAtYWY5NS1hNjliYzBhZmQxNWIiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9uYW1lIjoiZGF2ZSIsIm9yaWdpbiI6InVhYSIsInVzZXJfaWQiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJyZXZfc2lnIjoiYTVlZjY4ODkiLCJhdWQiOlsib3BlbmlkIiwiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il19.cOHCiZgmbtN1uyuvEou879du5XVvJqHEmkf6-2G0V9I1qYy0PJbGMHNL32d6L9LVsnXs4iTfi1qpKfdEGbfZbX8rNwNwcRoPndOZBepTTZHJPcU49VEF4t1hZ5icbt_4mSiQwpNy2n_mqwHizV8BAeOeQc_zUE-YFiuQC6V3ac0rTYO4NJGSioSG_y6HFp3refiPZVPT7En-dwhd-Yic1SB1OPxQ5bRNS7AAjGLeeCWOZQVxwQa5Atv9t791yUkH1zX8Psh_LRy2O8E6o7IBLI_yjz5N-YIHRa-BuMPDdKWarcOjy1KKJM27HynAQo1T4J0Ft8hSsIxFlObTd3-FVQ','expires_in':43199,'scope':'testgroup openid','jti':'7a33c5a68ccb4b4bbd97b814eea11772'}";
private HttpClient GetMockHttpClient()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.When("http://localhost/tokenUrl")
.WithFormData("client_id", "validId")
.Respond("application/json", realTokenResponse); // Respond with JSON
mockHttp
.When("http://localhost/tokenUrl")
.WithFormData("client_id", "badId")
.Respond(HttpStatusCode.BadRequest, new StringContent(string.Empty)); // Respond with JSON
return mockHttp.ToHttpClient();
}
}
}

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

@ -0,0 +1,49 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class CloudFoundryJwtTest
{
[Fact]
public void ClaimsReMapped_WhenPresent()
{
// arrange
var claims = new List<Claim>
{
new Claim("client_id", "clientId"),
new Claim("user_id", "nameId"),
new Claim("given_name", "First_Name"),
new Claim("family_name", "Last_Name"),
};
var jwt = new JwtSecurityToken(issuer: "uaa");
var identity = new ClaimsIdentity(claims);
// act
CloudFoundryJwt.OnTokenValidatedAddClaims(identity, jwt);
// assert
Assert.Contains(identity.Claims, c => c.Type == ClaimTypes.NameIdentifier && c.Value == "nameId");
Assert.Contains(identity.Claims, c => c.Type == ClaimTypes.GivenName && c.Value == "First_Name");
Assert.Contains(identity.Claims, c => c.Type == ClaimTypes.Surname && c.Value == "Last_Name");
Assert.DoesNotContain(identity.Claims, c => c.Type == ClaimTypes.Email);
Assert.Contains(identity.Claims, c => c.Type == ClaimTypes.Name && c.Value == "clientId");
}
}
}

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

@ -0,0 +1,69 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Steeltoe.CloudFoundry.Connector.Services;
using System;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class CloudFoundryOptionsConfigurerTest
{
[Fact]
public void Configure_NoOptions_Throws()
{
var exception = Assert.Throws<ArgumentNullException>(() => CloudFoundryOptionsConfigurer.Configure(null, null));
Assert.Equal("options", exception.ParamName);
}
[Fact]
public void Configure_NoServiceInfo_ReturnsDefaults()
{
// arrange
var opts = new CloudFoundryOptions();
string authURL = "http://" + CloudFoundryDefaults.OAuthServiceUrl;
// act
CloudFoundryOptionsConfigurer.Configure(null, opts);
// assert
Assert.Equal(authURL, opts.AuthorizationUrl);
Assert.Equal(CloudFoundryDefaults.ClientId, opts.ClientId);
Assert.Equal(CloudFoundryDefaults.ClientSecret, opts.ClientSecret);
Assert.Equal(authURL + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl);
Assert.True(opts.ValidateAudience);
Assert.True(opts.ValidateCertificates);
Assert.True(opts.ValidateIssuer);
Assert.True(opts.ValidateLifetime);
}
[Fact]
public void Configure_WithServiceInfo_ReturnsExpected()
{
// arrange
string authURL = "http://domain";
var opts = new CloudFoundryOptions();
SsoServiceInfo info = new SsoServiceInfo("foobar", "clientId", "secret", "http://domain");
// act
CloudFoundryOptionsConfigurer.Configure(info, opts);
// assert
Assert.Equal("clientId", opts.ClientId);
Assert.Equal("secret", opts.ClientSecret);
Assert.Equal(authURL + CloudFoundryDefaults.CheckTokenUri, opts.TokenInfoUrl);
Assert.True(opts.ValidateCertificates);
}
}
}

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

@ -0,0 +1,44 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class CloudFoundryOptionsTest
{
[Fact]
public void ParameterlessConstructor_StillReadsEnvVars()
{
// arrange
Environment.SetEnvironmentVariable("sso_auth_domain", "auth_domain");
Environment.SetEnvironmentVariable("sso_client_id", "ssoClientId");
Environment.SetEnvironmentVariable("sso_client_secret", "ssoClientSecret");
// act
var options = new CloudFoundryOptions();
// assert
Assert.Equal("auth_domain", options.AuthorizationUrl);
Assert.Equal("ssoClientId", options.ClientId);
Assert.Equal("ssoClientSecret", options.ClientSecret);
// reset the env
Environment.SetEnvironmentVariable("sso_auth_domain", string.Empty);
Environment.SetEnvironmentVariable("sso_client_id", string.Empty);
Environment.SetEnvironmentVariable("sso_client_secret", string.Empty);
}
}
}

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

@ -0,0 +1,162 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using RichardSzalay.MockHttp;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class CloudFoundryTokenKeyResolverTest
{
private readonly string tokenKeysJsonString = @"{'keys':[{'kty':'RSA','e':'AQAB','use':'sig','kid':'key-1','alg':'RS256','value':'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyt7z23ctJP3UStbx0b/\nLbLWbQfEfOAHh09x7BnFB7vLw4cngzy685LrQyi12FKHDx+0Ux3B4o5hvfHLmgml\npXrWUjf0G3aE77FfMhulmIS9avlNjSUErO7Tgq4/XYiasfWeiIfjhs4cQvOwZeF3\nSBlj5VdHakLertr2DAIigmlziCOkb4is67dfGLZkc8UeKTJmueW56jlT9hyCRjBM\nGbV9LNiaZ6vp+jwk2ugW0pIjMbyfMxoIExiMQmYwT0TP/n8cd89eaKqmO2HXiYL9\nyqqTMMCV6I2lXNxXCEu/cii7kj9Il4aLowzWHJ0Z4XPJsTufV8uZShYxV+gBSekM\nLwIDAQAB\n-----END PUBLIC KEY-----','n':'AMsre89t3LST91ErW8dG_y2y1m0HxHzgB4dPcewZxQe7y8OHJ4M8uvOS60MotdhShw8ftFMdweKOYb3xy5oJpaV61lI39Bt2hO-xXzIbpZiEvWr5TY0lBKzu04KuP12ImrH1noiH44bOHELzsGXhd0gZY-VXR2pC3q7a9gwCIoJpc4gjpG-IrOu3Xxi2ZHPFHikyZrnlueo5U_YcgkYwTBm1fSzYmmer6fo8JNroFtKSIzG8nzMaCBMYjEJmME9Ez_5_HHfPXmiqpjth14mC_cqqkzDAleiNpVzcVwhLv3Iou5I_SJeGi6MM1hydGeFzybE7n1fLmUoWMVfoAUnpDC8'}]}";
private readonly CloudFoundryOptions happyPathOptions = new CloudFoundryOptions() { AuthorizationUrl = "http://localhost" };
private readonly CloudFoundryOptions networkFailOptions = new CloudFoundryOptions() { AuthorizationUrl = "http://localhost:81" };
private readonly CloudFoundryOptions serviceUnavailableOptions = new CloudFoundryOptions() { AuthorizationUrl = "http://localhost:82" };
[Fact]
public void TokenKeyResolver_Requires_Options()
{
var exception = Assert.Throws<ArgumentNullException>(() => new CloudFoundryTokenKeyResolver(null));
}
[Fact]
public void ResolveSigningKey_ReturnsKey_FromServer()
{
// arrange a resolver that succeeds
var tkr = new CloudFoundryTokenKeyResolver(happyPathOptions, GetMockHttpClient());
// act
var expected = tkr.ResolveSigningKey(string.Empty, null, "key-1", happyPathOptions.TokenValidationParameters);
// assert
Assert.NotNull(expected);
var tokenKey = expected.First();
Assert.IsType<JsonWebKey>(tokenKey);
Assert.Equal("key-1", tokenKey.KeyId);
}
[Fact]
public void ResolveSigningKey_ReturnsKeyPreviouslyResolved()
{
// arrange a resolver that has previously retrieved keys, but will fail going forward
var tkr = new CloudFoundryTokenKeyResolver(happyPathOptions, GetMockHttpClient());
tkr.ResolveSigningKey(string.Empty, null, "key-1", happyPathOptions.TokenValidationParameters);
tkr.Options = networkFailOptions;
Assert.Equal(networkFailOptions.AuthorizationUrl, tkr.Options.AuthorizationUrl);
// act
var expected = tkr.ResolveSigningKey(string.Empty, null, "key-1", happyPathOptions.TokenValidationParameters);
// assert
Assert.NotNull(expected);
var tokenKey = expected.First();
Assert.IsType<JsonWebKey>(tokenKey);
Assert.Equal("key-1", tokenKey.KeyId);
}
[Fact]
public void ResolveSigningKey_ReturnsNull_WhenNoKeyFound()
{
// arrange
var tkr = new CloudFoundryTokenKeyResolver(serviceUnavailableOptions, GetMockHttpClient());
// act
var expected = tkr.ResolveSigningKey(string.Empty, null, "key-1", serviceUnavailableOptions.TokenValidationParameters);
// assert
Assert.Null(expected);
}
[Fact]
public async void FetchKeySet_Throws_OnHttpClientException()
{
// arrange
var tkr = new CloudFoundryTokenKeyResolver(networkFailOptions, GetMockHttpClient());
// act
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => tkr.FetchKeySet());
}
[Fact]
public async void FetchKeySet_ReturnsNull_OnFailure()
{
// arrange
var tkr = new CloudFoundryTokenKeyResolver(serviceUnavailableOptions, GetMockHttpClient());
// act
var expected = await tkr.FetchKeySet();
// assert
Assert.Null(expected);
}
[Fact]
public async void FetchKeySet_ReturnsKeySet_OnSuccess()
{
// arrange
var tkr = new CloudFoundryTokenKeyResolver(happyPathOptions, GetMockHttpClient());
// act
var expected = await tkr.FetchKeySet();
// assert
Assert.Contains(expected.Keys, key => key.Kid == "key-1");
var tokenKey = expected.Keys.First();
Assert.Equal("RS256", tokenKey.Alg);
Assert.Equal("sig", tokenKey.Use);
Assert.Equal("AQAB", tokenKey.E);
}
[Fact]
public void GetJsonWebKeySet_Parses_JsonString()
{
// arrange
var tkr = new CloudFoundryTokenKeyResolver(happyPathOptions);
// act
#pragma warning disable CS0618 // Type or member is obsolete
var expected = tkr.GetJsonWebKeySet(tokenKeysJsonString);
#pragma warning restore CS0618 // Type or member is obsolete
// assert
Assert.Contains(expected.Keys, key => key.Kid == "key-1");
var tokenKey = expected.Keys.First();
Assert.Equal("RS256", tokenKey.Alg);
Assert.Equal("sig", tokenKey.Use);
Assert.Equal("AQAB", tokenKey.E);
}
private HttpClient GetMockHttpClient()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.When("http://localhost:81" + CloudFoundryDefaults.JwtTokenUri)
.Throw(new HttpRequestException()); // emulate a DNS failure
mockHttp
.When("http://localhost:82" + CloudFoundryDefaults.JwtTokenUri)
.Respond(HttpStatusCode.ServiceUnavailable); // Respond unavailable
mockHttp
.When("http://localhost" + CloudFoundryDefaults.JwtTokenUri)
.Respond("application/json", tokenKeysJsonString); // Respond with JSON
return mockHttp.ToHttpClient();
}
}
}

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

@ -0,0 +1,191 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Microsoft.IdentityModel.Tokens;
using RichardSzalay.MockHttp;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.ServiceModel.Web;
using System.Text;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class JwtAuthorizationManagerTest
{
private readonly CloudFoundryOptions _options = new CloudFoundryOptions() { AuthorizationUrl = "http://localhost" };
private readonly string tokenKeysJsonString = @"{'keys':[{'kty':'RSA','e':'AQAB','use':'sig','kid':'key-1','alg':'RS256','value':'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyt7z23ctJP3UStbx0b/\nLbLWbQfEfOAHh09x7BnFB7vLw4cngzy685LrQyi12FKHDx+0Ux3B4o5hvfHLmgml\npXrWUjf0G3aE77FfMhulmIS9avlNjSUErO7Tgq4/XYiasfWeiIfjhs4cQvOwZeF3\nSBlj5VdHakLertr2DAIigmlziCOkb4is67dfGLZkc8UeKTJmueW56jlT9hyCRjBM\nGbV9LNiaZ6vp+jwk2ugW0pIjMbyfMxoIExiMQmYwT0TP/n8cd89eaKqmO2HXiYL9\nyqqTMMCV6I2lXNxXCEu/cii7kj9Il4aLowzWHJ0Z4XPJsTufV8uZShYxV+gBSekM\nLwIDAQAB\n-----END PUBLIC KEY-----','n':'AMsre89t3LST91ErW8dG_y2y1m0HxHzgB4dPcewZxQe7y8OHJ4M8uvOS60MotdhShw8ftFMdweKOYb3xy5oJpaV61lI39Bt2hO-xXzIbpZiEvWr5TY0lBKzu04KuP12ImrH1noiH44bOHELzsGXhd0gZY-VXR2pC3q7a9gwCIoJpc4gjpG-IrOu3Xxi2ZHPFHikyZrnlueo5U_YcgkYwTBm1fSzYmmer6fo8JNroFtKSIzG8nzMaCBMYjEJmME9Ez_5_HHfPXmiqpjth14mC_cqqkzDAleiNpVzcVwhLv3Iou5I_SJeGi6MM1hydGeFzybE7n1fLmUoWMVfoAUnpDC8'},{'kty':'RSA','e':'AQAB','use':'sig','kid':'key-2','alg':'RS256','value':'some super simple key that just happens to have enough characters','n':'someNvalue'}]}";
[Fact]
public void JwtAuthorizationManager_Requires_Options()
{
// arrange
var manager = new JwtAuthorizationManager();
// act
var exception = Assert.Throws<WebFaultException<string>>(() => manager.GetPrincipalFromRequestHeaders(null));
// assert
Assert.Equal("SSO Configuration is missing", exception.Detail);
}
[Fact]
public void JwtAuthorizationManager_Requires_AuthorizationHeader()
{
// arrange
var manager = new JwtAuthorizationManager(_options);
var headers = new WebHeaderCollection();
// act
var exception = Assert.Throws<WebFaultException<string>>(() => manager.GetPrincipalFromRequestHeaders(headers));
// assert
Assert.Equal("No Authorization header", exception.Detail);
}
[Fact]
public void JwtAuthorizationManager_Requires_BearerTokenFormat()
{
// arrange
var manager = new JwtAuthorizationManager(_options);
var headers = new WebHeaderCollection
{
{ HttpRequestHeader.Authorization, "bear" }
};
// act
var exception = Assert.Throws<WebFaultException<string>>(() => manager.GetPrincipalFromRequestHeaders(headers));
// assert
Assert.Equal("Wrong Token Format", exception.Detail);
}
[Fact]
public void JwtAuthorizationManager_Requires_An_Actual_Token()
{
// arrange
var manager = new JwtAuthorizationManager(_options);
var headers = new WebHeaderCollection
{
{ HttpRequestHeader.Authorization, "Bearer " }
};
// act
var exception = Assert.Throws<WebFaultException<string>>(() => manager.GetPrincipalFromRequestHeaders(headers));
// assert
Assert.Equal("No Token", exception.Detail);
}
[Fact]
public void JwtAuthorizationManager_ThrowsInvalidToken()
{
// arrange
_options.TokenKeyResolver = null;
_options.TokenKeyResolver = new CloudFoundry.CloudFoundryTokenKeyResolver(_options.AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri, GetMockHttpMessageHandler(), false);
_options.TokenValidator.Options = _options;
_options.TokenValidationParameters = null;
_options.TokenValidationParameters = _options.GetTokenValidationParameters();
var manager = new JwtAuthorizationManager(_options);
var headers = new WebHeaderCollection
{
{ HttpRequestHeader.Authorization, "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI3YTMzYzVhNjhjY2I0YjRiYmQ5N2I4MTRlZWExMTc3MiIsInN1YiI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsInNjb3BlIjpbInRlc3Rncm91cCIsIm9wZW5pZCJdLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJhenAiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImRhdmUiLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsImF1dGhfdGltZSI6MTU0NjY0MjkzMywicmV2X3NpZyI6ImE1ZWY2ODg5IiwiaWF0IjoxNTQ2NjQyOTM1LCJleHAiOjE1NDY2ODYxMzUsImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsImF1ZCI6WyJvcGVuaWQiLCJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiXX0.tGTXZzuuUSObTwdPHSx-zvnld20DH5hlOZlYp5DhjwkMIsZB0uIvVwbVDkPp7H_AmmeJoo6vqa5hbbgfgnYpTrKlCGOypnHoa3yRIKrwcDmLLujaMz6ApZeaJ7sJN-0N1UnPZ9iGcqvt9hNb_198zRnMXGH72oI0e2iGUBV1olCFVdZTnMGT7sUieDFKy7n0ghZYq_gUI8rfvTwiC3lfxv0nDXz4oE9Z-UKhK6q1zkAtQrz61FQ_CHONejz1JnuxQFKMMvm8JLcRkn6OL-EcSi1hkmFw0efO1OqccQacxphlafyHloVPQ3IOtzLjCf8sJ5NgTdCTC3iddT_sYovdrg" }
};
// act
var exception = Assert.Throws<WebFaultException<string>>(() => manager.GetPrincipalFromRequestHeaders(headers));
// assert
Assert.StartsWith("IDX10223: Lifetime validation failed", exception.Detail);
}
[Fact(Skip = "fails at signature validation")]
public void JwtAuthorizationManager_ReturnsPrincipalFromToken()
{
// arrange
_options.TokenKeyResolver = null;
_options.TokenKeyResolver = new CloudFoundry.CloudFoundryTokenKeyResolver(_options.AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri, GetMockHttpMessageHandler(), false);
_options.TokenValidator.Options = _options;
_options.TokenValidationParameters = null;
_options.TokenValidationParameters = _options.GetTokenValidationParameters();
var manager = new JwtAuthorizationManager(_options);
var headers = new WebHeaderCollection
{
{ HttpRequestHeader.Authorization, $"Bearer {CreateJwt()}" }
};
// act
var principal = manager.GetPrincipalFromRequestHeaders(headers);
// assert
Assert.NotNull(principal);
Assert.Equal("dave", principal.Identity.Name);
}
private HttpMessageHandler GetMockHttpMessageHandler()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.When(HttpMethod.Get, _options.AuthorizationUrl + CloudFoundryDefaults.JwtTokenUri)
.Respond("application/json", tokenKeysJsonString); // Respond with JSON
return mockHttp;
}
private string CreateJwt()
{
var signingKey = "some super simple key that just happens to have enough characters";
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey));
var header = new JwtHeader(new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature))
{
{ "kid", "key-2" }
};
var payload = new JwtPayload("uaa", "tests", new List<Claim> { new Claim("scope", "openid") }, null, DateTime.Now.AddMinutes(5));
var secToken = new JwtSecurityToken(header, payload);
var handler = new JwtSecurityTokenHandler();
var token = handler.WriteToken(secToken);
return token;
// {
// { "jti": "7a33c5a68ccb4b4bbd97b814eea11772" },
// { "sub": "95bbb346-b68c-4f15-b341-70d60f9f46ba"},
// "scope": [ "testgroup", "openid" ],
// "client_id": "c920b4f5-487c-4dd0-a63d-4d40c1331986",
// "cid": "c920b4f5-487c-4dd0-a63d-4d40c1331986",
// "azp": "c920b4f5-487c-4dd0-a63d-4d40c1331986",
// "grant_type": "authorization_code",
// "user_id": "95bbb346-b68c-4f15-b341-70d60f9f46ba",
// "origin": "uaa",
// "user_name": "dave",
// "email": "dave@testcloud.com",
// "auth_time": 1546642933,
// "rev_sig": "a5ef6889",
// "iat": 1546642935,
// "exp": 1546686135,
// "iss": "https://steeltoe.uaa.cf.beet.springapps.io/oauth/token",
// "zid": "3a3eadad-5b2f-4530-af95-a69bc0afd15b",
// "aud": [
// "openid",
// "c920b4f5-487c-4dd0-a63d-4d40c1331986"
// ]
// }
// }
}
}
}

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

@ -0,0 +1,84 @@
// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Moq;
using RichardSzalay.MockHttp;
using System.Linq;
using System.Net.Http;
using System.ServiceModel.Channels;
using Xunit;
namespace Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test
{
public class JwtHeaderMessageInspectorTest
{
[Fact]
public void MessageInspector_AttachesUserToken()
{
// arrange
var options = new CloudFoundryOptions() { AuthorizationUrl = "http://localhost", ForwardUserCredentials = true };
var inspector = new JwtHeaderMessageInspector(options, "someToken");
var properties = new MessageProperties { { HttpRequestMessageProperty.Name, new HttpRequestMessageProperty() } };
var message = new Mock<Message>();
message.Setup(p => p.Properties).Returns(() => properties);
var mo = message.Object;
// act
inspector.BeforeSendRequest(ref mo, null);
HttpRequestMessageProperty httpRequestMessage;
mo.Properties.TryGetValue(HttpRequestMessageProperty.Name, out object httpRequestMessageObject);
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
// assert
Assert.True(httpRequestMessage.Headers.AllKeys.Any());
Assert.Equal("Bearer someToken", httpRequestMessage.Headers["Authorization"]);
}
[Fact]
public void MessageInspector_GetsAndAttachesOwnToken()
{
// arrange
var options = new CloudFoundryOptions() { AccessTokenEndpoint = "/tokenUrl", AuthorizationUrl = "http://localhost", ClientId = "validId", ClientSecret = "validSecret" };
var inspector = new JwtHeaderMessageInspector(options, null, GetMockHttpClient());
var properties = new MessageProperties { { HttpRequestMessageProperty.Name, new HttpRequestMessageProperty() } };
var message = new Mock<Message>();
message.Setup(p => p.Properties).Returns(() => properties);
var mo = message.Object;
// act
inspector.BeforeSendRequest(ref mo, null);
HttpRequestMessageProperty httpRequestMessage;
mo.Properties.TryGetValue(HttpRequestMessageProperty.Name, out object httpRequestMessageObject);
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
// assert
Assert.True(httpRequestMessage.Headers.AllKeys.Any());
Assert.Equal("Bearer someClientCredentialsToken", httpRequestMessage.Headers["Authorization"]);
}
private readonly string realTokenResponse = "{'access_token':'someClientCredentialsToken','token_type':'bearer','id_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJzdWIiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJhdWQiOlsiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il0sImlzcyI6Imh0dHBzOi8vc3RlZWx0b2UudWFhLmNmLmJlZXQuc3ByaW5nYXBwcy5pby9vYXV0aC90b2tlbiIsImV4cCI6MTU0NjY4NjEzNSwiaWF0IjoxNTQ2NjQyOTM1LCJhbXIiOlsicHdkIl0sImF6cCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsInNjb3BlIjpbIm9wZW5pZCJdLCJlbWFpbCI6ImRhdmVAdGVzdGNsb3VkLmNvbSIsInppZCI6IjNhM2VhZGFkLTViMmYtNDUzMC1hZjk1LWE2OWJjMGFmZDE1YiIsIm9yaWdpbiI6InVhYSIsImp0aSI6IjdhMzNjNWE2OGNjYjRiNGJiZDk3YjgxNGVlYTExNzcyIiwicHJldmlvdXNfbG9nb25fdGltZSI6MTU0NjYzMzU1NjA0NCwiZW1haWxfdmVyaWZpZWQiOnRydWUsImNsaWVudF9pZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImNpZCI6ImM5MjBiNGY1LTQ4N2MtNGRkMC1hNjNkLTRkNDBjMTMzMTk4NiIsImdyYW50X3R5cGUiOiJhdXRob3JpemF0aW9uX2NvZGUiLCJ1c2VyX25hbWUiOiJkYXZlIiwicmV2X3NpZyI6ImE1ZWY2ODg5IiwidXNlcl9pZCI6Ijk1YmJiMzQ2LWI2OGMtNGYxNS1iMzQxLTcwZDYwZjlmNDZiYSIsImF1dGhfdGltZSI6MTU0NjY0MjkzM30.KkTVOTg7Bhj1EWO63QmWzjnEAnKesoSLGfGL-2Y19PiK62KRd66dOcVcQEA_nWIE1mJQZsDByQYwcEuVRAiP-mXY0L2MrWUnRlW5yn1fqOc44iSDggMF5VfjGQok8fGfBPQX7va0evfaOaulRMuWsijYvzZtV-KncGUpxGwkzRs2AAEbkAv1_vAD2zSGJ-ji5L7s4a2-Qc_LxDlNANoYllzMTVxZ2DSvVPLfKPNGgSvNC7t053ExfGRXk-6cPxVznkngWDlYALeXsnrbvXdjuk1dw8dcXRhL4PUJDI7EVvTdqzd1fPYRgAQ3KJZOmvzBY7bxFtoq9odmKKHTI4CFUQ','refresh_token':'eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI4NjdiNTcxNTBlNmM0ZDQ3OTM0NmE3ZjgwMTJmMGY2My1yIiwic3ViIjoiOTViYmIzNDYtYjY4Yy00ZjE1LWIzNDEtNzBkNjBmOWY0NmJhIiwic2NvcGUiOlsidGVzdGdyb3VwIiwib3BlbmlkIl0sImlhdCI6MTU0NjY0MjkzNSwiZXhwIjoxNTQ5MjM0OTM1LCJjaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJjbGllbnRfaWQiOiJjOTIwYjRmNS00ODdjLTRkZDAtYTYzZC00ZDQwYzEzMzE5ODYiLCJpc3MiOiJodHRwczovL3N0ZWVsdG9lLnVhYS5jZi5iZWV0LnNwcmluZ2FwcHMuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiIzYTNlYWRhZC01YjJmLTQ1MzAtYWY5NS1hNjliYzBhZmQxNWIiLCJncmFudF90eXBlIjoiYXV0aG9yaXphdGlvbl9jb2RlIiwidXNlcl9uYW1lIjoiZGF2ZSIsIm9yaWdpbiI6InVhYSIsInVzZXJfaWQiOiI5NWJiYjM0Ni1iNjhjLTRmMTUtYjM0MS03MGQ2MGY5ZjQ2YmEiLCJyZXZfc2lnIjoiYTVlZjY4ODkiLCJhdWQiOlsib3BlbmlkIiwiYzkyMGI0ZjUtNDg3Yy00ZGQwLWE2M2QtNGQ0MGMxMzMxOTg2Il19.cOHCiZgmbtN1uyuvEou879du5XVvJqHEmkf6-2G0V9I1qYy0PJbGMHNL32d6L9LVsnXs4iTfi1qpKfdEGbfZbX8rNwNwcRoPndOZBepTTZHJPcU49VEF4t1hZ5icbt_4mSiQwpNy2n_mqwHizV8BAeOeQc_zUE-YFiuQC6V3ac0rTYO4NJGSioSG_y6HFp3refiPZVPT7En-dwhd-Yic1SB1OPxQ5bRNS7AAjGLeeCWOZQVxwQa5Atv9t791yUkH1zX8Psh_LRy2O8E6o7IBLI_yjz5N-YIHRa-BuMPDdKWarcOjy1KKJM27HynAQo1T4J0Ft8hSsIxFlObTd3-FVQ','expires_in':43199,'scope':'testgroup openid','jti':'7a33c5a68ccb4b4bbd97b814eea11772'}";
private HttpClient GetMockHttpClient()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp
.When("http://localhost/tokenUrl")
.WithFormData("client_id", "validId")
.Respond("application/json", realTokenResponse); // Respond with JSON
return mockHttp.ToHttpClient();
}
}
}

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

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreTestVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="RichardSzalay.MockHttp" Version="$(MockHttp)" />
<PackageReference Include="StyleCop.Analyzers" Version="$(StyleCopVersion)">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitStudioVersion)" />
<DotNetCliToolReference Include="dotnet-xunit" Version="$(XunitVersion)" />
</ItemGroup>
<PropertyGroup>
<NoWarn>SA1101;SA1124;SA1201;SA1309;SA1310;SA1401;SA1600;SA1652;1591</NoWarn>
<RootNamespace>Steeltoe.Security.Authentication.CloudFoundry.Wcf.Test</RootNamespace>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Steeltoe.Security.Authentication.CloudFoundryWcf\Steeltoe.Security.Authentication.CloudFoundryWcf.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceModel.Web" />
</ItemGroup>
</Project>

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

@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="RichardSzalay.MockHttp" Version="$(MockHttp)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersVersion)" />

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\versions.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>