Skip to content

Commit c6439c3

Browse files
Access control: add further role checks and update tests
1 parent f875a66 commit c6439c3

File tree

4 files changed

+48
-35
lines changed

4 files changed

+48
-35
lines changed

src/Certify.Core/Management/Access/AccessControl.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -453,9 +453,9 @@ public string HashPassword(string password, string saltString = null)
453453
return hashed;
454454
}
455455

456-
public async Task<bool> AddRole(string contextUserId, Role r)
456+
public async Task<bool> AddRole(string contextUserId, Role r, bool bypassIntegrityCheck = false)
457457
{
458-
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
458+
if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
459459
{
460460
await AuditWarning("User {contextUserId} attempted to add an role action without being in required role.", contextUserId);
461461
return false;
@@ -465,9 +465,9 @@ public async Task<bool> AddRole(string contextUserId, Role r)
465465
return true;
466466
}
467467

468-
public async Task<bool> AddAssignedRole(string contextUserId, AssignedRole r)
468+
public async Task<bool> AddAssignedRole(string contextUserId, AssignedRole r, bool bypassIntegrityCheck = false)
469469
{
470-
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
470+
if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
471471
{
472472
await AuditWarning("User {contextUserId} attempted to add an assigned role without being in required role.", contextUserId);
473473
return false;
@@ -477,9 +477,9 @@ public async Task<bool> AddAssignedRole(string contextUserId, AssignedRole r)
477477
return true;
478478
}
479479

480-
public async Task<bool> AddResourceAction(string contextUserId, ResourceAction action)
480+
public async Task<bool> AddResourceAction(string contextUserId, ResourceAction action, bool bypassIntegrityCheck = false)
481481
{
482-
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
482+
if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
483483
{
484484
await AuditWarning("User {contextUserId} attempted to add a resource action without being in required role.", contextUserId);
485485
return false;
@@ -494,7 +494,7 @@ public async Task<List<AssignedRole>> GetAssignedRoles(string contextUserId, str
494494
if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
495495
{
496496
await AuditWarning("User {contextUserId} attempted to read assigned role for [{id}] without being in required role.", contextUserId, id);
497-
return new List<AssignedRole>();
497+
return null;
498498
}
499499

500500
var assignedRoles = await _store.GetItems<AssignedRole>(nameof(AssignedRole));
@@ -507,6 +507,7 @@ public async Task<RoleStatus> GetSecurityPrincipleRoleStatus(string contextUserI
507507
if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
508508
{
509509
await AuditWarning("User {contextUserId} attempted to read role status role for [{id}] without being in required role.", contextUserId, id);
510+
return null;
510511
}
511512

512513
var allAssignedRoles = await _store.GetItems<AssignedRole>(nameof(AssignedRole));
@@ -590,7 +591,7 @@ public async Task<List<AssignedAccessToken>> GetAssignedAccessTokens(string cont
590591
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
591592
{
592593
await AuditWarning("User {contextUserId} attempted to list assigned access tokens without being in required role.", contextUserId);
593-
return [];
594+
return null;
594595
}
595596

596597
return await _store.GetItems<AssignedAccessToken>(nameof(AssignedAccessToken));

src/Certify.Core/Management/Access/IAccessControl.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ public interface IAccessControl
2727
Task<bool> UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate);
2828
Task<SecurityPrincipleCheckResponse> CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck);
2929

30-
Task<bool> AddRole(string contextUserId, Role role);
31-
Task<bool> AddAssignedRole(string contextUserId, AssignedRole assignedRole);
32-
Task<bool> AddResourceAction(string contextUserId, ResourceAction action);
30+
Task<bool> AddRole(string contextUserId, Role role, bool bypassIntegrityCheck = false);
31+
Task<bool> AddAssignedRole(string contextUserId, AssignedRole assignedRole, bool bypassIntegrityCheck = false);
32+
Task<bool> AddResourceAction(string contextUserId, ResourceAction action, bool bypassIntegrityCheck = false);
3333

3434
Task<List<AssignedAccessToken>> GetAssignedAccessTokens(string contextUserId);
3535
Task<bool> AddAssignedAccessToken(string contextUserId, AssignedAccessToken token);

src/Certify.Models/Hub/AccessControl.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33

44
namespace Certify.Models.Hub
@@ -104,6 +104,11 @@ public class AccessTokenCheck
104104
public AccessToken Token { get; set; }
105105
public AccessCheck Check { get; set; }
106106
}
107+
108+
public class AccessTokenTypes
109+
{
110+
public const string Simple = "simple";
111+
}
107112
public class AccessToken : ConfigurationStoreItem
108113
{
109114
public string TokenType { get; set; } = default!;

src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Linq;
@@ -229,15 +229,19 @@ public async Task TestAddGetAssignedRoles()
229229

230230
// Setup policy with actions and add policy to store
231231
var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin);
232-
_ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true);
232+
var addPolicy = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true);
233+
234+
Assert.IsTrue(addPolicy, "Expected to add role");
233235

234236
// Setup and add roles and policy assignments to store
235237
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id);
236-
await access.AddRole(contextUserId, role);
238+
var addedRole = await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
239+
240+
Assert.IsTrue(addedRole, "Expected to add role");
237241

238242
// Assign security principles to roles and add roles and policy assignments to store
239243
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin };
240-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
244+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
241245

242246
// Validate AssignedRole list returned by AccessControl.GetAssignedRoles()
243247
foreach (var assignedRole in assignedRoles)
@@ -256,6 +260,9 @@ public async Task TestGetAssignedRolesNoRoles()
256260
var adminSecurityPrinciples = new List<SecurityPrinciple> { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin };
257261
adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true));
258262

