Skip to content

Commit 924851b

Browse files
Access control: add access token admin methods, update role checks
1 parent 021a1c7 commit 924851b

File tree

10 files changed

+328
-60
lines changed

10 files changed

+328
-60
lines changed

src/Certify.Client/CertifyApiClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ public async Task<List<SecurityPrinciple>> GetAccessSecurityPrinciples(AuthConte
751751

752752
public async Task<Certify.Models.Config.ActionResult> CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null)
753753
{
754-
var result = await PostAsync("access/checkapitoken", new AccessTokenCheck { Check = check, Token = token }, authContext);
754+
var result = await PostAsync("access/apitoken/check", new AccessTokenCheck { Check = check, Token = token }, authContext);
755755
return JsonConvert.DeserializeObject<ActionResult>(await result.Content.ReadAsStringAsync());
756756
}
757757

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

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public async Task<bool> IsInitialized()
5555
}
5656
}
5757

58-
public async Task<List<Role>> GetRoles()
58+
public async Task<List<Role>> GetRoles(string contextUserId)
5959
{
6060
return await _store.GetItems<Role>(nameof(Role));
6161
}
@@ -453,24 +453,52 @@ public string HashPassword(string password, string saltString = null)
453453
return hashed;
454454
}
455455

456-
public async Task AddRole(Role r)
456+
public async Task<bool> AddRole(string contextUserId, Role r)
457457
{
458+
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
459+
{
460+
await AuditWarning("User {contextUserId} attempted to add an role action without being in required role.", contextUserId);
461+
return false;
462+
}
463+
458464
await _store.Add(nameof(Role), r);
465+
return true;
459466
}
460467

461-
public async Task AddAssignedRole(AssignedRole r)
468+
public async Task<bool> AddAssignedRole(string contextUserId, AssignedRole r)
462469
{
470+
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
471+
{
472+
await AuditWarning("User {contextUserId} attempted to add an assigned role without being in required role.", contextUserId);
473+
return false;
474+
}
475+
463476
await _store.Add(nameof(AssignedRole), r);
477+
return true;
464478
}
465479

466-
public async Task AddAssignedAccessToken(AssignedAccessToken t)
480+
public async Task<bool> AddAssignedAccessToken(string contextUserId, AssignedAccessToken t)
467481
{
482+
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
483+
{
484+
await AuditWarning("User {contextUserId} attempted to add an assigned access token without being in required role.", contextUserId);
485+
return false;
486+
}
487+
468488
await _store.Add(nameof(AssignedAccessToken), t);
489+
return true;
469490
}
470491

471-
public async Task AddResourceAction(ResourceAction action)
492+
public async Task<bool> AddResourceAction(string contextUserId, ResourceAction action)
472493
{
494+
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
495+
{
496+
await AuditWarning("User {contextUserId} attempted to add a resource action without being in required role.", contextUserId);
497+
return false;
498+
}
499+
473500
await _store.Add(nameof(ResourceAction), action);
501+
return true;
474502
}
475503

476504
public async Task<List<AssignedRole>> GetAssignedRoles(string contextUserId, string id)
@@ -491,7 +519,6 @@ public async Task<RoleStatus> GetSecurityPrincipleRoleStatus(string contextUserI
491519
if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
492520
{
493521
await AuditWarning("User {contextUserId} attempted to read role status role for [{id}] without being in required role.", contextUserId, id);
494-
495522
}
496523

497524
var allAssignedRoles = await _store.GetItems<AssignedRole>(nameof(AssignedRole));
@@ -570,6 +597,30 @@ await GetSecurityPrincipleByUsername(contextUserId, passwordCheck.Username) :
570597
}
571598
}
572599

