Skip to content

Commit

Permalink
Implement API token access checks and refactor access checks
Browse files Browse the repository at this point in the history
  • Loading branch information
webprofusion-chrisc committed Jan 21, 2025
1 parent d1b8c03 commit 021a1c7
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 154 deletions.
7 changes: 6 additions & 1 deletion src/Certify.Client/CertifyApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -749,8 +749,13 @@ public async Task<List<SecurityPrinciple>> GetAccessSecurityPrinciples(AuthConte
return JsonToObject<List<SecurityPrinciple>>(result);
}

#endregion
public async Task<Certify.Models.Config.ActionResult> CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null)
{
var result = await PostAsync("access/checkapitoken", new AccessTokenCheck { Check = check, Token = token }, authContext);
return JsonConvert.DeserializeObject<ActionResult>(await result.Content.ReadAsStringAsync());
}

#endregion
private T JsonToObject<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json);
Expand Down
1 change: 1 addition & 0 deletions src/Certify.Client/ICertifyClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public partial interface ICertifyInternalApiClient
Task<List<ActionStep>> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null);

Task<ActionStep> UpdateManagementHub(string url, string joiningKey, AuthContext authContext = null);
Task<Certify.Models.Config.ActionResult> CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null);
#endregion System

#region Server
Expand Down
33 changes: 21 additions & 12 deletions src/Certify.Core/Management/Access/AccessControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public async Task<SecurityPrinciple> GetSecurityPrincipleByUsername(string conte
/// <param name="identifier">optional resource identifier, if access is limited by specific resource</param>
/// <param name="scopedAssignedRoles">optional scoped assigned roles to limit access to (for scoped access token checks etc)</param>
/// <returns></returns>
public async Task<bool> IsAuthorised(string contextUserId, string principleId, string resourceType, string actionId, string identifier = null, List<string> scopedAssignedRoles = null)
public async Task<bool> IsSecurityPrincipleAuthorised(string contextUserId, AccessCheck check)
{
// to determine is a principle has access to perform a particular action
// for each group the principle is part of
Expand All @@ -226,12 +226,12 @@ public async Task<bool> IsAuthorised(string contextUserId, string principleId, s
var allPolicies = await _store.GetItems<ResourcePolicy>(nameof(ResourcePolicy));

// get the assigned roles for this specific security principle
var spAssignedRoles = allAssignedRoles.Where(a => a.SecurityPrincipleId == principleId);
var spAssignedRoles = allAssignedRoles.Where(a => a.SecurityPrincipleId == check.SecurityPrincipleId);

// if scoped assigned role ID specified (access token check etc), reduce scope of assigned roles to check
if (scopedAssignedRoles?.Any() == true)
if (check.ScopedAssignedRoles?.Any() == true)
{
spAssignedRoles = spAssignedRoles.Where(a => scopedAssignedRoles.Contains(a.Id));
spAssignedRoles = spAssignedRoles.Where(a => check.ScopedAssignedRoles.Contains(a.Id));
}

// get all role definitions included in the principles assigned roles
Expand All @@ -243,32 +243,32 @@ public async Task<bool> IsAuthorised(string contextUserId, string principleId, s
var spAssignedPolicies = allPolicies.Where(r => spAssignedRoleDefinitions.Any(p => p.Policies.Contains(r.Id)));

// check an assigned policy allows the required resource action
if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(actionId)))
if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(check.ResourceActionId)))
{

// if any of the service principles assigned roles are restricted by resource type,
// check for identifier matches (e.g. role assignment restricted on domains )

if (spSpecificAssignedRoles.Any(a => a.IncludedResources?.Any(r => r.ResourceType == resourceType) == true))
if (spSpecificAssignedRoles.Any(a => a.IncludedResources?.Any(r => r.ResourceType == check.ResourceType) == true))
{
var allIncludedResources = spSpecificAssignedRoles.SelectMany(a => a.IncludedResources).Distinct();

if (resourceType == ResourceTypes.Domain && !identifier.Trim().StartsWith("*") && identifier.Contains("."))
if (check.ResourceType == ResourceTypes.Domain && !check.Identifier.Trim().StartsWith("*") && check.Identifier.Contains("."))
{
// get wildcard for respective domain identifier
var identifierComponents = identifier.Split('.');
var identifierComponents = check.Identifier.Split('.');

var wildcard = "*." + string.Join(".", identifierComponents.Skip(1));

// search for matching identifier

foreach (var includedResource in allIncludedResources)
{
if (includedResource.ResourceType == resourceType && includedResource.Identifier == wildcard)
if (includedResource.ResourceType == check.ResourceType && includedResource.Identifier == wildcard)
{
return true;
}
else if (includedResource.ResourceType == resourceType && includedResource.Identifier == identifier)
else if (includedResource.ResourceType == check.ResourceType && includedResource.Identifier == check.Identifier)
{
return true;
}
Expand All @@ -289,7 +289,7 @@ public async Task<bool> IsAuthorised(string contextUserId, string principleId, s
}
}

public async Task<ActionResult> IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, string resourceType, string actionId, string identifier)
public async Task<ActionResult> IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, AccessCheck check)
{
// resolve security principle from access token

Expand All @@ -305,7 +305,16 @@ public async Task<ActionResult> IsAccessTokenAuthorised(string contextUserId, Ac

// check related principle has access

var isAuthorised = await IsAuthorised(contextUserId, knownAssignedToken.SecurityPrincipleId, resourceType, actionId, identifier, knownAssignedToken.ScopedAssignedRoles);
var scopedCheck = new AccessCheck
{
SecurityPrincipleId = knownAssignedToken.SecurityPrincipleId,
ResourceActionId = check.ResourceActionId,
Identifier = check.Identifier,
ResourceType = check.ResourceType,
ScopedAssignedRoles = knownAssignedToken.ScopedAssignedRoles
};

var isAuthorised = await IsSecurityPrincipleAuthorised(contextUserId, scopedCheck);

if (isAuthorised)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Certify.Core/Management/Access/IAccessControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public interface IAccessControl
/// </summary>
/// <returns></returns>
Task<List<Role>> GetRoles();
Task<bool> IsAuthorised(string contextUserId, string principleId, string resourceType, string actionId, string identifier = null, List<string> scopedAssignedRoles = null);
Task<bool> IsSecurityPrincipleAuthorised(string contextUserId, AccessCheck check);
Task<Models.Config.ActionResult> IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, AccessCheck check);
Task<bool> IsPrincipleInRole(string contextUserId, string id, string roleId);
Task<List<AssignedRole>> GetAssignedRoles(string contextUserId, string id);
Task<RoleStatus> GetSecurityPrincipleRoleStatus(string contextUserId, string id);
Expand Down
30 changes: 27 additions & 3 deletions src/Certify.Models/Hub/AccessControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,35 @@ public class AssignedRole : ConfigurationStoreItem
public List<Resource>? IncludedResources { get; set; } = [];
}

public class AccessCheck
{
public string? SecurityPrincipleId { get; set; } = default!;
public string ResourceType { get; set; } = default!;
public string ResourceActionId { get; set; } = default!;
public string? Identifier { get; set; } = default!;

public List<string> ScopedAssignedRoles { get; set; } = [];

public AccessCheck() { }
public AccessCheck(string? securityPrincipleId, string resourceType, string resourceActionId, string? identifier = null)
{
SecurityPrincipleId = securityPrincipleId;
ResourceType = resourceType;
ResourceActionId = resourceActionId;
Identifier = identifier;
}
}

public class AccessTokenCheck
{
public AccessToken Token { get; set; }
public AccessCheck Check { get; set; }
}
public class AccessToken : ConfigurationStoreItem
{
public string TokenType { get; set; }
public string Secret { get; set; }
public string ClientId { get; set; }
public string TokenType { get; set; } = default!;
public string Secret { get; set; } = default!;
public string ClientId { get; set; } = default!;
public DateTimeOffset? DateCreated { get; set; }
public DateTimeOffset? DateExpiry { get; set; }
public DateTimeOffset? DateRevoked { get; set; }
Expand Down
Loading

0 comments on commit 021a1c7

Please sign in to comment.