263+
// assigned admin role to TestAdmin (also the contextUserId) so they can check roles for the other admin user
264+
await access.AddAssignedRole(TestSecurityPrinciples.TestAdmin.Id, TestAssignedRoles.TestAdmin, bypassIntegrityCheck: true);
265+
259266
// Validate AssignedRole list returned by AccessControl.GetAssignedRoles()
260267
var adminAssignedRoles = await access.GetAssignedRoles(contextUserId, adminSecurityPrinciples[0].Id);
261268
Assert.IsNotNull(adminAssignedRoles, "Expected list returned by AccessControl.GetAssignedRoles() to not be null");
@@ -303,7 +310,7 @@ public async Task TestUpdateSecurityPrinciple()
303310

304311
// Assign security principles to roles and add roles and policy assignments to store
305312
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin };
306-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
313+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
307314

308315
// Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update
309316
var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id);
@@ -363,11 +370,11 @@ public async Task TestUpdateSecurityPrincipleBadUpdate()
363370

364371
// Setup and add roles and policy assignments to store
365372
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id);
366-
await access.AddRole(contextUserId, role);
373+
await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
367374

368375
// Assign security principles to roles and add roles and policy assignments to store
369376
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin };
370-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
377+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
371378

372379
// Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update
373380
var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id);
@@ -400,11 +407,11 @@ public async Task TestUpdateSecurityPrinciplePassword()
400407

401408
// Setup and add roles and policy assignments to store
402409
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id);
403-
await access.AddRole(contextUserId, role);
410+
await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
404411

405412
// Assign security principles to roles and add roles and policy assignments to store
406413
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin };
407-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
414+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
408415

409416
// Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update
410417
var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id);
@@ -496,11 +503,11 @@ public async Task TestDeleteSecurityPrinciple()
496503

497504
// Setup and add roles and policy assignments to store
498505
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id);
499-
await access.AddRole(contextUserId, role);
506+
await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
500507

501508
// Assign security principles to roles and add roles and policy assignments to store
502509
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin };
503-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
510+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
504511

505512
// Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null
506513
var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id);
@@ -620,19 +627,19 @@ public async Task TestIsPrincipleInRole()
620627

621628
// Setup security principle actions
622629
var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System);
623-
actions.ForEach(async a => await access.AddResourceAction(contextUserId, a));
630+
actions.ForEach(async a => await access.AddResourceAction(contextUserId, a, bypassIntegrityCheck: true));
624631

625632
// Setup policy with actions and add policy to store
626633
var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin);
627634
_ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true);
628635

629636
// Setup and add roles and policy assignments to store
630637
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id);
631-
await access.AddRole(contextUserId, role);
638+
await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
632639

633640
// Assign security principles to roles and add roles and policy assignments to store
634641
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin };
635-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
642+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
636643

637644
// Validate specified admin user is a principle role
638645
bool hasAccess;
@@ -662,10 +669,10 @@ public async Task TestDomainAuth()
662669

663670
// Setup and add roles and policy assignments to store
664671
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id);
665-
await access.AddRole(contextUserId, role);
672+
await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
666673

667674
// Assign security principles to roles and add roles and policy assignments to store
668-
await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain
675+
await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserDomainConsumer, true); // devops user in consumer role for a specific domain
669676

670677
// Validate user can consume a cert for a given domain
671678
var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "www.example.com"));
@@ -691,10 +698,10 @@ public async Task TestWildcardDomainAuth()
691698

692699
// Setup and add roles and policy assignments to store
693700
var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id);
694-
await access.AddRole(contextUserId, role);
701+
await access.AddRole(contextUserId, role, bypassIntegrityCheck: true);
695702

696703
// Assign security principles to roles and add roles and policy assignments to store
697-
await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain
704+
await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer, bypassIntegrityCheck: true); // devops user in consumer role for a wildcard domain
698705

699706
// Validate user can consume any subdomain via a granted wildcard
700707
var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"));
@@ -761,7 +768,7 @@ public async Task TestUserAPIToken()
761768

762769
// allow test admin to perform access checks
763770
var assignedRoles = new List<AssignedRole> { TestAssignedRoles.TestAdmin };
764-
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r));
771+
assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true));
765772

766773
// Add test devops user security principle
767774
_ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true);
@@ -783,10 +790,10 @@ public async Task TestUserAPIToken()
783790
var assignedRolesForDevopsUser = await access.GetAssignedRoles(contextUserId, TestSecurityPrinciples.DevopsUser.Id);
784791

785792
// create and assign a new API token
786-
var apiToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example API token" };
787-
var apiExpiredToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example expired API token", DateExpiry = DateTimeOffset.UtcNow.AddDays(-1) };
788-
var apiRevokedToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example revoked API token", DateRevoked = DateTimeOffset.UtcNow.AddDays(-1) };
789-
var apiTokenBad = new AccessToken { ClientId = TestSecurityPrinciples.DomainOwner.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example bad API token (invalid client id)" };
793+
var apiToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example API token" };
794+
var apiExpiredToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example expired API token", DateExpiry = DateTimeOffset.UtcNow.AddDays(-1) };
795+
var apiRevokedToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example revoked API token", DateRevoked = DateTimeOffset.UtcNow.AddDays(-1) };
796+
var apiTokenBad = new AccessToken { ClientId = TestSecurityPrinciples.DomainOwner.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example bad API token (invalid client id)" };
790797
var assignedToken = new AssignedAccessToken
791798
{
792799
AccessTokens = [apiToken, apiExpiredToken, apiRevokedToken],

0 commit comments

Comments
 (0)