Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e16364b
update test'
jarupatj Jul 19, 2022
59519d0
Update * string to constant variable.
jarupatj Jul 19, 2022
a37b8fa
Update code formatting
jarupatj Jul 20, 2022
32982ea
Expand wildcard actions
jarupatj Jul 20, 2022
4fc4b22
Wildcard action gets resolved to CRUD actions
Jul 20, 2022
c024972
Remove wildcard special case from authorization checks.
jarupatj Jul 20, 2022
0aba931
Adding test cases for wildcard action
Jul 20, 2022
882fbf5
Add unittests for wildcard handling
jarupatj Jul 20, 2022
cfdcf04
Add tests that verify internal data structure when wildcard is used.
jarupatj Jul 20, 2022
d60e75c
Using defined constants for wildcard and actions
Jul 20, 2022
d017d5f
Merge branch 'main' into dev/agarwalayush/WildcardActionSupport
ayush3797 Jul 20, 2022
a507939
Using Assert true/false
Jul 20, 2022
f441e66
Merge branch 'dev/agarwalayush/WildcardActionSupport' of https://gith…
Jul 20, 2022
838631f
Add more test.
jarupatj Jul 20, 2022
656425f
Merge branch 'jarupatj/wildcardissue' into dev/agarwalayush/WildcardA…
jarupatj Jul 20, 2022
1161320
Add more comprehensive tests for AuthorizationResolver
jarupatj Jul 20, 2022
668c901
Clean up include exclude column test. Remove unnessary duplicate test…
jarupatj Jul 20, 2022
7b5e3b1
Add test comments and fix expected behavior of WildcardColumnExclusio…
jarupatj Jul 20, 2022
872f196
Run dotnet format
jarupatj Jul 20, 2022
ce0e263
Apply suggestions from code review
jarupatj Jul 20, 2022
43814e8
Merge branch 'dev/agarwalayush/WildcardActionSupport' of https://gith…
jarupatj Jul 20, 2022
fe2f828
Address comments.
jarupatj Jul 20, 2022
f787e6b
Add test more test case to check exclusion precedence.
jarupatj Jul 20, 2022
f283d6d
Update format using dotnet format
jarupatj Jul 20, 2022
9643a3a
Merge branch 'main' into dev/agarwalayush/WildcardActionSupport
jarupatj Jul 20, 2022
a85e1f6
Merge branch 'main' into dev/agarwalayush/WildcardActionSupport
Aniruddh25 Jul 20, 2022
bb2bbc1
Merge branch 'main' into dev/agarwalayush/WildcardActionSupport
Aniruddh25 Jul 20, 2022
4430c7e
Merge branch 'main' into dev/agarwalayush/WildcardActionSupport
jarupatj Jul 20, 2022
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

Large diffs are not rendered by default.

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
81 changes: 37 additions & 44 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 @@ -119,17 +118,7 @@ public bool AreColumnsAllowedForAction(string entityName, string roleName, strin
// Columns.Count() will never be zero because this method is called after a check ensures Count() > 0
Assert.IsFalse(columns.Count() == 0, message: "columns.Count() should be greater than 0.");

ActionMetadata actionToColumnMap;
RoleMetadata roleInEntity = EntityPermissionsMap[entityName].RoleToActionMap[roleName];

try
{
actionToColumnMap = roleInEntity.ActionToColumnMap[actionName];
}
catch (KeyNotFoundException)
{
actionToColumnMap = roleInEntity.ActionToColumnMap[WILDCARD];
}
ActionMetadata actionToColumnMap = EntityPermissionsMap[entityName].RoleToActionMap[roleName].ActionToColumnMap[actionName];

// Each column present in the request is an "exposedColumn".
// Authorization permissions reference "backingColumns"
Expand All @@ -140,8 +129,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) ||
Comment thread
Aniruddh25 marked this conversation as resolved.
!(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 +178,8 @@ 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;
}
// Get the database policy for the specified action.
string? dbPolicy = actionMetadata!.DatabasePolicy;

return dbPolicy is not null ? dbPolicy : string.Empty;
}
Expand All @@ -227,15 +204,15 @@ public void SetEntityPermissionMap(RuntimeConfig? runtimeConfig)
object[] Actions = permission.Actions;
foreach (JsonElement actionElement in Actions)
{
string actionName = string.Empty;
string action = string.Empty;
ActionMetadata actionToColumn = new();
IEnumerable<string> allTableColumns = ResolveTableDefinitionColumns(entityName);

// Implicitly, all table columns are 'allowed' when an actiontype is a string.
// Since no granular field permissions exist for this action within the current role.
if (actionElement.ValueKind is JsonValueKind.String)
{
actionName = actionElement.ToString();
action = actionElement.ToString();
actionToColumn.Included.UnionWith(allTableColumns);
actionToColumn.Allowed.UnionWith(allTableColumns);
}
Expand All @@ -246,7 +223,7 @@ public void SetEntityPermissionMap(RuntimeConfig? runtimeConfig)
if (RuntimeConfig.TryGetDeserializedConfig(actionElement.ToString(), out Action? actionObj)
&& actionObj is not null)
{
actionName = actionObj.Name;
action = actionObj.Name;
if (actionObj.Fields!.Include is not null)
{
// When a wildcard (*) is defined for Included columns, all of the table's
Expand Down Expand Up @@ -287,20 +264,25 @@ 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 })))
IEnumerable<string> actionNames = GetAllActions(action);
foreach (string actionName in actionNames)
{
entityToRoleMap.ActionToRolesMap[actionName].Add(role);
}
// 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 })))
{
entityToRoleMap.ActionToRolesMap[actionName].Add(role);
}

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

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

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