600+
public async Task<List<AccessToken>> GetAccessTokens(string contextUserId)
601+
{
602+
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
603+
{
604+
await AuditWarning("User {contextUserId} attempted to list access tokens without being in required role.", contextUserId);
605+
return [];
606+
}
607+
608+
return await _store.GetItems<AccessToken>(nameof(AccessToken));
609+
}
610+
611+
public async Task<bool> AddAccessToken(string contextUserId, AccessToken a)
612+
{
613+
if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id))
614+
{
615+
await AuditWarning("User {contextUserId} attempted to add an access token without being in required role.", contextUserId);
616+
return false;
617+
}
618+
619+
await _store.Add(nameof(AccessToken), a);
620+
621+
return true;
622+
}
623+
573624
public string GetSHA256Hash(string val)
574625
{
575626
using (var sha256Hash = SHA256.Create())

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public interface IAccessControl
1616
/// Get the list of standard roles built-in to the system
1717
/// </summary>
1818
/// <returns></returns>
19-
Task<List<Role>> GetRoles();
19+
Task<List<Role>> GetRoles(string contextUserId);
2020
Task<bool> IsSecurityPrincipleAuthorised(string contextUserId, AccessCheck check);
2121
Task<Models.Config.ActionResult> IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, AccessCheck check);
2222
Task<bool> IsPrincipleInRole(string contextUserId, string id, string roleId);
@@ -27,9 +27,12 @@ public interface IAccessControl
2727
Task<bool> UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate);
2828
Task<SecurityPrincipleCheckResponse> CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck);
2929

30-
Task AddRole(Role role);
31-
Task AddAssignedRole(AssignedRole assignedRole);
32-
Task AddResourceAction(ResourceAction action);
30+
Task<bool> AddRole(string contextUserId, Role role);
31+
Task<bool> AddAssignedRole(string contextUserId, AssignedRole assignedRole);
32+
Task<bool> AddResourceAction(string contextUserId, ResourceAction action);
33+
34+
Task<List<AccessToken>> GetAccessTokens(string contextUserId);
35+
Task<bool> AddAccessToken(string contextUserId, AccessToken token);
3336
Task<bool> IsInitialized();
3437
}
3538
}

src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private static async Task BootstrapAdminUserAndRoles(IAccessControl access)
9090
foreach (var r in assignedRoles)
9191
{
9292
// add roles and policy assignments to store
93-
await access.AddAssignedRole(r);
93+
await access.AddAssignedRole(adminSp.Id, r);
9494
}
9595
}
9696

@@ -103,11 +103,13 @@ private static async Task UpdateStandardRoles(IAccessControl access)
103103
{
104104
// setup roles with policies
105105

106+
var adminSvcPrinciple = "admin_01";
107+
106108
var actions = Policies.GetStandardResourceActions();
107109

108110
foreach (var action in actions)
109111
{
110-
await access.AddResourceAction(action);
112+
await access.AddResourceAction(adminSvcPrinciple, action);
111113
}
112114

113115
// setup policies with actions
@@ -126,7 +128,7 @@ private static async Task UpdateStandardRoles(IAccessControl access)
126128
foreach (var r in roles)
127129
{
128130
// add roles and policy assignments to store
129-
await access.AddRole(r);
131+
await access.AddRole(adminSvcPrinciple, r);
130132
}
131133
}
132134

src/Certify.Models/Hub/AccessControl.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public class AccessToken : ConfigurationStoreItem
109109
public string TokenType { get; set; } = default!;
110110
public string Secret { get; set; } = default!;
111111
public string ClientId { get; set; } = default!;
112+
112113
public DateTimeOffset? DateCreated { get; set; }
113114
public DateTimeOffset? DateExpiry { get; set; }
114115
public DateTimeOffset? DateRevoked { get; set; }

src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,178 @@ public virtual async System.Threading.Tasks.Task<RoleStatus> GetSecurityPrincipl
428428
}
429429
}
430430

