Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion src/Auth/IAuthorizationResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public interface IAuthorizationResolver
/// <param name="operation">Operation type: Create, Read, Update, Delete.</param>
/// <param name="httpContext">Contains token claims of the authenticated user used in policy evaluation.</param>
/// <returns>Returns the parsed policy, if successfully processed, or an exception otherwise.</returns>
public string TryProcessDBPolicy(string entityName, string roleName, Operation operation, HttpContext httpContext);
public string ProcessDBPolicy(string entityName, string roleName, Operation operation, HttpContext httpContext);

/// <summary>
/// Get list of roles defined for entity within runtime configuration.. This is applicable for GraphQL when creating authorization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Azure.DataApiBuilder.Service.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using PermissionOperation = Azure.DataApiBuilder.Config.PermissionOperation;
Expand Down Expand Up @@ -966,10 +967,77 @@ public void ParseValidDbPolicy(string policy, string expectedParsedPolicy)
context.Setup(x => x.User).Returns(principal);
context.Setup(x => x.Request.Headers[AuthorizationResolver.CLIENT_ROLE_HEADER]).Returns(TEST_ROLE);

string parsedPolicy = authZResolver.TryProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
string parsedPolicy = authZResolver.ProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
Assert.AreEqual(parsedPolicy, expectedParsedPolicy);
}

/// <summary>
/// Tests authorization policy processing mechanism by validating value type compatibility
/// of claims present in HttpContext.User.Claims.
/// </summary>
/// <param name="claimValueType">Claim.ValueType which is a string, by definition.</param>
/// <param name="claimValue">Claim.Value which is a string, by definition.</param>
/// <param name="supportedValueType">Whether Claim.ValueType is supported by DAB engine</param>
/// <seealso cref="https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/9ddad8fc51ed2732622323612acad83f6629d5ba/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs#L76-L124"/>
/// <seealso cref="https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/59d1307a260829c0f8609a183a962aceaeffba89/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs#L82-L112"/>
#pragma warning disable format
[DataTestMethod]
[DataRow(ClaimValueTypes.String, "StringLiteral", true, DisplayName = "string")]
[DataRow(ClaimValueTypes.Boolean, "true", true, DisplayName = "bool")]
[DataRow(ClaimValueTypes.Integer, "65535", true, DisplayName = "short")]
[DataRow(ClaimValueTypes.Integer, "-2147483648", true, DisplayName = "int - Scenario 1")]
[DataRow(ClaimValueTypes.Integer32, "2147483647", true, DisplayName = "int - Scenario 2")]
[DataRow(ClaimValueTypes.Integer64, "9223372036854775807", true, DisplayName = "long")]
[DataRow(ClaimValueTypes.UInteger32, "4294967295", true, DisplayName = "uint")]
[DataRow(ClaimValueTypes.UInteger64, "18446744073709551615", true, DisplayName = "ulong")]
[DataRow(ClaimValueTypes.Double, "12.34", true, DisplayName = "decimal")]
[DataRow(ClaimValueTypes.Double, "12.345", true, DisplayName = "double")]
[DataRow(JsonClaimValueTypes.JsonNull, "null", true, DisplayName = "Json null literal")]
[DataRow(ClaimValueTypes.DateTime, "2022-11-30T22:57:57.5847834Z", false, DisplayName = "DateTime")]
[DataRow(JsonClaimValueTypes.Json, "{\"\"ext1\"\":\"\"ext1Value\"\"}", false, DisplayName = "Json object")]
[DataRow(JsonClaimValueTypes.JsonArray, "[{\"\"ext1\"\":\"\"ext1Value\"\"}]", false, DisplayName = "Json array")]
#pragma warning restore format
public void DbPolicy_ClaimValueTypeParsing(string claimValueType, string claimValue, bool supportedValueType)
{
// To adhere with OData 4 ABNF construction rules (Section 7: Literal Data Values)
// - Primitive string literals in URLS must be enclosed within single quotes.
// - http://docs.oasis-open.org/odata/odata/v4.01/cs01/abnf/odata-abnf-construction-rules.txt
string odataClaimValue = (claimValueType == ClaimValueTypes.String) ? "'" + claimValue + "'" : claimValue;
string expectedPolicy = "(" + odataClaimValue + ") eq col1";
string policyDefinition = "@claims.testClaim eq @item.col1";

RuntimeConfig runtimeConfig = InitRuntimeConfig(
entityName: TEST_ENTITY,
roleName: TEST_ROLE,
operation: TEST_OPERATION,
includedCols: new HashSet<string> { "col1" },
databasePolicy: policyDefinition);

AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

Mock<HttpContext> context = new();

//Add identity object to the Mock context object.
ClaimsIdentity identity = new(TEST_AUTHENTICATION_TYPE, TEST_CLAIMTYPE_NAME, AuthenticationConfig.ROLE_CLAIM_TYPE);
identity.AddClaim(new Claim("testClaim", claimValue, claimValueType));

ClaimsPrincipal principal = new(identity);
context.Setup(x => x.User).Returns(principal);
context.Setup(x => x.Request.Headers[AuthorizationResolver.CLIENT_ROLE_HEADER]).Returns(TEST_ROLE);

try
{
string parsedPolicy = authZResolver.ProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
Assert.IsTrue(supportedValueType);
Assert.AreEqual(expectedPolicy, parsedPolicy);
}
catch (DataApiBuilderException ex)
{
Assert.IsFalse(supportedValueType, message: ex.Message);
Assert.AreEqual(expected: AuthorizationResolver.INVALID_POLICY_CLAIM_MESSAGE, actual: ex.Message, message: ex.Message);
}
}

