Skip to content
Closed
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
11 changes: 7 additions & 4 deletions DataGateway.Auth/AuthorizationMetadataHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ public class EntityMetadata
public Dictionary<string, RoleMetadata> RoleToActionMap { get; set; } = new();

/// <summary>
/// Given the key (actionName) returns a key/value collection of fieldName to Roles
/// i.e. READ action
/// Key(field): id -> Value(collection): permitted in {Role1, Role2, ..., RoleN}
/// Key(field): title -> Value(collection): permitted in {Role1}
/// Field to action to role mapping.
/// Given the key (Field aka. column name) returns a key/value collection of action to Roles
/// i.e. ID column
/// Key(field): id -> Dictionary(actions)
/// each entry in the dictionary contains action to role map.
/// create: permitted in {Role1, Role2, ..., RoleN}
/// delete: permitted in {Role1, RoleN}
/// </summary>
public Dictionary<string, Dictionary<string, List<string>>> FieldToRolesMap { get; set; } = new();

Expand Down
9 changes: 7 additions & 2 deletions DataGateway.Auth/IAuthorizationResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,19 @@ public interface IAuthorizationResolver
/// <param name="entityName">Entity to lookup permissions</param>
/// <param name="actionName">Action to lookup applicable roles</param>
/// <returns>Collection of roles. Empty list if entityPermissionsMap is null.</returns>
public static IEnumerable<string> GetRolesForAction(string entityName, string actionName, Dictionary<string, EntityMetadata>? entityPermissionsMap)
public static IEnumerable<string> GetRolesForAction(
string entityName,
string actionName,
Dictionary<string, EntityMetadata>? entityPermissionsMap)
{
if (entityName is null)
{
throw new ArgumentNullException(paramName: "entityName");
}

if (entityPermissionsMap is not null && entityPermissionsMap[entityName].ActionToRolesMap.TryGetValue(actionName, out List<string>? roleList) && roleList is not null)
if (entityPermissionsMap is not null &&
entityPermissionsMap[entityName].ActionToRolesMap.TryGetValue(actionName, out List<string>? roleList) &&
roleList is not null)
{
return roleList;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Text.Json;
Expand Down Expand Up @@ -87,7 +88,9 @@ public void NoRoleHeader_RoleContextTest()
Assert.AreEqual(authZResolver.IsValidRoleContext(context.Object), expected);
}
#endregion

#region Role and Action on Entity Tests

/// <summary>
/// Tests the AreRoleAndActionDefinedForEntity stage of authorization.
/// Request Action is defined for role -> VALID
Expand All @@ -110,8 +113,46 @@ public void AreRoleAndActionDefinedForEntityTest(
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

// Mock Request Values
Assert.AreEqual(authZResolver.AreRoleAndActionDefinedForEntity(AuthorizationHelpers.TEST_ENTITY, roleName, actionName), expected);
Assert.AreEqual(expected, authZResolver.AreRoleAndActionDefinedForEntity(AuthorizationHelpers.TEST_ENTITY, roleName, actionName));
}

/// <summary>
/// Test that wildcard actions are expanded to explicit actions.
/// Verifies that internal data structure are created correctly.
/// </summary>
[TestMethod]
public void TestWildcardAction()
{
string roleName = "myRole";
List<string> expectedRoles = new() { roleName };

RuntimeConfig runtimeConfig = AuthorizationHelpers.InitRuntimeConfig(AuthorizationHelpers.TEST_ENTITY, roleName, AuthorizationResolver.WILDCARD);

// Override the action to be a list of string for wildcard instead of a list of object created by InitRuntimeConfig()
//
runtimeConfig.Entities[AuthorizationHelpers.TEST_ENTITY].Permissions[0].Actions = new object[] { JsonSerializer.SerializeToElement(AuthorizationResolver.WILDCARD) };
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

// There should not be a wildcard action in AuthorizationResolver.EntityPermissionsMap
//
Assert.IsFalse(authZResolver.AreRoleAndActionDefinedForEntity(AuthorizationHelpers.TEST_ENTITY, roleName, AuthorizationResolver.WILDCARD));

// All the wildcard action should be expand to explicit actions.
//
string[] allAvailableActions = { ActionType.READ, ActionType.CREATE, ActionType.DELETE, ActionType.UPDATE };
foreach (string action in allAvailableActions)
{
Assert.IsTrue(authZResolver.AreRoleAndActionDefinedForEntity(AuthorizationHelpers.TEST_ENTITY, roleName, action));

IEnumerable<string> actualRolesForCol1 = authZResolver.GetRolesForField(AuthorizationHelpers.TEST_ENTITY, "col1", action);

CollectionAssert.AreEquivalent(expectedRoles, actualRolesForCol1.ToList());
}

IEnumerable<string> actualRolesForAction = authZResolver.GetRolesForAction(AuthorizationHelpers.TEST_ENTITY, ActionType.CREATE);
CollectionAssert.AreEquivalent(expectedRoles, actualRolesForAction.ToList());
}

#endregion

/// <summary>
Expand Down Expand Up @@ -165,7 +206,7 @@ public void WildcardIncludeColDefinedForAction_ColsForActionTest()
AuthorizationHelpers.TEST_ENTITY,
AuthorizationHelpers.TEST_ROLE,
ActionType.CREATE,
includedCols: new HashSet<string> { "*" }
includedCols: new HashSet<string> { AuthorizationResolver.WILDCARD }
);
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