431+
/// <summary>
432+
/// Get list of API access tokens [Generated]
433+
/// </summary>
434+
/// <returns>OK</returns>
435+
/// <exception cref="ApiException">A server side error occurred.</exception>
436+
public virtual System.Threading.Tasks.Task<System.Collections.Generic.ICollection<AccessToken>> GetAccessTokensAsync()
437+
{
438+
return GetAccessTokensAsync(System.Threading.CancellationToken.None);
439+
}
440+
441+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
442+
/// <summary>
443+
/// Get list of API access tokens [Generated]
444+
/// </summary>
445+
/// <returns>OK</returns>
446+
/// <exception cref="ApiException">A server side error occurred.</exception>
447+
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<AccessToken>> GetAccessTokensAsync(System.Threading.CancellationToken cancellationToken)
448+
{
449+
var client_ = _httpClient;
450+
var disposeClient_ = false;
451+
try
452+
{
453+
using (var request_ = new System.Net.Http.HttpRequestMessage())
454+
{
455+
request_.Method = new System.Net.Http.HttpMethod("GET");
456+
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
457+
458+
var urlBuilder_ = new System.Text.StringBuilder();
459+
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
460+
// Operation Path: "internal/v1/access/token"
461+
urlBuilder_.Append("internal/v1/access/token");
462+
463+
PrepareRequest(client_, request_, urlBuilder_);
464+
465+
var url_ = urlBuilder_.ToString();
466+
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
467+
468+
PrepareRequest(client_, request_, url_);
469+
470+
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
471+
var disposeResponse_ = true;
472+
try
473+
{
474+
var headers_ = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>>();
475+
foreach (var item_ in response_.Headers)
476+
headers_[item_.Key] = item_.Value;
477+
if (response_.Content != null && response_.Content.Headers != null)
478+
{
479+
foreach (var item_ in response_.Content.Headers)
480+
headers_[item_.Key] = item_.Value;
481+
}
482+
483+
ProcessResponse(client_, response_);
484+
485+
var status_ = (int)response_.StatusCode;
486+
if (status_ == 200)
487+
{
488+
var objectResponse_ = await ReadObjectResponseAsync<System.Collections.Generic.ICollection<AccessToken>>(response_, headers_, cancellationToken).ConfigureAwait(false);
489+
if (objectResponse_.Object == null)
490+
{
491+
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
492+
}
493+
return objectResponse_.Object;
494+
}
495+
else
496+
{
497+
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
498+
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
499+
}
500+
}
501+
finally
502+
{
503+
if (disposeResponse_)
504+
response_.Dispose();
505+
}
506+
}
507+
}
508+
finally
509+
{
510+
if (disposeClient_)
511+
client_.Dispose();
512+
}
513+
}
514+
515+
/// <summary>
516+
/// Add new access token [Generated]
517+
/// </summary>
518+
/// <returns>OK</returns>
519+
/// <exception cref="ApiException">A server side error occurred.</exception>
520+
public virtual System.Threading.Tasks.Task<ActionResult> AddAccessTokenAsync(AccessToken body)
521+
{
522+
return AddAccessTokenAsync(body, System.Threading.CancellationToken.None);
523+
}
524+
525+
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
526+
/// <summary>
527+
/// Add new access token [Generated]
528+
/// </summary>
529+
/// <returns>OK</returns>
530+
/// <exception cref="ApiException">A server side error occurred.</exception>
531+
public virtual async System.Threading.Tasks.Task<ActionResult> AddAccessTokenAsync(AccessToken body, System.Threading.CancellationToken cancellationToken)
532+
{
533+
var client_ = _httpClient;
534+
var disposeClient_ = false;
535+
try
536+
{
537+
using (var request_ = new System.Net.Http.HttpRequestMessage())
538+
{
539+
var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings);
540+
var content_ = new System.Net.Http.StringContent(json_);
541+
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
542+
request_.Content = content_;
543+
request_.Method = new System.Net.Http.HttpMethod("POST");
544+
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));
545+
546+
var urlBuilder_ = new System.Text.StringBuilder();
547+
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
548+
// Operation Path: "internal/v1/access/token"
549+
urlBuilder_.Append("internal/v1/access/token");
550+
551+
PrepareRequest(client_, request_, urlBuilder_);
552+
553+
var url_ = urlBuilder_.ToString();
554+
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
555+
556+
PrepareRequest(client_, request_, url_);
557+
558+
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
559+
var disposeResponse_ = true;
560+
try
561+
{
562+
var headers_ = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>>();
563+
foreach (var item_ in response_.Headers)
564+
headers_[item_.Key] = item_.Value;
565+
if (response_.Content != null && response_.Content.Headers != null)
566+
{
567+
foreach (var item_ in response_.Content.Headers)
568+
headers_[item_.Key] = item_.Value;
569+
}
570+
571+
ProcessResponse(client_, response_);
572+
573+
var status_ = (int)response_.StatusCode;
574+
if (status_ == 200)
575+
{
576+
var objectResponse_ = await ReadObjectResponseAsync<ActionResult>(response_, headers_, cancellationToken).ConfigureAwait(false);
577+
if (objectResponse_.Object == null)
578+
{
579+
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
580+
}
581+
return objectResponse_.Object;
582+
}
583+
else
584+
{
585+
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
586+
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
587+
}
588+
}
589+
finally
590+
{
591+
if (disposeResponse_)
592+
response_.Dispose();
593+
}
594+
}
595+
}
596+
finally
597+
{
598+
if (disposeClient_)
599+
client_.Dispose();
600+
}
601+
}
602+
431603
/// <summary>
432604
/// Get list of available security principles [Generated]
433605
/// </summary>

0 commit comments

Comments
 (0)