/// <summary>
/// Test to validate that we are correctly throwing an appropriate exception when the user request
/// lacks a claim required by the policy.
Expand Down Expand Up @@ -1003,7 +1071,7 @@ public void ParseInvalidDbPolicyWithUserNotPossessingAllClaims(string policy)

try
{
authZResolver.TryProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
authZResolver.ProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
}
catch (DataApiBuilderException ex)
{
Expand All @@ -1017,7 +1085,7 @@ public void ParseInvalidDbPolicyWithUserNotPossessingAllClaims(string policy)
/// duplicate role claims are ignored, so just checks policy is parsed as expected in this case
/// </summary>
/// <param name="exceptionExpected"> Whether we expect an exception (403 forbidden) to be thrown while parsing policy </param>
/// <param name="claims"> Parameter list of claim types/keys to add to the claims dictionary that can be accessed with @claims </param>
/// <param name="claimTypes"> Parameter list of claim types/keys to add to the claims dictionary that can be accessed with @claims </param>
[DataTestMethod]
[DataRow(true, AuthenticationConfig.ROLE_CLAIM_TYPE, "username", "guid", "username",
DisplayName = "duplicate claim expect exception")]
Expand Down Expand Up @@ -1055,7 +1123,7 @@ public void ParsePolicyWithDuplicateUserClaims(bool exceptionExpected, params st
{
try
{
authZResolver.TryProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
authZResolver.ProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
Assert.Fail();
}
catch (DataApiBuilderException ex)
Expand All @@ -1068,7 +1136,7 @@ public void ParsePolicyWithDuplicateUserClaims(bool exceptionExpected, params st
{
// If the role claim was the only duplicate, simply verify policy parsed as expected
string expectedPolicy = $"('{defaultClaimValue}') eq 1";
string parsedPolicy = authZResolver.TryProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
string parsedPolicy = authZResolver.ProcessDBPolicy(TEST_ENTITY, TEST_ROLE, TEST_OPERATION, context.Object);
Assert.AreEqual(expected: expectedPolicy, actual: parsedPolicy);
}
}
Expand Down Expand Up @@ -1118,7 +1186,7 @@ public void GetDBPolicyTest(
context.Setup(x => x.User).Returns(principal);
context.Setup(x => x.Request.Headers[AuthorizationResolver.CLIENT_ROLE_HEADER]).Returns(clientRole);

string parsedPolicy = authZResolver.TryProcessDBPolicy(TEST_ENTITY, clientRole, requestOperation, context.Object);
string parsedPolicy = authZResolver.ProcessDBPolicy(TEST_ENTITY, clientRole, requestOperation, context.Object);
string errorMessage = "TryProcessDBPolicy returned unexpected value.";
if (expectPolicy)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public void TestWildcardPolicyResolvesToEmpty(string httpMethod)
AuthorizationResolver authorizationResolver = SetupAuthResolverWithWildcardOperation();
HttpContext httpContext = CreateHttpContext(httpMethod: httpMethod, clientRole: "admin");

Assert.AreEqual(expected: string.Empty, actual: authorizationResolver.TryProcessDBPolicy(
Assert.AreEqual(expected: string.Empty, actual: authorizationResolver.ProcessDBPolicy(
entityName: AuthorizationHelpers.TEST_ENTITY,
roleName: "admin",
operation: RestService.HttpVerbToOperations(httpVerbName: httpMethod),
Expand Down
17 changes: 15 additions & 2 deletions src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,29 @@ public void VisitorLeftFieldGreaterThanRightNullFilterTest()
);
}

/// <summary>
/// Tests processed authorization policies (@claims.claimName eq @item.columnName) -> ('UserName' eq ScreenName)
/// against the custom OData Filter parser resolver ClaimsTypeDataUriResolver.
/// The columns xyz_types are sourced from type_table.
/// Constant values/literals in expressions are parsed by Microsoft.OData.UriParser.ExpressionLexer which
/// attempts to resolve value to its OData(EdmPrimitiveTypeKind) via
/// https://github.com/OData/odata.net/blob/f3bf65a74a7ed4028ff8074ccae31e4c2019772d/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs#L1206-L1221
/// </summary>
/// <param name="resolvedAuthZPolicyText">Filter parser input, the processed authorization policy</param>
/// <param name="errorExpected">Whether an OData Filter parser error is expected</param>
/// <seealso cref="https://learn.microsoft.com/dotnet/framework/data/adonet/sql/linq/sql-clr-type-mapping"/>
[DataTestMethod]
// Constant on left side and OData EDM object on right side of binary operator. (L->R)
[DataRow("'1' eq int_types", false, DisplayName = "L->R: Cast token claim of type string to integer, left to right ")]
[DataRow("'1' eq int_types", false, DisplayName = "L->R: Cast token claim of type string to integer")]
[DataRow("12.24 eq float_types", false, DisplayName = "L->R: Cast token claim of type single to type double (CLR) which maps to (SQL) float")]
[DataRow("'13B4F4EC-C45B-46EC-99F2-77BC22A256A7' eq guid_types", false, DisplayName = "L->R: Cast token claim of type string to GUID")]
[DataRow("'true' eq boolean_types", false, DisplayName = "L->R: Cast token claim of type string to bool (true)")]
[DataRow("'false' eq boolean_types", false, DisplayName = "L->R: Cast token claim of type string to bool (false)")]
[DataRow("1 eq string_types", false, DisplayName = "L->R: Cast token claim of type int to string")]
[DataRow("true eq string_types", false, DisplayName = "L->R: Cast token claim of type bool to string")]
// Constant on right side and OData EDM object on left side of binary operator. (R->L)
[DataRow("int_types eq '1'", false, DisplayName = "R->L: Cast token claim of type string to integer")]
[DataRow("float_types eq 12.24", false, DisplayName = "R->L: Cast token claim of type single to type double (CLR) which maps to (SQL) float")]
[DataRow("guid_types eq '13B4F4EC-C45B-46EC-99F2-77BC22A256A7'", false, DisplayName = "R->L: Cast token claim of type string to GUID")]
[DataRow("boolean_types eq 'true'", false, DisplayName = "R->L: Cast token claim of type string to bool (true)")]
[DataRow("boolean_types eq 'false'", false, DisplayName = "R->L: Cast token claim of type string to bool (false)")]
Expand Down Expand Up @@ -143,7 +156,7 @@ public void CustomODataUriParserResolverTest(string resolvedAuthZPolicyText, boo
catch (Exception e) when (e is DataApiBuilderException || e is ODataException)
{
Assert.IsTrue(errorExpected, message: "Filter clause creation was not expected to fail.");
Assert.IsTrue(e.Message.Contains(expectedErrorMessageFragment));
Assert.IsTrue(e.Message.Contains(expectedErrorMessageFragment), message: e.Message);
}
}
#endregion
Expand Down
Loading