Expand All @@ -183,7 +224,7 @@ public void WildcardIncludeColsSomeExcludeDefinedForActionSuccess_ColsForActionT
AuthorizationHelpers.TEST_ENTITY,
AuthorizationHelpers.TEST_ROLE,
ActionType.CREATE,
includedCols: new HashSet<string> { "*" },
includedCols: new HashSet<string> { AuthorizationResolver.WILDCARD },
excludedCols: new HashSet<string> { "col1", "col2" }
);
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);
Expand Down Expand Up @@ -278,7 +319,7 @@ public void WildcardExcludeColsDefinedForAction_ColsForActionTest()
AuthorizationHelpers.TEST_ENTITY,
AuthorizationHelpers.TEST_ROLE,
ActionType.CREATE,
excludedCols: new HashSet<string> { "*" }
excludedCols: new HashSet<string> { AuthorizationResolver.WILDCARD }
);
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

Expand All @@ -296,7 +337,7 @@ public void WildcardIncludeColsSomeExcludeDefinedForAction_ColsForActionTest()
AuthorizationHelpers.TEST_ENTITY,
AuthorizationHelpers.TEST_ROLE,
ActionType.CREATE,
includedCols: new HashSet<string> { "*" },
includedCols: new HashSet<string> { AuthorizationResolver.WILDCARD },
excludedCols: new HashSet<string> { "col1", "col2" }
);
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);
Expand All @@ -316,7 +357,7 @@ public void WildcardExcludeColsSomeIncludeDefinedForAction_ColsForActionTest()
AuthorizationHelpers.TEST_ROLE,
ActionType.CREATE,
includedCols: new HashSet<string> { "col1", "col2" },
excludedCols: new HashSet<string> { "*" }
excludedCols: new HashSet<string> { AuthorizationResolver.WILDCARD }
);
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

Expand All @@ -335,7 +376,7 @@ public void WildcardExcludeColsSomeIncludeDefinedForActionSuccess_ColsForActionT
AuthorizationHelpers.TEST_ROLE,
ActionType.CREATE,
includedCols: new HashSet<string> { "col1", "col2" },
excludedCols: new HashSet<string> { "*" }
excludedCols: new HashSet<string> { AuthorizationResolver.WILDCARD }
);
AuthorizationResolver authZResolver = AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.DataGateway.Auth;
using Azure.DataGateway.Config;
Expand Down Expand Up @@ -346,15 +347,20 @@ IEnumerable<string> columnsRequested
}

