Skip to content

Commit

Permalink
Some initial works
Browse files Browse the repository at this point in the history
  • Loading branch information
queil committed Jan 4, 2022
1 parent dd90681 commit 32e375e
Show file tree
Hide file tree
Showing 44 changed files with 1,962 additions and 3 deletions.
36 changes: 33 additions & 3 deletions src/HotChocolate/AspNetCore/HotChocolate.AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.StarWars.Tests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.Subscriptions.InMemory", "..\Core\src\Subscriptions.InMemory\HotChocolate.Subscriptions.InMemory.csproj", "{86C6C9FB-3AF3-4DB6-AEC2-0F3941FEA664}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Execution", "..\Core\src\Execution\HotChocolate.Execution.csproj", "{9957A20C-4DCC-4643-B97D-ACF00D75C702}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.Execution", "..\Core\src\Execution\HotChocolate.Execution.csproj", "{9957A20C-4DCC-4643-B97D-ACF00D75C702}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore.Authorization", "src\AspNetCore.Authorization\HotChocolate.AspNetCore.Authorization.csproj", "{3DA60297-97CB-4712-89C6-F71EB75F98EA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.AspNetCore.Authorization", "src\AspNetCore.Authorization\HotChocolate.AspNetCore.Authorization.csproj", "{3DA60297-97CB-4712-89C6-F71EB75F98EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore.Authorization.Tests", "test\AspNetCore.Authorization.Tests\HotChocolate.AspNetCore.Authorization.Tests.csproj", "{CBADBC3F-FACC-424C-8E7D-28A029F5C238}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.AspNetCore.Authorization.Tests", "test\AspNetCore.Authorization.Tests\HotChocolate.AspNetCore.Authorization.Tests.csproj", "{CBADBC3F-FACC-424C-8E7D-28A029F5C238}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.AspNetCore.Authorization.Opa", "src\AspNetCore.Authorization.Opa\HotChocolate.AspNetCore.Authorization.Opa.csproj", "{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotChocolate.AspNetCore.Authorization.Opa.Tests", "test\AspNetCore.Authorization.Opa.Tests\HotChocolate.AspNetCore.Authorization.Opa.Tests.csproj", "{30A5797F-205D-4FA4-976D-8F707139CE51}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -249,6 +253,30 @@ Global
{CBADBC3F-FACC-424C-8E7D-28A029F5C238}.Release|x64.Build.0 = Release|Any CPU
{CBADBC3F-FACC-424C-8E7D-28A029F5C238}.Release|x86.ActiveCfg = Release|Any CPU
{CBADBC3F-FACC-424C-8E7D-28A029F5C238}.Release|x86.Build.0 = Release|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Debug|x64.ActiveCfg = Debug|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Debug|x64.Build.0 = Debug|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Debug|x86.ActiveCfg = Debug|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Debug|x86.Build.0 = Debug|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Release|Any CPU.Build.0 = Release|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Release|x64.ActiveCfg = Release|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Release|x64.Build.0 = Release|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Release|x86.ActiveCfg = Release|Any CPU
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87}.Release|x86.Build.0 = Release|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Debug|x64.ActiveCfg = Debug|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Debug|x64.Build.0 = Debug|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Debug|x86.ActiveCfg = Debug|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Debug|x86.Build.0 = Debug|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Release|Any CPU.Build.0 = Release|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Release|x64.ActiveCfg = Release|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Release|x64.Build.0 = Release|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Release|x86.ActiveCfg = Release|Any CPU
{30A5797F-205D-4FA4-976D-8F707139CE51}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -270,6 +298,8 @@ Global
{9957A20C-4DCC-4643-B97D-ACF00D75C702} = {8A75EB03-3E25-4819-AE7D-1159D5AED817}
{3DA60297-97CB-4712-89C6-F71EB75F98EA} = {2E2070DF-95C2-48F2-A8DF-7FE3734817ED}
{CBADBC3F-FACC-424C-8E7D-28A029F5C238} = {936FF2E5-6576-4257-A7A3-F2093D44E6CD}
{6CFCB82F-4A89-496A-A261-3FEBDD8EEA87} = {2E2070DF-95C2-48F2-A8DF-7FE3734817ED}
{30A5797F-205D-4FA4-976D-8F707139CE51} = {936FF2E5-6576-4257-A7A3-F2093D44E6CD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EAA92712-961A-4595-82AD-C031830477CC}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="6.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(LibraryTargetFrameworks)</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Core\src\Types\HotChocolate.Types.csproj" />
<ProjectReference Include="..\AspNetCore.Authorization\HotChocolate.AspNetCore.Authorization.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#if NET6_0
using System.Net.Http.Json;
#endif
using System.Text.Json;
using HotChocolate.Resolvers;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.AspNetCore.Authorization;

public sealed class QueryResponse
{
public Guid? DecisionId { get; set; }
public bool Result { get; set; }
}

public sealed class QueryRequest
{
public Input Input { get; set; } = Input.Empty;
}

internal static class OpaHttpExtensions
{
internal static HttpContent ToJsonContent(this QueryRequest request, JsonSerializerOptions options)
{
#if NET6_0
return JsonContent.Create(request, options: options);
#else
var body = JsonSerializer.Serialize(request, options);
return new StringContent(body, System.Text.Encoding.UTF8, "application/json");
#endif
}

internal static async Task<QueryResponse?> QueryResponseFromJsonAsync(this HttpContent content, JsonSerializerOptions options, CancellationToken token)
{
#if NET6_0
return await content.ReadFromJsonAsync<QueryResponse>(options, token);
#else
return await JsonSerializer.DeserializeAsync<QueryResponse>(await content.ReadAsStreamAsync(), options, token);
#endif
}
}

public sealed class Input
{
public static readonly Input Empty = new();
}

public interface IOpaService
{
Task<QueryResponse?> QueryAsync(string policyPath, QueryRequest request, CancellationToken token);
}

public sealed class OpaOptions
{
public JsonSerializerOptions JsonSerializerOptions { get; set; } = new JsonSerializerOptions();
}

public sealed class OpaService : IOpaService
{
private readonly HttpClient _httpClient;
private readonly OpaOptions _options;

public OpaService(HttpClient httpClient, OpaOptions options)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
}

public async Task<QueryResponse?> QueryAsync(string policyPath, QueryRequest request, CancellationToken token)
{
if (policyPath is null) throw new ArgumentNullException(nameof(policyPath));
if (request is null) throw new ArgumentNullException(nameof(request));

HttpResponseMessage? response = await _httpClient.PostAsync(policyPath, request.ToJsonContent(_options.JsonSerializerOptions), token);
response.EnsureSuccessStatusCode();
return await response.Content.QueryResponseFromJsonAsync(_options.JsonSerializerOptions, token);
}
}


public interface IOpaDecision
{
AuthorizeResult Map(QueryResponse? response);
}

public sealed class OpaDecision : IOpaDecision
{
public AuthorizeResult Map(QueryResponse? response)
{
if (response is not null && response.Result)
{
return AuthorizeResult.Allowed;
}
return AuthorizeResult.NotAllowed;
}
}

/// <summary>
/// An implementation that delegates authz to OPA (Open Policy Agent) REST API endpoint
/// </summary>
public class OpaAuthorizationHandler : IAuthorizationHandler
{
/// <summary>
/// Authorize current directive using Microsoft.AspNetCore.Authorization.
/// </summary>
/// <param name="context">The current middleware context.</param>
/// <param name="directive">The authorization directive.</param>
/// <returns>
/// Returns a value indicating if the current session is authorized to
/// access the resolver data.
/// </returns>
public async ValueTask<AuthorizeResult> AuthorizeAsync(
IMiddlewareContext context,
AuthorizeDirective directive)
{
IOpaService? opaService = context.Services.GetRequiredService<IOpaService>();
IOpaDecision? opaDecision = context.Services.GetRequiredService<IOpaDecision>();
var request = new QueryRequest { Input = new Input()};
QueryResponse? response = await opaService.QueryAsync(directive.Policy ?? string.Empty, request, context.RequestAborted);
return opaDecision.Map(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Collections;
using System.Collections.Generic;
using HotChocolate.Execution.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.AspNetCore.Authorization;

public class AuthorizationAttributeTestData : IEnumerable<object[]>
{
public class Query
{
[Authorize]
public string GetDefault() => "foo";

[Authorize(Policy = "HasAgeDefined")]
public string GetAge() => "foo";

[Authorize(Roles = new[] { "a" })]
public string GetRoles() => "foo";

[Authorize(Roles = new[] { "a", "b" })]
[GraphQLName("roles_ab")]
public string GetRolesAb() => "foo";

[Authorize(Policy = "a")]
[Authorize(Policy = "b")]
public string GetPiped() => "foo";

[Authorize(Policy = "a", Apply = ApplyPolicy.AfterResolver)]
public string GetAfterResolver() => "foo";
}

private Action<IRequestExecutorBuilder> CreateSchema() =>
builder => builder
.AddQueryType<Query>()
.AddAuthorization();

public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { CreateSchema() };
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Threading.Tasks;
using HotChocolate.Execution;
using HotChocolate.Resolvers;
using HotChocolate.Tests;
using HotChocolate.Types;
using Microsoft.Extensions.DependencyInjection;
using Snapshooter;
using Snapshooter.Xunit;
using Xunit;

namespace HotChocolate.AspNetCore.Authorization;

public class AuthorizationHandlerTests
{
[InlineData(AuthorizeResult.Allowed)]
[InlineData(AuthorizeResult.NoDefaultPolicy)]
[InlineData(AuthorizeResult.NotAllowed)]
[InlineData(AuthorizeResult.NotAuthenticated)]
[InlineData(AuthorizeResult.PolicyNotFound)]
[Theory]
public async Task Authorize(AuthorizeResult result)
{
Snapshot.FullName(new SnapshotNameExtension(result));

await new ServiceCollection()
.AddGraphQLServer()
.AddQueryType()
.AddTypeExtension<QueryExtensions>()
.AddAuthorizationHandler<OpaAuthorizationHandler>()
.ExecuteRequestAsync(
QueryRequestBuilder
.New()
.SetQuery("{ bar }")
.AddProperty("auth", result)
.Create())
.MatchSnapshotAsync();
}

[Authorize]
[ExtendObjectType(OperationTypeNames.Query)]
public class QueryExtensions
{
public string Bar() => "bar";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Collections.Generic;
using HotChocolate.Execution.Configuration;
using HotChocolate.Resolvers;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.AspNetCore.Authorization;

public class AuthorizationTestData : IEnumerable<object[]>
{
private readonly string SchemaCode = @"
type Query {
default: String @authorize
age: String @authorize(policy: ""HasAgeDefined"")
roles: String @authorize(roles: [""a""])
roles_ab: String @authorize(roles: [""a"" ""b""])
piped: String
@authorize(policy: ""a"")
@authorize(policy: ""b"")
afterResolver: String
@authorize(policy: ""a"" apply: AFTER_RESOLVER)
}
";

private readonly FieldMiddleware _schemaMiddleware = next => context =>
{
context.Result = "foo";
return next.Invoke(context);
};

private Action<IRequestExecutorBuilder> CreateSchema() =>
sb => sb
.AddDocumentFromString(SchemaCode)
.AddAuthorization()
.UseField(_schemaMiddleware);

private Action<IRequestExecutorBuilder> CreateSchemaWithBuilder() =>
sb => sb
.AddDocumentFromString(SchemaCode)
.AddAuthorization()
.UseField(_schemaMiddleware);

public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { CreateSchema() };
yield return new object[] { CreateSchemaWithBuilder() };
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Loading

0 comments on commit 32e375e

Please sign in to comment.