Skip to content

Commit

Permalink
Security definition generation from AuthenticationProviderKey (#205)
Browse files Browse the repository at this point in the history
* Bump Swashbuckle version

* Implementing security definition generation

* Basic security definition test

* Provide readme for security definition generation

* Add AuthenticationOptions missing comments

* Add braces to single line if statement
  • Loading branch information
bodnarkevin committed Nov 20, 2021
1 parent 0c55d5a commit 68a9401
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 11 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,55 @@ public class PublishedDownstreamInterceptor : ISwaggerDownstreamInterceptor
Note, the service is still visible in the swagger ui the response is only visible in the request to the downstream url.
If you want to control the visibility of the endpoints as well you have to implement a custom swagger ui.

## Security definition generation

It is possible to generate security definitions for the enpoints based on Ocelot configuration

1. Add `AuthenticationOptions` to your route definition
``` Json
"Routes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"ServiceName": "projects",
"UpstreamPathTemplate": "/api/project/{everything}",
"SwaggerKey": "projects",
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": [ "scope" ]
},
}
]
```

2. Provide a mapping in Startup between `AuthenticationProviderKey` and it's corresponding `securityDefintion`
```CSharp
services.AddSwaggerForOcelot(Configuration,
(o) =>
{
o.AddAuthenticationProviderKeyMapping("Bearer", "appAuth");
});
```

3. Now you should have security definitions on your swagger documents
``` Json
{
"paths": {
"/api/project": {
"get": {
...
"security": [
{
"appAuth": [ "scope" ]
}
]
}
}
}
}
```

Note, this does not affect nor checks the swagger document's `securityDefinitions` property.

## Limitation

- Now, this library support only `{everything}` as a wildcard in routing definition. #68
Expand Down
22 changes: 22 additions & 0 deletions src/MMLib.SwaggerForOcelot/Configuration/AuthenticationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;

namespace MMLib.SwaggerForOcelot.Configuration
{
/// <summary>
/// Ocelot AuthenticationOptions configuration.
/// </summary>
public class AuthenticationOptions
{
/// <summary>
/// Ocelot AuthenticationProviderKey configuration.
/// Authentication provider key.
/// </summary>
public string AuthenticationProviderKey { get; set; }

/// <summary>
/// Ocelot AllowedScopes configartion.
/// Ocelot will get all scope claims and make sure that the user has all of them.
/// </summary>
public List<string> AllowedScopes { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ public void GenerateDocsDocsForGatewayItSelf(Action<OcelotGatewayItSelfSwaggerGe
options?.Invoke(OcelotGatewayItSelfSwaggerGenOptions);
}

/// <summary>
/// Adds a mapping between Ocelot's AuthenticationProviderKey and Swagger's securityScheme
/// If a route has a match, security definition will be added to the endpoint with the provided AllowedScopes from the config.
/// </summary>
/// <param name="authenticationProviderKey"></param>
/// <param name="securityScheme"></param>
public void AddAuthenticationProviderKeyMapping(string authenticationProviderKey, string securityScheme)
{
AuthenticationProviderKeyMap.Add(authenticationProviderKey, securityScheme);
}

/// <summary>
/// Register aggregate docs generator post process.
/// </summary>
Expand All @@ -51,5 +62,7 @@ public void GenerateDocsDocsForGatewayItSelf(Action<OcelotGatewayItSelfSwaggerGe
internal const string GatewayKey = "gateway";

internal OcelotGatewayItSelfSwaggerGenOptions OcelotGatewayItSelfSwaggerGenOptions { get; private set; }

internal Dictionary<string, string> AuthenticationProviderKeyMap { get; } = new();
}
}
10 changes: 8 additions & 2 deletions src/MMLib.SwaggerForOcelot/Configuration/RouteOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using Kros.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using Kros.Extensions;

namespace MMLib.SwaggerForOcelot.Configuration
{
Expand All @@ -11,6 +11,7 @@ namespace MMLib.SwaggerForOcelot.Configuration
public class RouteOptions
{
private const string CatchAllPlaceHolder = "{everything}";

private readonly string[] _defaultMethodsTypes =
new string[] { "get", "post", "put", "delete", "options", "patch", "head", "connect", "trace" };

Expand Down Expand Up @@ -116,6 +117,11 @@ public RouteOptions()
/// </summary>
public Dictionary<string, string> ParametersMap { get; set; }

/// <summary>
/// Gets or sets the authentication options.
/// </summary>
public AuthenticationOptions AuthenticationOptions { get; set; }

/// <summary>
/// Gets the downstream path.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/MMLib.SwaggerForOcelot/OpenApiProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,10 @@ public static class OpenApiProperties
/// The tag's name property name. Property is a child of <seealso cref="Tags"/>.
/// </summary>
public const string TagName = "name";

/// <summary>
/// The endpoint's security definition property name
/// </summary>
public const string Security = "security";
}
}
11 changes: 7 additions & 4 deletions src/MMLib.SwaggerForOcelot/RouteOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ internal static class RouteOptionsExtensions
/// <param name="routeOptions">The re route options.</param>
public static IEnumerable<RouteOptions> GroupByPaths(this IEnumerable<RouteOptions> routeOptions)
=> routeOptions
.GroupBy(p => new { p.SwaggerKey, p.UpstreamPathTemplate, p.DownstreamPathTemplate, p.VirtualDirectory})
.Select(p => {
.GroupBy(p => new { p.SwaggerKey, p.UpstreamPathTemplate, p.DownstreamPathTemplate, p.VirtualDirectory })
.Select(p =>
{
RouteOptions route = p.First();
return new RouteOptions(
p.Key.SwaggerKey,
Expand All @@ -26,7 +27,8 @@ public static IEnumerable<RouteOptions> GroupByPaths(this IEnumerable<RouteOptio
p.Where(r => r.UpstreamHttpMethod != null).SelectMany(r => r.UpstreamHttpMethod))
{
DownstreamHttpVersion = route.DownstreamHttpVersion,
DownstreamScheme = route.DownstreamScheme
DownstreamScheme = route.DownstreamScheme,
AuthenticationOptions = route.AuthenticationOptions,
};
});

Expand Down Expand Up @@ -64,7 +66,8 @@ public static IEnumerable<RouteOptions> GroupByPaths(this IEnumerable<RouteOptio
c.Version),
VirtualDirectory = routeOption.VirtualDirectory,
DownstreamHttpVersion = routeOption.DownstreamHttpVersion,
DownstreamScheme = routeOption.DownstreamScheme
DownstreamScheme = routeOption.DownstreamScheme,
AuthenticationOptions = routeOption.AuthenticationOptions,
});
routeOptions.AddRange(versionMappedRouteOptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ namespace MMLib.SwaggerForOcelot.Transformation
/// <seealso cref="ISwaggerJsonTransformer" />
public class SwaggerJsonTransformer : ISwaggerJsonTransformer
{
private readonly OcelotSwaggerGenOptions _ocelotSwaggerGenOptions;

public SwaggerJsonTransformer(OcelotSwaggerGenOptions ocelotSwaggerGenOptions)
{
_ocelotSwaggerGenOptions = ocelotSwaggerGenOptions;
}

/// <inheritdoc/>
public string Transform(
string swaggerJson,
Expand Down Expand Up @@ -141,6 +148,8 @@ private void RenameAndRemovePaths(IEnumerable<RouteOptions> routes, JToken paths

if (route != null && RemoveMethods(path, route))
{
AddSecurityDefinitions(path, route);

RenameToken(path, ConvertDownstreamPathToUpstreamPath(downstreamPath, route.DownstreamPath, route.UpstreamPath, basePath));
}
else
Expand Down Expand Up @@ -177,6 +186,36 @@ private bool RemoveMethods(JProperty path, RouteOptions route)
return path.First.Any();
}

private void AddSecurityDefinitions(JProperty path, RouteOptions route)
{
var authProviderKey = route.AuthenticationOptions?.AuthenticationProviderKey;

if (string.IsNullOrEmpty(authProviderKey))
{
return;
}

if (_ocelotSwaggerGenOptions.AuthenticationProviderKeyMap.TryGetValue(
authProviderKey,
out var securityScheme))
{
var method = path.First.First as JProperty;

while (method != null)
{
var securityProperty = new JProperty(OpenApiProperties.Security,
new JArray(
new JObject(
new JProperty(securityScheme,
new JArray(route.AuthenticationOptions?.AllowedScopes?.ToArray() ?? Array.Empty<string>())))));

((JObject)method.Value).Add(securityProperty);

method = method.Next as JProperty;
}
}
}

private static void RemoveItems<T>(JToken token, JToken paths, params Func<T, string>[] searchPaths)
where T : class
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

<EmbeddedResource Include="Tests\BasicConfiguration.json" />

<EmbeddedResource Include="Tests\BasicSecurityDefinition.json" />

<EmbeddedResource Include="Tests\Issue_186.json" />

<EmbeddedResource Include="Tests\Issue_149.json" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public async Task AllowVersionPlaceholder()
string swaggerJson,
IEnumerable<RouteOptions> routeOptions,
string serverOverride,
bool servers) => new SwaggerJsonTransformer()
bool servers) => new SwaggerJsonTransformer(OcelotSwaggerGenOptions.Default)
.Transform(swaggerJson,routeOptions, serverOverride, servers));
var swaggerForOcelotMiddleware = new SwaggerForOcelotMiddleware(
next.Invoke,
Expand Down Expand Up @@ -198,7 +198,7 @@ public async Task RespectDownstreamHttpVersionRouteSetting()
string swaggerJson,
IEnumerable<RouteOptions> routeOptions,
string serverOverride,
bool servers) => new SwaggerJsonTransformer()
bool servers) => new SwaggerJsonTransformer(OcelotSwaggerGenOptions.Default)
.Transform(swaggerJson, routeOptions, serverOverride, servers));
var swaggerForOcelotMiddleware = new SwaggerForOcelotMiddleware(
next.Invoke,
Expand Down
8 changes: 7 additions & 1 deletion tests/MMLib.SwaggerForOcelot.Tests/SwaggerForOcelotShould.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JsonDiffPatchDotNet;
using MMLib.SwaggerForOcelot.Configuration;
using MMLib.SwaggerForOcelot.Transformation;
using Newtonsoft.Json.Linq;
using System;
Expand All @@ -17,7 +18,12 @@ public class SwaggerForOcelotShould
[ClassData(typeof(TestCasesProvider))]
public void TransferDownstreamSwaggerToUpstreamFormat(TestCase testData)
{
var transformer = new SwaggerJsonTransformer();
var options = new OcelotSwaggerGenOptions();

foreach (var mapping in testData.AuthenticationProviderKeyMap)
options.AuthenticationProviderKeyMap.Add(mapping.Key, mapping.Value);

var transformer = new SwaggerJsonTransformer(options);

string transformed = transformer.Transform(
testData.DownstreamSwagger.ToString(),
Expand Down
6 changes: 4 additions & 2 deletions tests/MMLib.SwaggerForOcelot.Tests/TestCase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using MMLib.SwaggerForOcelot.Configuration;
using MMLib.SwaggerForOcelot.Configuration;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;

namespace MMLib.SwaggerForOcelot.Tests
{
Expand Down Expand Up @@ -47,6 +47,8 @@ public class TestCase
/// </value>
public bool TakeServersFromDownstreamService { get; set; } = false;

public Dictionary<string, string> AuthenticationProviderKeyMap { get; set; } = new();

/// <summary>
/// Test name.
/// </summary>
Expand Down
Loading

0 comments on commit 68a9401

Please sign in to comment.