Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add get-authorization-context policy support in compiler #106

Merged
merged 7 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/Authoring/Configs/GetAuthorizationContextConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.ApiManagement.PolicyToolkit.Authoring;

/// <summary>
/// Configuration for the get-authorization-context policy.
/// </summary>
public record GetAuthorizationContextConfig
{
/// <summary>
/// The credential provider resource identifier. Policy expressions are allowed.
/// </summary>
public required string ProviderId { get; init; }

/// <summary>
/// The connection resource identifier. Policy expressions are allowed.
/// </summary>
public required string AuthorizationId { get; init; }

/// <summary>
/// The name of the context variable to receive the Authorization object. Policy expressions are allowed.
/// </summary>
public required string ContextVariableName { get; init; }

/// <summary>
/// Type of identity to check against the connection's access policy. Default is "managed". Policy expressions are allowed.
/// </summary>
public string? IdentityType { get; init; } = "managed";

/// <summary>
/// A Microsoft Entra JWT bearer token to check against the connection permissions. Ignored for identity-type other than jwt. Policy expressions are allowed.
/// </summary>
public string? Identity { get; init; }

/// <summary>
/// Boolean. If acquiring the authorization context results in an error, the context variable is assigned a value of null if true, otherwise return 500. Default is false. Policy expressions are allowed.
/// </summary>
public bool? IgnoreError { get; init; } = false;
}
31 changes: 31 additions & 0 deletions src/Authoring/Expressions/Authorization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.ApiManagement.PolicyToolkit.Authoring.Expressions;

/// <summary>
/// Represents an authorization object with access token and claims.
/// </summary>
public class Authorization
{
/// <summary>
/// Gets the bearer access token to authorize a backend HTTP request.
/// </summary>
public string AccessToken { get; }

/// <summary>
/// Gets the claims returned from the authorization server's token response API.
/// </summary>
public IReadOnlyDictionary<string, object> Claims { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Authorization"/> class.
/// </summary>
/// <param name="accessToken">The bearer access token.</param>
/// <param name="claims">The claims returned from the authorization server's token response API.</param>
public Authorization(string accessToken, IReadOnlyDictionary<string, object> claims)
{
AccessToken = accessToken;
Claims = claims;
}
}
6 changes: 6 additions & 0 deletions src/Authoring/IInboundContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,10 @@ public interface IInboundContext : IHaveExpressionContext
/// <param name="config">Configuration of retry policy</param>
/// <param name="section">Child policies which should be retried</param>
void Retry(RetryConfig config, Action section);

/// <summary>
/// The get-authorization-context policy retrieves an authorization context from a specified provider.
/// </summary>
/// <param name="config">The configuration for the get-authorization-context policy.</param>
void GetAuthorizationContext(GetAuthorizationContextConfig config);
}
4 changes: 2 additions & 2 deletions src/Core/Compiling/CSharpPolicyCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Xml.Linq;
Expand Down Expand Up @@ -68,4 +68,4 @@ private void CompileSection(ICompilationContext context, string section, MethodD
_blockCompiler.Value.Compile(sectionContext, method.Body);
context.AddPolicy(sectionElement);
}
}
}
69 changes: 69 additions & 0 deletions src/Core/Compiling/Policy/GetAuthorizationContextCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Xml.Linq;

using Azure.ApiManagement.PolicyToolkit.Authoring;
using Azure.ApiManagement.PolicyToolkit.Compiling.Diagnostics;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Azure.ApiManagement.PolicyToolkit.Compiling.Policy;

public class GetAuthorizationContextCompiler : IMethodPolicyHandler
{
public string MethodName => nameof(IInboundContext.GetAuthorizationContext);

public void Handle(ICompilationContext context, InvocationExpressionSyntax node)
{
if (!node.TryExtractingConfigParameter<GetAuthorizationContextConfig>(
context,
"get-authorization-context",
out var values))
{
return;
}

var element = new XElement("get-authorization-context");

if (!element.AddAttribute(values, nameof(GetAuthorizationContextConfig.ProviderId), "provider-id"))
{
context.Report(Diagnostic.Create(
CompilationErrors.RequiredParameterNotDefined,
node.GetLocation(),
"get-authorization-context",
nameof(GetAuthorizationContextConfig.ProviderId)
));
return;
}

if (!element.AddAttribute(values, nameof(GetAuthorizationContextConfig.AuthorizationId), "authorization-id"))
{
context.Report(Diagnostic.Create(
CompilationErrors.RequiredParameterNotDefined,
node.GetLocation(),
"get-authorization-context",
nameof(GetAuthorizationContextConfig.AuthorizationId)
));
return;
}

if (!element.AddAttribute(values, nameof(GetAuthorizationContextConfig.ContextVariableName), "context-variable-name"))
{
context.Report(Diagnostic.Create(
CompilationErrors.RequiredParameterNotDefined,
node.GetLocation(),
"get-authorization-context",
nameof(GetAuthorizationContextConfig.ContextVariableName)
));
return;
}

element.AddAttribute(values, nameof(GetAuthorizationContextConfig.IdentityType), "identity-type");
element.AddAttribute(values, nameof(GetAuthorizationContextConfig.Identity), "identity");
element.AddAttribute(values, nameof(GetAuthorizationContextConfig.IgnoreError), "ignore-error");

context.AddPolicy(element);
}
}
131 changes: 131 additions & 0 deletions test/Test.Core/Compiling/GetAuthorizationContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.ApiManagement.PolicyToolkit.Compiling;

