mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-23 00:32:50 +08:00
hacky auth working
This commit is contained in:
parent
bd07af6926
commit
112a9c303e
46
README.md
46
README.md
@ -4,6 +4,8 @@
|
||||
|
||||
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
[ Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
|
||||
|
||||
Attempt at a .NET Api Gateway
|
||||
|
||||
This project is aimed at people using .NET running
|
||||
@ -101,15 +103,14 @@ Currently this is the only way to get configuration into Ocelot.
|
||||
};
|
||||
|
||||
services.AddOcelotOutputCaching(settings);
|
||||
services.AddOcelotFileConfiguration(Configuration);
|
||||
services.AddOcelot();
|
||||
services.AddOcelot(Configuration);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
|
||||
app.UseOcelot();
|
||||
await app.UseOcelot();
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,6 +387,43 @@ In orde to use caching on a route in your ReRoute configuration add this setting
|
||||
|
||||
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
||||
|
||||
## Administration
|
||||
|
||||
Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated
|
||||
using bearer tokens that you request from iteself. This support is provided by the amazing IdentityServer
|
||||
project that I have been using for a few years now. Check them out.
|
||||
|
||||
In order to enable the administration section you need to do a few things. First of all add this to your
|
||||
initial configuration.json. The value can be anything you want and it is obviously reccomended don't use
|
||||
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
||||
MapWhen functionality of asp.net core and all requests to root/administration will be sent there not
|
||||
to the Ocelot middleware.
|
||||
|
||||
"GlobalConfiguration": {
|
||||
"AdministrationPath": "/administration"
|
||||
}
|
||||
|
||||
This will get the admin area set up but not the authentication. You need to set 3 environmental variables.
|
||||
|
||||
OCELOT_USERNAME
|
||||
OCELOT_HASH
|
||||
OCELOT_SALT
|
||||
|
||||
These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to
|
||||
use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to
|
||||
supply username and password.
|
||||
|
||||
In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() this technique is based on MS doc I found online TODO find and link...
|
||||
|
||||
OK next thing is to get this config into Ocelot...
|
||||
|
||||
|
||||
At the moment Ocelot supports really limited options in terms of users and authentication for the admin API. At
|
||||
least your stuff needs to be hashed!
|
||||
|
||||
|
||||
|
||||
|
||||
## Ocelot Middleware injection and overrides
|
||||
|
||||
Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware
|
||||
|
@ -1 +1 @@
|
||||
{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","UpstreamHttpMethod":"get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration"}}
|
||||
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}}
|
22
src/Ocelot/Configuration/Authentication/HashMatcher.cs
Normal file
22
src/Ocelot/Configuration/Authentication/HashMatcher.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace Ocelot.Configuration.Authentication
|
||||
{
|
||||
public class HashMatcher : IHashMatcher
|
||||
{
|
||||
public bool Match(string password, string salt, string hash)
|
||||
{
|
||||
byte[] s = Convert.FromBase64String(salt);
|
||||
|
||||
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||
password: password,
|
||||
salt: s,
|
||||
prf: KeyDerivationPrf.HMACSHA256,
|
||||
iterationCount: 10000,
|
||||
numBytesRequested: 256 / 8));
|
||||
|
||||
return hashed == hash;
|
||||
}
|
||||
}
|
||||
}
|
7
src/Ocelot/Configuration/Authentication/IHashMatcher.cs
Normal file
7
src/Ocelot/Configuration/Authentication/IHashMatcher.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ocelot.Configuration.Authentication
|
||||
{
|
||||
public interface IHashMatcher
|
||||
{
|
||||
bool Match(string password, string salt, string hash);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Validation;
|
||||
using Ocelot.Configuration.Provider;
|
||||
|
||||
namespace Ocelot.Configuration.Authentication
|
||||
{
|
||||
public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
|
||||
{
|
||||
private readonly IHashMatcher _matcher;
|
||||
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||
|
||||
public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
_identityServerConfiguration = identityServerConfiguration;
|
||||
_matcher = matcher;
|
||||
}
|
||||
|
||||
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName);
|
||||
|
||||
if(user == null)
|
||||
{
|
||||
context.Result = new GrantValidationResult(
|
||||
TokenRequestErrors.InvalidGrant,
|
||||
"invalid custom credential");
|
||||
}
|
||||
else if(_matcher.Match(context.Password, user.Salt, user.Hash))
|
||||
{
|
||||
context.Result = new GrantValidationResult(
|
||||
subject: "admin",
|
||||
authenticationMethod: "custom");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new GrantValidationResult(
|
||||
TokenRequestErrors.InvalidGrant,
|
||||
"invalid custom credential");
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using IdentityServer4.Models;
|
||||
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public interface IIdentityServerConfiguration
|
||||
{
|
||||
string ApiName { get; }
|
||||
bool RequireHttps { get; }
|
||||
List<string> AllowedScopes { get; }
|
||||
SupportedTokens SupportedTokens { get; }
|
||||
string ApiSecret { get; }
|
||||
string Description {get;}
|
||||
bool Enabled {get;}
|
||||
IEnumerable<string> AllowedGrantTypes {get;}
|
||||
AccessTokenType AccessTokenType {get;}
|
||||
bool RequireClientSecret {get;}
|
||||
List<User> Users {get;}
|
||||
}
|
||||
}
|
@ -1,49 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Test;
|
||||
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public class HardCodedIdentityServerConfigurationProvider : IIdentityServerConfigurationProvider
|
||||
{
|
||||
public IdentityServerConfiguration Get()
|
||||
{
|
||||
var url = "";
|
||||
return new IdentityServerConfiguration(
|
||||
url,
|
||||
"admin",
|
||||
false,
|
||||
SupportedTokens.Both,
|
||||
"secret",
|
||||
new List<string> {"admin", "openid", "offline_access"},
|
||||
"Ocelot Administration",
|
||||
true,
|
||||
GrantTypes.ResourceOwnerPassword,
|
||||
AccessTokenType.Jwt,
|
||||
false,
|
||||
new List<TestUser> {
|
||||
new TestUser
|
||||
{
|
||||
Username = "admin",
|
||||
Password = "admin",
|
||||
SubjectId = "admin",
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IIdentityServerConfigurationProvider
|
||||
{
|
||||
IdentityServerConfiguration Get();
|
||||
}
|
||||
|
||||
public class IdentityServerConfiguration
|
||||
public class IdentityServerConfiguration : IIdentityServerConfiguration
|
||||
{
|
||||
public IdentityServerConfiguration(
|
||||
string identityServerUrl,
|
||||
string apiName,
|
||||
bool requireHttps,
|
||||
SupportedTokens supportedTokens,
|
||||
@ -54,9 +17,8 @@ namespace Ocelot.Configuration.Provider
|
||||
IEnumerable<string> grantType,
|
||||
AccessTokenType accessTokenType,
|
||||
bool requireClientSecret,
|
||||
List<TestUser> users)
|
||||
List<User> users)
|
||||
{
|
||||
IdentityServerUrl = identityServerUrl;
|
||||
ApiName = apiName;
|
||||
RequireHttps = requireHttps;
|
||||
SupportedTokens = supportedTokens;
|
||||
@ -70,7 +32,6 @@ namespace Ocelot.Configuration.Provider
|
||||
Users = users;
|
||||
}
|
||||
|
||||
public string IdentityServerUrl { get; private set; }
|
||||
public string ApiName { get; private set; }
|
||||
public bool RequireHttps { get; private set; }
|
||||
public List<string> AllowedScopes { get; private set; }
|
||||
@ -80,7 +41,7 @@ namespace Ocelot.Configuration.Provider
|
||||
public bool Enabled {get;private set;}
|
||||
public IEnumerable<string> AllowedGrantTypes {get;private set;}
|
||||
public AccessTokenType AccessTokenType {get;private set;}
|
||||
public bool RequireClientSecret = false;
|
||||
public List<TestUser> Users {get;private set;}
|
||||
public bool RequireClientSecret {get;private set;}
|
||||
public List<User> Users {get;private set;}
|
||||
}
|
||||
}
|
17
src/Ocelot/Configuration/Provider/User.cs
Normal file
17
src/Ocelot/Configuration/Provider/User.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Ocelot.Configuration.Provider
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public User(string subject, string userName, string hash, string salt)
|
||||
{
|
||||
Subject = subject;
|
||||
UserName = userName;
|
||||
Hash = hash;
|
||||
Salt = salt;
|
||||
}
|
||||
public string Subject { get; private set; }
|
||||
public string UserName { get; private set; }
|
||||
public string Hash { get; private set; }
|
||||
public string Salt { get; private set; }
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ using Ocelot.Authentication.Handler.Factory;
|
||||
using Ocelot.Authorisation;
|
||||
using Ocelot.Cache;
|
||||
using Ocelot.Claims;
|
||||
using Ocelot.Configuration.Authentication;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Parser;
|
||||
@ -41,7 +42,6 @@ namespace Ocelot.DependencyInjection
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
|
||||
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
|
||||
{
|
||||
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
|
||||
@ -51,24 +51,23 @@ namespace Ocelot.DependencyInjection
|
||||
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
||||
|
||||
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
||||
{
|
||||
return AddOcelot(services, configurationRoot, null);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, IIdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
services.Configure<FileConfiguration>(configurationRoot);
|
||||
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
||||
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
||||
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddOcelot(this IServiceCollection services)
|
||||
{
|
||||
return AddOcelot(services, null);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddOcelot(this IServiceCollection services, IdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
if(identityServerConfiguration != null)
|
||||
{
|
||||
services.AddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||
services.AddSingleton<IHashMatcher, HashMatcher>();
|
||||
services.AddIdentityServer()
|
||||
.AddTemporarySigningCredential()
|
||||
.AddInMemoryApiResources(new List<ApiResource>
|
||||
@ -101,8 +100,7 @@ namespace Ocelot.DependencyInjection
|
||||
Enabled = identityServerConfiguration.Enabled,
|
||||
RequireClientSecret = identityServerConfiguration.RequireClientSecret
|
||||
}
|
||||
})
|
||||
.AddTestUsers(identityServerConfiguration.Users);
|
||||
}).AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
|
||||
}
|
||||
|
||||
services.AddMvcCore()
|
||||
|
@ -37,21 +37,7 @@ namespace Ocelot.Middleware
|
||||
/// <returns></returns>
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
||||
{
|
||||
await builder.UseOcelot(new OcelotMiddlewareConfiguration(), null);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder,IdentityServerConfiguration identityServerConfiguration)
|
||||
{
|
||||
await builder.UseOcelot(new OcelotMiddlewareConfiguration(), identityServerConfiguration);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder,OcelotMiddlewareConfiguration middlewareConfiguration)
|
||||
{
|
||||
await builder.UseOcelot(middlewareConfiguration, null);
|
||||
await builder.UseOcelot(new OcelotMiddlewareConfiguration());
|
||||
|
||||
return builder;
|
||||
}
|
||||
@ -62,9 +48,9 @@ namespace Ocelot.Middleware
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="middlewareConfiguration"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration, IdentityServerConfiguration identityServerConfiguration)
|
||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
|
||||
{
|
||||
await CreateAdministrationArea(builder, identityServerConfiguration);
|
||||
await CreateAdministrationArea(builder);
|
||||
|
||||
// This is registered to catch any global exceptions that are not handled
|
||||
builder.UseExceptionHandlerMiddleware();
|
||||
@ -168,10 +154,12 @@ namespace Ocelot.Middleware
|
||||
return ocelotConfiguration.Data;
|
||||
}
|
||||
|
||||
private static async Task CreateAdministrationArea(IApplicationBuilder builder, IdentityServerConfiguration identityServerConfiguration)
|
||||
private static async Task CreateAdministrationArea(IApplicationBuilder builder)
|
||||
{
|
||||
var configuration = await CreateConfiguration(builder);
|
||||
|
||||
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
|
||||
|
||||
if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null)
|
||||
{
|
||||
var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder));
|
||||
|
@ -29,7 +29,8 @@
|
||||
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
|
||||
"Consul": "0.7.2.1",
|
||||
"Polly": "5.0.3",
|
||||
"IdentityServer4": "1.0.1"
|
||||
"IdentityServer4": "1.0.1",
|
||||
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
|
||||
},
|
||||
"runtimes": {
|
||||
"win10-x64": {},
|
||||
|
@ -139,8 +139,7 @@ namespace Ocelot.AcceptanceTests
|
||||
};
|
||||
|
||||
s.AddOcelotOutputCaching(settings);
|
||||
s.AddOcelotFileConfiguration(configuration);
|
||||
s.AddOcelot();
|
||||
s.AddOcelot(configuration);
|
||||
})
|
||||
.ConfigureLogging(l =>
|
||||
{
|
||||
|
@ -235,7 +235,7 @@ namespace Ocelot.IntegrationTests
|
||||
new KeyValuePair<string, string>("client_secret", "secret"),
|
||||
new KeyValuePair<string, string>("scope", "admin"),
|
||||
new KeyValuePair<string, string>("username", "admin"),
|
||||
new KeyValuePair<string, string>("password", "admin"),
|
||||
new KeyValuePair<string, string>("password", "secret"),
|
||||
new KeyValuePair<string, string>("grant_type", "password")
|
||||
};
|
||||
var content = new FormUrlEncodedContent(formData);
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CacheManager.Core;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using IdentityServer4.Models;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -15,7 +18,7 @@ namespace Ocelot.ManualTest
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private IdentityServerConfiguration _identityServerConfig;
|
||||
private IIdentityServerConfiguration _identityServerConfig;
|
||||
|
||||
public Startup(IHostingEnvironment env)
|
||||
{
|
||||
@ -27,9 +30,6 @@ namespace Ocelot.ManualTest
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
Configuration = builder.Build();
|
||||
|
||||
var identityServerConfigProvider = new HardCodedIdentityServerConfigurationProvider();
|
||||
_identityServerConfig = identityServerConfigProvider.Get();
|
||||
}
|
||||
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
@ -46,15 +46,36 @@ namespace Ocelot.ManualTest
|
||||
};
|
||||
|
||||
services.AddOcelotOutputCaching(settings);
|
||||
services.AddOcelotFileConfiguration(Configuration);
|
||||
services.AddOcelot(_identityServerConfig);
|
||||
|
||||
var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME");
|
||||
var hash = Environment.GetEnvironmentVariable("OCELOT_HASH");
|
||||
var salt = Environment.GetEnvironmentVariable("OCELOT_SALT");
|
||||
|
||||
_identityServerConfig = new IdentityServerConfiguration(
|
||||
"admin",
|
||||
false,
|
||||
SupportedTokens.Both,
|
||||
"secret",
|
||||
new List<string> {"admin", "openid", "offline_access"},
|
||||
"Ocelot Administration",
|
||||
true,
|
||||
GrantTypes.ResourceOwnerPassword,
|
||||
AccessTokenType.Jwt,
|
||||
false,
|
||||
new List<User>
|
||||
{
|
||||
new User("admin", username, hash, salt)
|
||||
}
|
||||
);
|
||||
|
||||
services.AddOcelot(Configuration, _identityServerConfig);
|
||||
}
|
||||
|
||||
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
|
||||
await app.UseOcelot(_identityServerConfig);
|
||||
await app.UseOcelot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
|
||||
"Microsoft.NETCore.App": "1.1.0",
|
||||
"Consul": "0.7.2.1",
|
||||
"Polly": "5.0.3"
|
||||
"Polly": "5.0.3",
|
||||
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
|
||||
},
|
||||
|
||||
"tools": {
|
||||
|
33
test/Ocelot.UnitTests/Configuration/HashCreationTests.cs
Normal file
33
test/Ocelot.UnitTests/Configuration/HashCreationTests.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class HashCreationTests
|
||||
{
|
||||
[Fact]
|
||||
public void should_create_hash_and_salt()
|
||||
{
|
||||
var password = "secret";
|
||||
|
||||
var salt = new byte[128 / 8];
|
||||
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
|
||||
var storedSalt = Convert.ToBase64String(salt);
|
||||
|
||||
var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||
password: password,
|
||||
salt: salt,
|
||||
prf: KeyDerivationPrf.HMACSHA256,
|
||||
iterationCount: 10000,
|
||||
numBytesRequested: 256 / 8));
|
||||
}
|
||||
}
|
||||
}
|
76
test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs
Normal file
76
test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using Ocelot.Configuration.Authentication;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class HashMatcherTests
|
||||
{
|
||||
private string _password;
|
||||
private string _hash;
|
||||
private string _salt;
|
||||
private bool _result;
|
||||
private HashMatcher _hashMatcher;
|
||||
|
||||
public HashMatcherTests()
|
||||
{
|
||||
_hashMatcher = new HashMatcher();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_match_hash()
|
||||
{
|
||||
var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=";
|
||||
var salt = "zzWITpnDximUNKYLiUam/w==";
|
||||
var password = "secret";
|
||||
|
||||
this.Given(x => GivenThePassword(password))
|
||||
.And(x => GivenTheHash(hash))
|
||||
.And(x => GivenTheSalt(salt))
|
||||
.When(x => WhenIMatch())
|
||||
.Then(x => ThenTheResultIs(true))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_not_match_hash()
|
||||
{
|
||||
var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=";
|
||||
var salt = "zzWITpnDximUNKYLiUam/w==";
|
||||
var password = "secret1";
|
||||
|
||||
this.Given(x => GivenThePassword(password))
|
||||
.And(x => GivenTheHash(hash))
|
||||
.And(x => GivenTheSalt(salt))
|
||||
.When(x => WhenIMatch())
|
||||
.Then(x => ThenTheResultIs(false))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenThePassword(string password)
|
||||
{
|
||||
_password = password;
|
||||
}
|
||||
|
||||
private void GivenTheHash(string hash)
|
||||
{
|
||||
_hash = hash;
|
||||
}
|
||||
|
||||
private void GivenTheSalt(string salt)
|
||||
{
|
||||
_salt = salt;
|
||||
}
|
||||
|
||||
private void WhenIMatch()
|
||||
{
|
||||
_result = _hashMatcher.Match(_password, _salt, _hash);
|
||||
}
|
||||
|
||||
private void ThenTheResultIs(bool expected)
|
||||
{
|
||||
_result.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
using Ocelot.Configuration.Authentication;
|
||||
using Xunit;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Moq;
|
||||
using IdentityServer4.Validation;
|
||||
using Ocelot.Configuration.Provider;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
public class OcelotResourceOwnerPasswordValidatorTests
|
||||
{
|
||||
private OcelotResourceOwnerPasswordValidator _validator;
|
||||
private Mock<IHashMatcher> _matcher;
|
||||
private string _userName;
|
||||
private string _password;
|
||||
private ResourceOwnerPasswordValidationContext _context;
|
||||
private Mock<IIdentityServerConfiguration> _config;
|
||||
private User _user;
|
||||
|
||||
public OcelotResourceOwnerPasswordValidatorTests()
|
||||
{
|
||||
_matcher = new Mock<IHashMatcher>();
|
||||
_config = new Mock<IIdentityServerConfiguration>();
|
||||
_validator = new OcelotResourceOwnerPasswordValidator(_matcher.Object, _config.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_success()
|
||||
{
|
||||
this.Given(x => GivenTheUserName("tom"))
|
||||
.And(x => GivenThePassword("password"))
|
||||
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
|
||||
.And(x => GivenTheMatcherReturns(true))
|
||||
.When(x => WhenIValidate())
|
||||
.Then(x => ThenTheUserIsValidated())
|
||||
.And(x => ThenTheMatcherIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_fail_when_no_user()
|
||||
{
|
||||
this.Given(x => GivenTheUserName("bob"))
|
||||
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
|
||||
.And(x => GivenTheMatcherReturns(true))
|
||||
.When(x => WhenIValidate())
|
||||
.Then(x => ThenTheUserIsNotValidated())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_fail_when_password_doesnt_match()
|
||||
{
|
||||
this.Given(x => GivenTheUserName("tom"))
|
||||
.And(x => GivenThePassword("password"))
|
||||
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
|
||||
.And(x => GivenTheMatcherReturns(false))
|
||||
.When(x => WhenIValidate())
|
||||
.Then(x => ThenTheUserIsNotValidated())
|
||||
.And(x => ThenTheMatcherIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenTheMatcherIsCalledCorrectly()
|
||||
{
|
||||
_matcher
|
||||
.Verify(x => x.Match(_password, _user.Salt, _user.Hash), Times.Once);
|
||||
}
|
||||
|
||||
private void GivenThePassword(string password)
|
||||
{
|
||||
_password = password;
|
||||
}
|
||||
|
||||
private void GivenTheUserIs(User user)
|
||||
{
|
||||
_user = user;
|
||||
_config
|
||||
.Setup(x => x.Users)
|
||||
.Returns(new List<User>{_user});
|
||||
}
|
||||
|
||||
private void GivenTheMatcherReturns(bool expected)
|
||||
{
|
||||
_matcher
|
||||
.Setup(x => x.Match(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(expected);
|
||||
}
|
||||
|
||||
private void GivenTheUserName(string userName)
|
||||
{
|
||||
_userName = userName;
|
||||
}
|
||||
|
||||
private void WhenIValidate()
|
||||
{
|
||||
_context = new ResourceOwnerPasswordValidationContext
|
||||
{
|
||||
UserName = _userName,
|
||||
Password = _password
|
||||
};
|
||||
_validator.ValidateAsync(_context).Wait();
|
||||
}
|
||||
|
||||
private void ThenTheUserIsValidated()
|
||||
{
|
||||
_context.Result.IsError.ShouldBe(false);
|
||||
}
|
||||
|
||||
private void ThenTheUserIsNotValidated()
|
||||
{
|
||||
_context.Result.IsError.ShouldBe(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,8 @@
|
||||
"Shouldly": "2.8.2",
|
||||
"TestStack.BDDfy": "4.3.2",
|
||||
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
|
||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0"
|
||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
||||
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
|
||||
},
|
||||
"runtimes": {
|
||||
"win10-x64": {},
|
||||
|
Loading…
x
Reference in New Issue
Block a user