/// <summary>
/// Sets up an authorization resolver with a config that specifies the wildcard ("*") as the test entity's actions
/// Sets up an authorization resolver with a config that specifies the wildcard ("*") as the test entity's actions.
/// Explicitly use this instead of AuthorizationHelpers.InitRuntimeConfig() because we want to create actions as
/// array of string instead of array of object.
/// </summary>
private static AuthorizationResolver SetupAuthResolverWithWildcardActions()
{
RuntimeConfig runtimeConfig = AuthorizationHelpers.InitRuntimeConfig(
entityName: AuthorizationHelpers.TEST_ENTITY,
roleName: "admin",
actionName: "*"
);
actionName: "*");

// Override the action to be a list of string for wildcard instead of a list of object created by InitRuntimeConfig()
//
runtimeConfig.Entities[AuthorizationHelpers.TEST_ENTITY].Permissions[0].Actions = new object[] { JsonSerializer.SerializeToElement(AuthorizationResolver.WILDCARD) };

return AuthorizationHelpers.InitAuthorizationResolver(runtimeConfig);
}
Expand Down
84 changes: 49 additions & 35 deletions DataGateway.Service/Authorization/AuthorizationResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace Azure.DataGateway.Service.Authorization
public class AuthorizationResolver : IAuthorizationResolver
{
private ISqlMetadataProvider _metadataProvider;
private const string WILDCARD = "*";
public const string WILDCARD = "*";
public const string CLAIM_PREFIX = "@claims.";
public const string FIELD_PREFIX = "@item.";
public const string CLIENT_ROLE_HEADER = "X-MS-API-ROLE";
Expand Down Expand Up @@ -102,8 +102,7 @@ public bool AreRoleAndActionDefinedForEntity(string entityName, string roleName,
{
if (valueOfEntityToRole.RoleToActionMap.TryGetValue(roleName, out RoleMetadata? valueOfRoleToAction))
{
if (valueOfRoleToAction!.ActionToColumnMap.ContainsKey(WILDCARD) ||
valueOfRoleToAction!.ActionToColumnMap.ContainsKey(action))
if (valueOfRoleToAction!.ActionToColumnMap.ContainsKey(action))
{
return true;
}
Expand All @@ -122,14 +121,7 @@ public bool AreColumnsAllowedForAction(string entityName, string roleName, strin
ActionMetadata actionToColumnMap;
RoleMetadata roleInEntity = EntityPermissionsMap[entityName].RoleToActionMap[roleName];

try
{
actionToColumnMap = roleInEntity.ActionToColumnMap[actionName];
}
catch (KeyNotFoundException)
{
actionToColumnMap = roleInEntity.ActionToColumnMap[WILDCARD];
}
actionToColumnMap = roleInEntity.ActionToColumnMap[actionName];

// Each column present in the request is an "exposedColumn".
// Authorization permissions reference "backingColumns"
Expand All @@ -140,8 +132,8 @@ public bool AreColumnsAllowedForAction(string entityName, string roleName, strin
if (_metadataProvider.TryGetBackingColumn(entityName, field: exposedColumn, out string? backingColumn))
{
// backingColumn will not be null when TryGetBackingColumn() is true.
if (actionToColumnMap.Excluded.Contains(backingColumn!) || actionToColumnMap.Excluded.Contains(WILDCARD) ||
!(actionToColumnMap.Included.Contains(WILDCARD) || actionToColumnMap.Included.Contains(backingColumn!)))
if (actionToColumnMap.Excluded.Contains(backingColumn!) ||
!actionToColumnMap.Included.Contains(backingColumn!))
{
// If column is present in excluded OR excluded='*'
// If column is absent from included and included!=*
Expand Down Expand Up @@ -189,20 +181,7 @@ private string GetDBPolicyForRequest(string entityName, string roleName, string
RoleMetadata roleMetadata = EntityPermissionsMap[entityName].RoleToActionMap[roleName];
roleMetadata.ActionToColumnMap.TryGetValue(action, out ActionMetadata? actionMetadata);

// If action exists in map (explicitly specified in config), use its policy
// action should only be absent in roleMetadata if WILDCARD is in the map instead of specific actions,
// as authorization happens before policy parsing (would have already returned forbidden)
string? dbPolicy;
if (actionMetadata is not null)
{
dbPolicy = actionMetadata.DatabasePolicy;

} // else check if wildcard exists in action map, if so use its policy, else null
else
{
roleMetadata.ActionToColumnMap.TryGetValue(WILDCARD, out ActionMetadata? wildcardMetadata);
dbPolicy = wildcardMetadata is not null ? wildcardMetadata.DatabasePolicy : null;
}
string? dbPolicy = actionMetadata!.DatabasePolicy;

return dbPolicy is not null ? dbPolicy : string.Empty;
}
Expand Down Expand Up @@ -289,18 +268,31 @@ public void SetEntityPermissionMap(RuntimeConfig? runtimeConfig)

// Try to add the actionName to the map if not present.
// Builds up mapping: i.e. ActionType.CREATE permitted in {Role1, Role2, ..., RoleN}
if (!string.IsNullOrWhiteSpace(actionName) && !entityToRoleMap.ActionToRolesMap.TryAdd(actionName, new List<string>(new string[] { role })))
// Expand wildcard action to explicit action type.
//
if (actionName.Equals(WILDCARD))
{
entityToRoleMap.ActionToRolesMap[actionName].Add(role);
}
string[] allAvailableActions = { ActionType.READ, ActionType.CREATE, ActionType.DELETE, ActionType.UPDATE };

foreach (string allowedColumn in actionToColumn.Allowed)
{
entityToRoleMap.FieldToRolesMap.TryAdd(key: allowedColumn, CreateActionToRoleMap());
entityToRoleMap.FieldToRolesMap[allowedColumn][actionName].Add(role);
foreach (string action in allAvailableActions)
{
PopulateActionToRoleMap(entityToRoleMap.ActionToRolesMap, action, role);

PopulateFieldToRoleMap(entityToRoleMap.FieldToRolesMap, role, action, actionToColumn);

roleToAction.ActionToColumnMap[action] = actionToColumn;
}
}
// Otherwise, we know explicit action name. Just add that.
//
else if (!string.IsNullOrWhiteSpace(actionName))
{
PopulateActionToRoleMap(entityToRoleMap.ActionToRolesMap, actionName, role);

PopulateFieldToRoleMap(entityToRoleMap.FieldToRolesMap, role, actionName, actionToColumn);

roleToAction.ActionToColumnMap[actionName] = actionToColumn;
roleToAction.ActionToColumnMap[actionName] = actionToColumn;
}
}

entityToRoleMap.RoleToActionMap[role] = roleToAction;
Expand All @@ -310,6 +302,28 @@ public void SetEntityPermissionMap(RuntimeConfig? runtimeConfig)
}
}

private static void PopulateFieldToRoleMap(
Dictionary<string, Dictionary<string, List<string>>> fieldToRolesMap,
string role,
string actionName,
ActionMetadata actionToColumn)
{
foreach (string allowedColumn in actionToColumn.Allowed)
{
fieldToRolesMap.TryAdd(key: allowedColumn, CreateActionToRoleMap());

fieldToRolesMap[allowedColumn][actionName].Add(role);
}
}

private static void PopulateActionToRoleMap(Dictionary<string, List<string>> actionToRolesMap, string actionName, string role)
{
if (!actionToRolesMap.TryAdd(actionName, new List<string>(new string[] { role })))
{
actionToRolesMap[actionName].Add(role);
}
}

/// <inheritdoc />
public IEnumerable<string> GetAllowedColumns(string entityName, string roleName, string action)
{
Expand Down
Loading