[TestClass]
public class GetAuthorizationContextTests
{
[DataTestMethod]
[DataRow(
"""
[Document]
public class TestDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.GetAuthorizationContext(new GetAuthorizationContextConfig
{
ProviderId = "provider-id",
AuthorizationId = "authorization-id",
ContextVariableName = "context-variable-name"
});
}
}
""",
"""
<policies>
<inbound>
<get-authorization-context provider-id="provider-id" authorization-id="authorization-id" context-variable-name="context-variable-name" />
</inbound>
</policies>
""",
DisplayName = "Should compile get-authorization-context policy with required properties"
)]
[DataRow(
"""
[Document]
public class TestDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.GetAuthorizationContext(new GetAuthorizationContextConfig
{
ProviderId = "provider-id",
AuthorizationId = "authorization-id",
ContextVariableName = "context-variable-name",
IdentityType = "jwt",
Identity = "jwt-token",
IgnoreError = true
});
}
}
""",
"""
<policies>
<inbound>
<get-authorization-context provider-id="provider-id" authorization-id="authorization-id" context-variable-name="context-variable-name" identity-type="jwt" identity="jwt-token" ignore-error="true" />
</inbound>
</policies>
""",
DisplayName = "Should compile get-authorization-context policy with all properties"
)]
[DataRow(
"""
[Document]
public class TestDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.GetAuthorizationContext(new GetAuthorizationContextConfig
{
ProviderId = GetProviderId(context.ExpressionContext),
AuthorizationId = GetAuthorizationId(context.ExpressionContext),
ContextVariableName = GetContextVariableName(context.ExpressionContext)
});
}

private string GetProviderId(IExpressionContext context) => context.Variables["provider-id"];
private string GetAuthorizationId(IExpressionContext context) => context.Variables["authorization-id"];
private string GetContextVariableName(IExpressionContext context) => context.Variables["context-variable-name"];
}
""",
"""
<policies>
<inbound>
<get-authorization-context provider-id="@(context.Variables["provider-id"])" authorization-id="@(context.Variables["authorization-id"])" context-variable-name="@(context.Variables["context-variable-name"])" />
</inbound>
</policies>
""",
DisplayName = "Should compile get-authorization-context policy with policy expressions"
)]
[DataRow(
"""
[Document]
public class TestDocument : IDocument
{
public void Inbound(IInboundContext context)
{
context.GetAuthorizationContext(new GetAuthorizationContextConfig
{
ProviderId = GetProviderId(context.ExpressionContext),
AuthorizationId = GetAuthorizationId(context.ExpressionContext),
ContextVariableName = GetContextVariableName(context.ExpressionContext),
IdentityType = GetIdentityType(context.ExpressionContext),
Identity = GetIdentity(context.ExpressionContext),
IgnoreError = GetIgnoreError(context.ExpressionContext)
});
}

private string GetProviderId(IExpressionContext context) => context.Variables["provider-id"];
private string GetAuthorizationId(IExpressionContext context) => context.Variables["authorization-id"];
private string GetContextVariableName(IExpressionContext context) => context.Variables["context-variable-name"];
private string GetIdentityType(IExpressionContext context) => context.Variables["identity-type"];
private string GetIdentity(IExpressionContext context) => context.Variables["identity"];
private bool GetIgnoreError(IExpressionContext context) => (bool)context.Variables["ignore-error"];
}
""",
"""
<policies>
<inbound>
<get-authorization-context provider-id="@(context.Variables["provider-id"])" authorization-id="@(context.Variables["authorization-id"])" context-variable-name="@(context.Variables["context-variable-name"])" identity-type="@(context.Variables["identity-type"])" identity="@(context.Variables["identity"])" ignore-error="@((bool)context.Variables["ignore-error"])" />
</inbound>
</policies>
""",
DisplayName = "Should compile get-authorization-context policy with policy expressions in all properties"
)]
public void ShouldCompileGetAuthorizationContextPolicy(string code, string expectedXml)
{
code.CompileDocument().Should().BeSuccessful().And.DocumentEquivalentTo(expectedXml);
}
}
Loading