/// <summary>
/// Helper method to create a list consisting of the given action name.
/// In case the action is a wildcard(*), it gets resolved to a set of CRUD operations.
/// </summary>
/// <param name="action">Action name.</param>
/// <returns>IEnumerable of all available action name</returns>
private static IEnumerable<string> GetAllActions(string action)
{
return WILDCARD.Equals(action) ? RuntimeConfigValidator.ValidActions : new List<string> { action };
}

/// <inheritdoc />
public IEnumerable<string> GetAllowedColumns(string entityName, string roleName, string action)
{
Expand Down
14 changes: 7 additions & 7 deletions DataGateway.Service/Configurations/RuntimeConfigValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class RuntimeConfigValidator : IConfigValidator
private static readonly string _claimChars = @"@claims\.[^\s\)]*";

// Set of allowed actions for a request.
private static readonly HashSet<string> _validActions = new() { ActionType.CREATE, ActionType.READ, ActionType.UPDATE, ActionType.DELETE };
public static readonly HashSet<string> ValidActions = new() { ActionType.CREATE, ActionType.READ, ActionType.UPDATE, ActionType.DELETE };

public RuntimeConfigValidator(
RuntimeConfigProvider runtimeConfigProvider,
Expand Down Expand Up @@ -162,10 +162,10 @@ public void ValidatePermissionsInConfig(RuntimeConfig runtimeConfig)

// Check if the IncludeSet/ExcludeSet contain wildcard. If they contain wildcard, we make sure that they
// don't contain any other field. If they do, we throw an appropriate exception.
if (configAction.Fields!.Include.Contains("*") && configAction.Fields.Include.Count > 1 ||
configAction.Fields.Exclude.Contains("*") && configAction.Fields.Exclude.Count > 1)
if (configAction.Fields!.Include.Contains(AuthorizationResolver.WILDCARD) && configAction.Fields.Include.Count > 1 ||
configAction.Fields.Exclude.Contains(AuthorizationResolver.WILDCARD) && configAction.Fields.Exclude.Count > 1)
{
string incExc = configAction.Fields.Include.Contains("*") && configAction.Fields.Include.Count > 1 ? "included" : "excluded";
string incExc = configAction.Fields.Include.Contains(AuthorizationResolver.WILDCARD) && configAction.Fields.Include.Count > 1 ? "included" : "excluded";
throw new DataGatewayException(
message: $"No other field can be present with wildcard in the {incExc} set for: entity:{entityName}," +
$" role:{permissionSetting.Role}, action:{actionName}",
Expand Down Expand Up @@ -389,8 +389,8 @@ private static void AreFieldsAccessible(string policy, HashSet<string> includedF
private static bool IsFieldAccessible(Match columnNameMatch, HashSet<string> includedFields, HashSet<string> excludedFields)
{
string columnName = columnNameMatch.Value.Substring(AuthorizationResolver.FIELD_PREFIX.Length);
if (excludedFields.Contains(columnName!) || excludedFields.Contains("*") ||
!(includedFields.Contains("*") || includedFields.Contains(columnName)))
if (excludedFields.Contains(columnName!) || excludedFields.Contains(AuthorizationResolver.WILDCARD) ||
!(includedFields.Contains(AuthorizationResolver.WILDCARD) || includedFields.Contains(columnName)))
{
// If column is present in excluded OR excluded='*'
// If column is absent from included and included!=*
Expand Down Expand Up @@ -472,7 +472,7 @@ private static void ValidateActionName(string actionName, string entityName, str
/// <returns>Boolean value indicating whether the actionName is valid or not.</returns>
public static bool IsValidActionName(string actionName)
{
return actionName.Equals("*") || _validActions.Contains(actionName);
return actionName.Equals(AuthorizationResolver.WILDCARD) || ValidActions.Contains(actionName);
}
}
}
8 changes: 4 additions & 4 deletions DataGateway.Service/Services/RestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,16 @@ public static string HttpVerbToActions(string httpVerbName)
switch (httpVerbName)
{
case "POST":
return "create";
return ActionType.CREATE;
case "PUT":
case "PATCH":
// Please refer to the use of this method, which is to look out for policy based on crud operation type.
// Since create doesn't have filter predicates, PUT/PATCH would resolve to update operation.
return "update";
return ActionType.UPDATE;
case "DELETE":
return "delete";
return ActionType.DELETE;
case "GET":
return "read";
return ActionType.READ;
default:
throw new DataGatewayException(
message: "Unsupported operation type.",
Expand Down