diff --git a/README.md b/README.md index 39e5fc35..9bbaf309 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,19 @@ Priorities - Rate Limiting - Then a big list of cool things... -## How to use +# How to use -TBC.... +# Configuration + +TBC really but example configuration for a route below. + +ReRoutes: +- DownstreamTemplate: http://localhost:51876/ + UpstreamTemplate: / + UpstreamHttpMethod: Post + AuthenticationOptions: + Provider: IdentityServer.AccessToken + ProviderRootUrl: http://localhost:51888 + ScopeName: api + AdditionalScopes: [] + ScopeSecret: secret diff --git a/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerCreator.cs b/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerCreator.cs index 1428f239..ef0f9722 100644 --- a/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerCreator.cs +++ b/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerCreator.cs @@ -1,6 +1,8 @@ +using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Ocelot.Library.Infrastructure.Responses; +using AuthenticationOptions = Ocelot.Library.Infrastructure.Configuration.AuthenticationOptions; namespace Ocelot.Library.Infrastructure.Authentication { @@ -9,17 +11,18 @@ namespace Ocelot.Library.Infrastructure.Authentication /// public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator { - public Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app) + public Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions) { var builder = app.New(); builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { - //todo sort these options out - Authority = "http://localhost:51888", - ScopeName = "api", - - RequireHttpsMetadata = false + Authority = authOptions.ProviderRootUrl, + ScopeName = authOptions.ScopeName, + RequireHttpsMetadata = authOptions.RequireHttps, + AdditionalScopes = authOptions.AdditionalScopes, + SupportedTokens = SupportedTokens.Both, + ScopeSecret = authOptions.ScopeSecret }); builder.UseMvc(); diff --git a/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerFactory.cs b/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerFactory.cs index eaa8837b..1f606938 100644 --- a/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerFactory.cs +++ b/src/Ocelot.Library/Infrastructure/Authentication/AuthenticationHandlerFactory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Builder; using Ocelot.Library.Infrastructure.Errors; using Ocelot.Library.Infrastructure.Responses; +using AuthenticationOptions = Ocelot.Library.Infrastructure.Configuration.AuthenticationOptions; namespace Ocelot.Library.Infrastructure.Authentication { @@ -14,18 +15,18 @@ namespace Ocelot.Library.Infrastructure.Authentication _creator = creator; } - public Response Get(string provider, IApplicationBuilder app) + public Response Get(IApplicationBuilder app, AuthenticationOptions authOptions) { - var handler = _creator.CreateIdentityServerAuthenticationHandler(app); + var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions); if (!handler.IsError) { - return new OkResponse(new AuthenticationHandler(provider, handler.Data)); + return new OkResponse(new AuthenticationHandler(authOptions.Provider, handler.Data)); } return new ErrorResponse(new List { - new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for {provider}") + new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for {authOptions.Provider}") }); } } diff --git a/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerCreator.cs b/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerCreator.cs index cb1b2a91..553386d7 100644 --- a/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerCreator.cs +++ b/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerCreator.cs @@ -1,11 +1,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Ocelot.Library.Infrastructure.Responses; +using AuthenticationOptions = Ocelot.Library.Infrastructure.Configuration.AuthenticationOptions; namespace Ocelot.Library.Infrastructure.Authentication { public interface IAuthenticationHandlerCreator { - Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app); + Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions); } } diff --git a/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerFactory.cs b/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerFactory.cs index 6fc2af29..87f62346 100644 --- a/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerFactory.cs +++ b/src/Ocelot.Library/Infrastructure/Authentication/IAuthenticationHandlerFactory.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Builder; using Ocelot.Library.Infrastructure.Responses; +using AuthenticationOptions = Ocelot.Library.Infrastructure.Configuration.AuthenticationOptions; namespace Ocelot.Library.Infrastructure.Authentication { public interface IAuthenticationHandlerFactory { - Response Get(string provider, IApplicationBuilder app); + Response Get(IApplicationBuilder app, AuthenticationOptions authOptions); } } diff --git a/src/Ocelot.Library/Infrastructure/Authentication/SupportAuthenticationProviders.cs b/src/Ocelot.Library/Infrastructure/Authentication/SupportAuthenticationProviders.cs new file mode 100644 index 00000000..01ff98d0 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Authentication/SupportAuthenticationProviders.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Library.Infrastructure.Authentication +{ + public enum SupportAuthenticationProviders + { + IdentityServer + } +} diff --git a/src/Ocelot.Library/Infrastructure/Builder/ReRouteBuilder.cs b/src/Ocelot.Library/Infrastructure/Builder/ReRouteBuilder.cs index 3966ae7c..ee01b7d6 100644 --- a/src/Ocelot.Library/Infrastructure/Builder/ReRouteBuilder.cs +++ b/src/Ocelot.Library/Infrastructure/Builder/ReRouteBuilder.cs @@ -1,4 +1,6 @@ -namespace Ocelot.Library.Infrastructure.Builder +using System.Collections.Generic; + +namespace Ocelot.Library.Infrastructure.Builder { using Configuration; @@ -10,38 +12,84 @@ private string _upstreamHttpMethod; private bool _isAuthenticated; private string _authenticationProvider; + private string _authenticationProviderUrl; + private string _scopeName; + private List _additionalScopes; + private bool _requireHttps; + private string _scopeSecret; - public void WithDownstreamTemplate(string input) + public ReRouteBuilder() + { + _additionalScopes = new List(); + } + + public ReRouteBuilder WithDownstreamTemplate(string input) { _downstreamTemplate = input; + return this; } - public void WithUpstreamTemplate(string input) + public ReRouteBuilder WithUpstreamTemplate(string input) { _upstreamTemplate = input; + return this; } - public void WithUpstreamTemplatePattern(string input) + public ReRouteBuilder WithUpstreamTemplatePattern(string input) { _upstreamTemplatePattern = input; + return this; } - public void WithUpstreamHttpMethod(string input) + public ReRouteBuilder WithUpstreamHttpMethod(string input) { _upstreamHttpMethod = input; + return this; } - public void WithIsAuthenticated(bool input) + public ReRouteBuilder WithIsAuthenticated(bool input) { _isAuthenticated = input; + return this; } - public void WithAuthenticationProvider(string input) + public ReRouteBuilder WithAuthenticationProvider(string input) { _authenticationProvider = input; + return this; + } + + public ReRouteBuilder WithAuthenticationProviderUrl(string input) + { + _authenticationProviderUrl = input; + return this; + } + + public ReRouteBuilder WithAuthenticationProviderScopeName(string input) + { + _scopeName = input; + return this; + } + + public ReRouteBuilder WithAuthenticationProviderAdditionalScopes(List input) + { + _additionalScopes = input; + return this; + } + + public ReRouteBuilder WithRequireHttps(bool input) + { + _requireHttps = input; + return this; + } + + public ReRouteBuilder WithScopeSecret(string input) + { + _scopeSecret = input; + return this; } public ReRoute Build() { - return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, _authenticationProvider); + return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret)); } } } diff --git a/src/Ocelot.Library/Infrastructure/Configuration/AuthenticationOptions.cs b/src/Ocelot.Library/Infrastructure/Configuration/AuthenticationOptions.cs new file mode 100644 index 00000000..9cda2672 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Configuration/AuthenticationOptions.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Ocelot.Library.Infrastructure.Configuration +{ + public class AuthenticationOptions + { + public AuthenticationOptions(string provider, string providerRootUrl, string scopeName, bool requireHttps, List additionalScopes, string scopeSecret) + { + Provider = provider; + ProviderRootUrl = providerRootUrl; + ScopeName = scopeName; + RequireHttps = requireHttps; + AdditionalScopes = additionalScopes; + ScopeSecret = scopeSecret; + } + + public string Provider { get; private set; } + public string ProviderRootUrl { get; private set; } + public string ScopeName { get; private set; } + public string ScopeSecret { get; private set; } + public bool RequireHttps { get; private set; } + public List AdditionalScopes { get; private set; } + + } +} diff --git a/src/Ocelot.Library/Infrastructure/Configuration/OcelotConfiguration.cs b/src/Ocelot.Library/Infrastructure/Configuration/OcelotConfiguration.cs index 1b93447a..afa768d2 100644 --- a/src/Ocelot.Library/Infrastructure/Configuration/OcelotConfiguration.cs +++ b/src/Ocelot.Library/Infrastructure/Configuration/OcelotConfiguration.cs @@ -7,49 +7,81 @@ namespace Ocelot.Library.Infrastructure.Configuration public class OcelotConfiguration : IOcelotConfiguration { private readonly IOptions _options; + private readonly IConfigurationValidator _configurationValidator; private readonly List _reRoutes; private const string RegExMatchEverything = ".*"; private const string RegExMatchEndString = "$"; - public OcelotConfiguration(IOptions options) + public OcelotConfiguration(IOptions options, IConfigurationValidator configurationValidator) { _options = options; + _configurationValidator = configurationValidator; _reRoutes = new List(); - SetReRoutes(); + SetUpConfiguration(); } - private void SetReRoutes() + /// + /// This method is meant to be tempoary to convert a yaml config to an ocelot config...probably wont keep this but we will see + /// will need a refactor at some point as its crap + /// + private void SetUpConfiguration() { - foreach(var reRoute in _options.Value.ReRoutes) + var response = _configurationValidator.IsValid(_options.Value); + + if (!response.IsError && !response.Data.IsError) { - var upstreamTemplate = reRoute.UpstreamTemplate; - var placeholders = new List(); - - for (int i = 0; i < upstreamTemplate.Length; i++) + foreach (var reRoute in _options.Value.ReRoutes) { - if (IsPlaceHolder(upstreamTemplate, i)) - { - var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); - var difference = postitionOfPlaceHolderClosingBracket - i + 1; - var variableName = upstreamTemplate.Substring(i, difference); - placeholders.Add(variableName); - } + SetUpReRoute(reRoute); } - - foreach (var placeholder in placeholders) - { - upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); - } - - upstreamTemplate = $"{upstreamTemplate}{RegExMatchEndString}"; - - var isAuthenticated = !string.IsNullOrEmpty(reRoute.Authentication); - - _reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, reRoute.Authentication)); - } + } } - private static bool IsPlaceHolder(string upstreamTemplate, int i) + private void SetUpReRoute(YamlReRoute reRoute) + { + var upstreamTemplate = reRoute.UpstreamTemplate; + + var placeholders = new List(); + + for (int i = 0; i < upstreamTemplate.Length; i++) + { + if (IsPlaceHolder(upstreamTemplate, i)) + { + var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); + var difference = postitionOfPlaceHolderClosingBracket - i + 1; + var variableName = upstreamTemplate.Substring(i, difference); + placeholders.Add(variableName); + } + } + + foreach (var placeholder in placeholders) + { + upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); + } + + upstreamTemplate = $"{upstreamTemplate}{RegExMatchEndString}"; + + var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider); + + if (isAuthenticated) + { + var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, + reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName, + reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes, + reRoute.AuthenticationOptions.ScopeSecret); + + _reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute + )); + } + else + { + _reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, + upstreamTemplate, isAuthenticated, null)); + } + } + + private bool IsPlaceHolder(string upstreamTemplate, int i) { return upstreamTemplate[i] == '{'; } diff --git a/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs b/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs index 5d000511..12dfd1b2 100644 --- a/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs +++ b/src/Ocelot.Library/Infrastructure/Configuration/ReRoute.cs @@ -2,14 +2,14 @@ { public class ReRoute { - public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, string authenticationProvider) + public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions) { DownstreamTemplate = downstreamTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; - AuthenticationProvider = authenticationProvider; + AuthenticationOptions = authenticationOptions; } public string DownstreamTemplate { get; private set; } @@ -17,6 +17,6 @@ public string UpstreamTemplatePattern { get; private set; } public string UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } - public string AuthenticationProvider { get; private set; } + public AuthenticationOptions AuthenticationOptions { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/Configuration/Yaml/ConfigurationValidator.cs b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/ConfigurationValidator.cs index 64b72929..6c7aee98 100644 --- a/src/Ocelot.Library/Infrastructure/Configuration/Yaml/ConfigurationValidator.cs +++ b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/ConfigurationValidator.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using Ocelot.Library.Infrastructure.Authentication; using Ocelot.Library.Infrastructure.Errors; using Ocelot.Library.Infrastructure.Responses; @@ -8,6 +10,59 @@ namespace Ocelot.Library.Infrastructure.Configuration.Yaml public class ConfigurationValidator : IConfigurationValidator { public Response IsValid(YamlConfiguration configuration) + { + var result = CheckForDupliateReRoutes(configuration); + + if (result.IsError) + { + return new OkResponse(result); + } + + result = CheckForUnsupportedAuthenticationProviders(configuration); + + if (result.IsError) + { + return new OkResponse(result); + } + + return new OkResponse(result); + } + + private ConfigurationValidationResult CheckForUnsupportedAuthenticationProviders(YamlConfiguration configuration) + { + var errors = new List(); + + foreach (var yamlReRoute in configuration.ReRoutes) + { + var isAuthenticated = !string.IsNullOrEmpty(yamlReRoute.AuthenticationOptions?.Provider); + + if (!isAuthenticated) + { + continue; + } + + if (IsSupportedAuthenticationProvider(yamlReRoute.AuthenticationOptions?.Provider)) + { + continue; + } + + var error = new UnsupportedAuthenticationProviderError($"{yamlReRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {yamlReRoute.UpstreamTemplate}, upstream method is {yamlReRoute.UpstreamHttpMethod}"); + errors.Add(error); + } + + return errors.Count > 0 + ? new ConfigurationValidationResult(true, errors) + : new ConfigurationValidationResult(false); + } + + private bool IsSupportedAuthenticationProvider(string provider) + { + SupportAuthenticationProviders supportedProvider; + + return Enum.TryParse(provider, true, out supportedProvider); + } + + private ConfigurationValidationResult CheckForDupliateReRoutes(YamlConfiguration configuration) { var duplicateUpstreamTemplates = configuration.ReRoutes .Select(r => r.DownstreamTemplate) @@ -18,19 +73,15 @@ namespace Ocelot.Library.Infrastructure.Configuration.Yaml if (duplicateUpstreamTemplates.Count <= 0) { - return new OkResponse(new ConfigurationValidationResult(false)); - } - - var errors = new List(); - - foreach (var duplicateUpstreamTemplate in duplicateUpstreamTemplates) - { - var error = new DownstreamTemplateAlreadyUsedError(string.Format("Duplicate DownstreamTemplate: {0}", - duplicateUpstreamTemplate)); - errors.Add(error); + return new ConfigurationValidationResult(false); } - return new OkResponse(new ConfigurationValidationResult(true, errors)); + var errors = duplicateUpstreamTemplates + .Select(duplicateUpstreamTemplate => new DownstreamTemplateAlreadyUsedError(string.Format("Duplicate DownstreamTemplate: {0}", duplicateUpstreamTemplate))) + .Cast() + .ToList(); + + return new ConfigurationValidationResult(true, errors); } } } diff --git a/src/Ocelot.Library/Infrastructure/Configuration/Yaml/UnsupportedAuthenticationProviderError.cs b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/UnsupportedAuthenticationProviderError.cs new file mode 100644 index 00000000..1964ef80 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/UnsupportedAuthenticationProviderError.cs @@ -0,0 +1,12 @@ +using Ocelot.Library.Infrastructure.Errors; + +namespace Ocelot.Library.Infrastructure.Configuration.Yaml +{ + public class UnsupportedAuthenticationProviderError : Error + { + public UnsupportedAuthenticationProviderError(string message) + : base(message, OcelotErrorCode.UnsupportedAuthenticationProviderError) + { + } + } +} diff --git a/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlAuthenticationOptions.cs b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlAuthenticationOptions.cs new file mode 100644 index 00000000..463a75f5 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlAuthenticationOptions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Ocelot.Library.Infrastructure.Configuration.Yaml +{ + public class YamlAuthenticationOptions + { + public string Provider { get; set; } + public string ProviderRootUrl { get; set; } + public string ScopeName { get; set; } + public bool RequireHttps { get; set; } + public List AdditionalScopes { get; set; } + public string ScopeSecret { get; set; } + } +} diff --git a/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlReRoute.cs b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlReRoute.cs index 0c1e1e12..26c1569b 100644 --- a/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlReRoute.cs +++ b/src/Ocelot.Library/Infrastructure/Configuration/Yaml/YamlReRoute.cs @@ -5,6 +5,6 @@ public string DownstreamTemplate { get; set; } public string UpstreamTemplate { get; set; } public string UpstreamHttpMethod { get; set; } - public string Authentication { get; set; } + public YamlAuthenticationOptions AuthenticationOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/Errors/OcelotErrorCode.cs b/src/Ocelot.Library/Infrastructure/Errors/OcelotErrorCode.cs index 4f7a1fd7..01ddfa39 100644 --- a/src/Ocelot.Library/Infrastructure/Errors/OcelotErrorCode.cs +++ b/src/Ocelot.Library/Infrastructure/Errors/OcelotErrorCode.cs @@ -9,6 +9,7 @@ CannotAddDataError, CannotFindDataError, UnableToCompleteRequestError, - UnableToCreateAuthenticationHandlerError + UnableToCreateAuthenticationHandlerError, + UnsupportedAuthenticationProviderError } } diff --git a/src/Ocelot.Library/Infrastructure/Middleware/AuthenticationMiddleware.cs b/src/Ocelot.Library/Infrastructure/Middleware/AuthenticationMiddleware.cs index 896d1be3..89bdd875 100644 --- a/src/Ocelot.Library/Infrastructure/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot.Library/Infrastructure/Middleware/AuthenticationMiddleware.cs @@ -1,25 +1,18 @@ using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Ocelot.Library.Infrastructure.Authentication; using Ocelot.Library.Infrastructure.Configuration; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.Errors; using Ocelot.Library.Infrastructure.Repository; -using Ocelot.Library.Infrastructure.Responses; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Ocelot.Library.Infrastructure.Authentication; namespace Ocelot.Library.Infrastructure.Middleware { public class AuthenticationMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; - private RequestDelegate _authenticationNext; private readonly IScopedRequestDataRepository _scopedRequestDataRepository; private readonly IApplicationBuilder _app; private readonly IAuthenticationHandlerFactory _authHandlerFactory; @@ -46,7 +39,7 @@ namespace Ocelot.Library.Infrastructure.Middleware if (IsAuthenticatedRoute(downstreamRoute.Data.ReRoute)) { - var authenticationNext = _authHandlerFactory.Get(downstreamRoute.Data.ReRoute.AuthenticationProvider, _app); + var authenticationNext = _authHandlerFactory.Get(_app, downstreamRoute.Data.ReRoute.AuthenticationOptions); if (!authenticationNext.IsError) { diff --git a/src/Ocelot/Startup.cs b/src/Ocelot/Startup.cs index fc8a9d0f..1ea234e3 100644 --- a/src/Ocelot/Startup.cs +++ b/src/Ocelot/Startup.cs @@ -43,11 +43,13 @@ namespace Ocelot services.AddOptions(); services.AddMvc(); services.AddMvcCore().AddAuthorization().AddJsonFormatters(); + services.AddAuthentication(); + services.AddLogging(); services.Configure(Configuration); - services.AddAuthentication(); // Add framework services. + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index bfefdc7f..b38b70a7 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -43,7 +43,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_401_using_identity_server_access_token() { - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888")) + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 201, string.Empty)) .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration { @@ -54,7 +54,48 @@ namespace Ocelot.AcceptanceTests DownstreamTemplate = "http://localhost:51876/", UpstreamTemplate = "/", UpstreamHttpMethod = "Post", - Authentication = "JwtBearerAuthentication" + AuthenticationOptions = new YamlAuthenticationOptions + { + AdditionalScopes = new List(), + Provider = "IdentityServer", + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ScopeName = "api", + ScopeSecret = "secret" + } + } + } + })) + .And(x => x.GivenTheApiGatewayIsRunning()) + .And(x => x.GivenThePostHasContent("postContent")) + .When(x => x.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_401_using_identity_server_reference_token() + { + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Reference)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 201, string.Empty)) + .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://localhost:51876/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Post", + AuthenticationOptions = new YamlAuthenticationOptions + { + AdditionalScopes = new List(), + Provider = "IdentityServer", + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ScopeName = "api", + ScopeSecret = "secret" + } } } })) @@ -68,7 +109,7 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_201_using_identity_server_access_token() { - this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888")) + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Jwt)) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 201, string.Empty)) .And(x => x.GivenIHaveAToken("http://localhost:51888")) .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration @@ -80,7 +121,50 @@ namespace Ocelot.AcceptanceTests DownstreamTemplate = "http://localhost:51876/", UpstreamTemplate = "/", UpstreamHttpMethod = "Post", - Authentication = "JwtBearerAuthentication" + AuthenticationOptions = new YamlAuthenticationOptions + { + AdditionalScopes = new List(), + Provider = "IdentityServer", + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ScopeName = "api", + ScopeSecret = "secret" + } + } + } + })) + .And(x => x.GivenTheApiGatewayIsRunning()) + .And(x => x.GivenIHaveAddedATokenToMyRequest()) + .And(x => x.GivenThePostHasContent("postContent")) + .When(x => x.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => x.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .BDDfy(); + } + + [Fact] + public void should_return_201_using_identity_server_reference_token() + { + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:51888", "api", AccessTokenType.Reference)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", 201, string.Empty)) + .And(x => x.GivenIHaveAToken("http://localhost:51888")) + .And(x => x.GivenThereIsAConfiguration(new YamlConfiguration + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://localhost:51876/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Post", + AuthenticationOptions = new YamlAuthenticationOptions + { + AdditionalScopes = new List(), + Provider = "IdentityServer", + ProviderRootUrl = "http://localhost:51888", + RequireHttps = false, + ScopeName = "api", + ScopeSecret = "secret" + } } } })) @@ -144,7 +228,7 @@ namespace Ocelot.AcceptanceTests _ocelotBbuilder.Start(); } - private void GivenThereIsAnIdentityServerOn(string url) + private void GivenThereIsAnIdentityServerOn(string url, string scopeName, AccessTokenType tokenType) { _identityServerBuilder = new WebHostBuilder() .UseUrls(url) @@ -154,13 +238,21 @@ namespace Ocelot.AcceptanceTests .UseUrls(url) .ConfigureServices(services => { + services.AddLogging(); services.AddDeveloperIdentityServer() .AddInMemoryScopes(new List { new Scope { - Name = "api", + Name = scopeName, Description = "My API", - Enabled = true - + Enabled = true, + AllowUnrestrictedIntrospection = true, + ScopeSecrets = new List() + { + new Secret + { + Value = "secret".Sha256() + } + } }}) .AddInMemoryClients(new List { new Client @@ -168,8 +260,8 @@ namespace Ocelot.AcceptanceTests ClientId = "client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = new List { new Secret("secret".Sha256()) }, - AllowedScopes = new List { "api" }, - AccessTokenType = AccessTokenType.Jwt, + AllowedScopes = new List { scopeName }, + AccessTokenType = tokenType, Enabled = true, RequireClientSecret = false } }) @@ -182,7 +274,7 @@ namespace Ocelot.AcceptanceTests }}); }) .Configure(app => - { + { app.UseIdentityServer(); }) .Build(); @@ -248,6 +340,7 @@ namespace Ocelot.AcceptanceTests _identityServerBuilder?.Dispose(); } + // ReSharper disable once ClassNeverInstantiated.Local class BearerToken { [JsonProperty("access_token")] diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs index 028d07d9..1e2f6d42 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs @@ -17,8 +17,7 @@ namespace Ocelot.UnitTests.Authentication private readonly IAuthenticationHandlerFactory _authenticationHandlerFactory; private readonly Mock _app; private readonly Mock _creator; - - private string _provider; + private Library.Infrastructure.Configuration.AuthenticationOptions _authenticationOptions; private Response _result; public AuthenticationHandlerFactoryTests() @@ -31,27 +30,32 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_return_identity_server_access_token_handler() { - this.Given(x => x.GivenTheProviderIs("IdentityServer.AccessToken")) + this.Given(x => x.GivenTheAuthenticationOptionsAre(new Library.Infrastructure.Configuration.AuthenticationOptions("IdentityServer", "","",false, new List(), ""))) .And(x => x.GivenTheCreatorReturns()) .When(x => x.WhenIGetFromTheFactory()) - .Then(x => x.ThenTheHandlerIsReturned("IdentityServer.AccessToken")) + .Then(x => x.ThenTheHandlerIsReturned("IdentityServer")) .BDDfy(); } [Fact] public void should_return_error_if_cannot_create_handler() { - this.Given(x => x.GivenTheProviderIs("IdentityServer.AccessToken")) + this.Given(x => x.GivenTheAuthenticationOptionsAre(new Library.Infrastructure.Configuration.AuthenticationOptions("IdentityServer", "", "", false, new List(), ""))) .And(x => x.GivenTheCreatorReturnsAnError()) .When(x => x.WhenIGetFromTheFactory()) .Then(x => x.ThenAnErrorResponseIsReturned()) .BDDfy(); } + private void GivenTheAuthenticationOptionsAre(Library.Infrastructure.Configuration.AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + } + private void GivenTheCreatorReturnsAnError() { _creator - .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny())) + .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny(), It.IsAny())) .Returns(new ErrorResponse(new List { new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for xxx") @@ -61,18 +65,13 @@ namespace Ocelot.UnitTests.Authentication private void GivenTheCreatorReturns() { _creator - .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny())) + .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny(), It.IsAny())) .Returns(new OkResponse(x => Task.CompletedTask)); } - private void GivenTheProviderIs(string provider) - { - _provider = provider; - } - private void WhenIGetFromTheFactory() { - _result = _authenticationHandlerFactory.Get(_provider, _app.Object); + _result = _authenticationHandlerFactory.Get(_app.Object, _authenticationOptions); } private void ThenTheHandlerIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 3f3526e8..b797a9bf 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -37,6 +37,53 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + [Fact] + public void configuration_is_valid_with_valid_authentication_provider() + { + this.Given(x => x.GivenAConfiguration(new YamlConfiguration() + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://www.bbc.co.uk", + UpstreamTemplate = "http://asdf.com", + AuthenticationOptions = new YamlAuthenticationOptions + { + Provider = "IdentityServer" + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_with_invalid_authentication_provider() + { + this.Given(x => x.GivenAConfiguration(new YamlConfiguration() + { + ReRoutes = new List + { + new YamlReRoute + { + DownstreamTemplate = "http://www.bbc.co.uk", + UpstreamTemplate = "http://asdf.com", + AuthenticationOptions = new YamlAuthenticationOptions + { + Provider = "BootyBootyBottyRockinEverywhere" + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .BDDfy(); + } + [Fact] public void configuration_is_not_valid_with_duplicate_reroutes() { diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationTests.cs index 2977f54f..bfbeee98 100644 --- a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationTests.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using Microsoft.Extensions.Options; using Moq; +using Ocelot.Library.Infrastructure.Builder; using Ocelot.Library.Infrastructure.Configuration; using Ocelot.Library.Infrastructure.Configuration.Yaml; +using Ocelot.Library.Infrastructure.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -12,11 +14,13 @@ namespace Ocelot.UnitTests.Configuration public class OcelotConfigurationTests { private readonly Mock> _yamlConfig; + private readonly Mock _validator; private OcelotConfiguration _config; private YamlConfiguration _yamlConfiguration; public OcelotConfigurationTests() { + _validator = new Mock(); _yamlConfig = new Mock>(); } @@ -35,10 +39,16 @@ namespace Ocelot.UnitTests.Configuration } } })) + .And(x => x.GivenTheYamlConfigIsValid()) .When(x => x.WhenIInstanciateTheOcelotConfig()) .Then(x => x.ThenTheReRoutesAre(new List { - new ReRoute("/products/{productId}","/api/products/{productId}", "Get", "/api/products/.*$", false, "") + new ReRouteBuilder() + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("/api/products/.*$") + .Build() })) .BDDfy(); } @@ -58,10 +68,16 @@ namespace Ocelot.UnitTests.Configuration } } })) + .And(x => x.GivenTheYamlConfigIsValid()) .When(x => x.WhenIInstanciateTheOcelotConfig()) .Then(x => x.ThenTheReRoutesAre(new List { - new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}", "Get", "/api/products/.*/variants/.*$", false, "") + new ReRouteBuilder() + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("/api/products/.*/variants/.*$") + .Build() })) .BDDfy(); } @@ -81,10 +97,16 @@ namespace Ocelot.UnitTests.Configuration } } })) + .And(x => x.GivenTheYamlConfigIsValid()) .When(x => x.WhenIInstanciateTheOcelotConfig()) .Then(x => x.ThenTheReRoutesAre(new List { - new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}/", "Get", "/api/products/.*/variants/.*/$", false, "") + new ReRouteBuilder() + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") + .Build() })) .BDDfy(); } @@ -104,14 +126,27 @@ namespace Ocelot.UnitTests.Configuration } } })) + .And(x => x.GivenTheYamlConfigIsValid()) .When(x => x.WhenIInstanciateTheOcelotConfig()) .Then(x => x.ThenTheReRoutesAre(new List { - new ReRoute("/api/products/","/", "Get", "/$", false, "") + new ReRouteBuilder() + .WithDownstreamTemplate("/api/products/") + .WithUpstreamTemplate("/") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("/$") + .Build() })) .BDDfy(); } + private void GivenTheYamlConfigIsValid() + { + _validator + .Setup(x => x.IsValid(It.IsAny())) + .Returns(new OkResponse(new ConfigurationValidationResult(false))); + } + private void GivenTheYamlConfigIs(YamlConfiguration yamlConfiguration) { _yamlConfiguration = yamlConfiguration; @@ -122,7 +157,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIInstanciateTheOcelotConfig() { - _config = new OcelotConfiguration(_yamlConfig.Object); + _config = new OcelotConfiguration(_yamlConfig.Object, _validator.Object); } private void ThenTheReRoutesAre(List expectedReRoutes) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 127ec7d8..5a2a73cd 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Moq; +using Ocelot.Library.Infrastructure.Builder; using Ocelot.Library.Infrastructure.Configuration; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.Responses; @@ -34,17 +35,29 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder public void should_return_route() { this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List - { - new ReRoute("someDownstreamPath","someUpstreamPath", "Get", "someUpstreamPath", false, "") - } - )) + { + new ReRouteBuilder() + .WithDownstreamTemplate("someDownstreamPath") + .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("someUpstreamPath") + .Build() + } + )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRoute("someDownstreamPath","","","",false, "")))) + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamTemplate("someDownstreamPath") + .Build() + ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) .BDDfy(); } @@ -53,18 +66,35 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder public void should_return_correct_route_for_http_verb() { this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) - .And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse>(new List()))) + .And( + x => + x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List - { - new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty, false, ""), - new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty, false, "") - } - )) + { + new ReRouteBuilder() + .WithDownstreamTemplate("someDownstreamPath") + .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("") + .Build(), + new ReRouteBuilder() + .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamHttpMethod("Post") + .WithUpstreamTemplatePattern("") + .Build() + } + )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRoute("someDownstreamPathForAPost", "","","",false, "")))) + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamTemplate("someDownstreamPathForAPost") + .Build() + ))) .BDDfy(); } @@ -74,7 +104,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) .And(x => x.GivenTheConfigurationIs(new List { - new ReRoute("somPath", "somePath", "Get", "somePath", false, "") + new ReRouteBuilder() + .WithDownstreamTemplate("somPath") + .WithUpstreamTemplate("somePath") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("somePath") + .Build(), } )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) diff --git a/test/Ocelot.UnitTests/Middleware/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/AuthenticationMiddlewareTests.cs index 793bd66a..3adf0c61 100644 --- a/test/Ocelot.UnitTests/Middleware/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Middleware/AuthenticationMiddlewareTests.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Library.Infrastructure.Authentication; +using Ocelot.Library.Infrastructure.Builder; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.Middleware; using Ocelot.Library.Infrastructure.Repository; @@ -57,7 +58,7 @@ namespace Ocelot.UnitTests.Middleware [Fact] public void happy_path() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRoute("","","","",false, "")))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenNoExceptionsAreThrown()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Middleware/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/DownstreamRouteFinderMiddlewareTests.cs index ec48f886..07ca69e4 100644 --- a/test/Ocelot.UnitTests/Middleware/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Middleware/DownstreamRouteFinderMiddlewareTests.cs @@ -1,23 +1,22 @@ -using Ocelot.Library.Infrastructure.Middleware; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Ocelot.Library.Infrastructure.Builder; +using Ocelot.Library.Infrastructure.DownstreamRouteFinder; +using Ocelot.Library.Infrastructure.Middleware; +using Ocelot.Library.Infrastructure.Repository; +using Ocelot.Library.Infrastructure.Responses; +using Ocelot.Library.Infrastructure.UrlMatcher; +using TestStack.BDDfy; +using Xunit; namespace Ocelot.UnitTests.Middleware { - using System; - using System.Collections.Generic; - using System.IO; - using System.Net.Http; - using Library.Infrastructure.Configuration; - using Library.Infrastructure.DownstreamRouteFinder; - using Library.Infrastructure.Repository; - using Library.Infrastructure.Responses; - using Library.Infrastructure.UrlMatcher; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.TestHost; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using TestStack.BDDfy; - using Xunit; - public class DownstreamRouteFinderMiddlewareTests : IDisposable { private readonly Mock _downstreamRouteFinder; @@ -57,7 +56,7 @@ namespace Ocelot.UnitTests.Middleware [Fact] public void happy_path() { - this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRoute("any old string", "", "", "",false, "")))) + this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Middleware/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/DownstreamUrlCreatorMiddlewareTests.cs index d9545f2f..b1d165ba 100644 --- a/test/Ocelot.UnitTests/Middleware/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Middleware/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,4 +1,5 @@ -using Ocelot.Library.Infrastructure.Middleware; +using Ocelot.Library.Infrastructure.Builder; +using Ocelot.Library.Infrastructure.Middleware; namespace Ocelot.UnitTests.Middleware { @@ -59,7 +60,7 @@ namespace Ocelot.UnitTests.Middleware [Fact] public void happy_path() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRoute("any old string", "", "", "", false, "")))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) .And(x => x.TheUrlReplacerReturns("any old string")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) diff --git a/test/Ocelot.UnitTests/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index 18bf22d8..961341c9 100644 --- a/test/Ocelot.UnitTests/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Ocelot.Library.Infrastructure.Builder; using Ocelot.Library.Infrastructure.DownstreamRouteFinder; using Ocelot.Library.Infrastructure.Responses; using Ocelot.Library.Infrastructure.UrlMatcher; @@ -25,7 +26,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRoute("", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) .BDDfy(); @@ -34,7 +35,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables_with_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRoute("/", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .BDDfy(); @@ -43,7 +44,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRoute("api", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .BDDfy(); @@ -52,7 +53,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRoute("api/", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .BDDfy(); @@ -61,7 +62,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRoute("api/product/products/", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/product/products/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .BDDfy(); @@ -75,7 +76,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer new TemplateVariableNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRoute("productservice/products/{productId}/", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .BDDfy(); @@ -89,7 +90,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer new TemplateVariableNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRoute("productservice/products/{productId}/variants", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .BDDfy(); @@ -104,7 +105,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer new TemplateVariableNameAndValue("{variantId}", "12") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRoute("productservice/products/{productId}/variants/{variantId}", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .BDDfy(); @@ -120,7 +121,7 @@ namespace Ocelot.UnitTests.UrlTemplateReplacer new TemplateVariableNameAndValue("{categoryId}", "34") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRoute("productservice/category/{categoryId}/products/{productId}/variants/{variantId}", "", "", "", false, "")))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .BDDfy();