diff --git a/api/TwoWeeksReady/Authorization/AuthorizationExtensions.cs b/api/TwoWeeksReady/Authorization/AuthorizationExtensions.cs new file mode 100644 index 0000000..d760a9e --- /dev/null +++ b/api/TwoWeeksReady/Authorization/AuthorizationExtensions.cs @@ -0,0 +1,14 @@ +using AzureFunctions.OidcAuthentication; +using System.Linq; + +namespace TwoWeeksReady.Authorization +{ + public static class AuthorizationExtensions + { + public static bool IsInRole(this ApiAuthenticationResult authenticationResult, string roleName) + { + var roles = authenticationResult.User.FindAll("https://schemas.2wradmin.com/role"); + return roles.Any(r => r.Value == roleName); + } + } +} diff --git a/api/TwoWeeksReady/Authorization/Roles.cs b/api/TwoWeeksReady/Authorization/Roles.cs new file mode 100644 index 0000000..494afef --- /dev/null +++ b/api/TwoWeeksReady/Authorization/Roles.cs @@ -0,0 +1,7 @@ +namespace TwoWeeksReady.Authorization +{ + public static class Roles + { + public static string Admin = "admin"; + } +} diff --git a/api/TwoWeeksReady/Hazards/HazardApiBase.cs b/api/TwoWeeksReady/Hazards/HazardApiBase.cs index 8f66e33..784ce02 100644 --- a/api/TwoWeeksReady/Hazards/HazardApiBase.cs +++ b/api/TwoWeeksReady/Hazards/HazardApiBase.cs @@ -11,168 +11,181 @@ using Microsoft.Azure.Documents.Linq; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using TwoWeeksReady.Authorization; namespace TwoWeeksReady.Hazards { - public abstract class HazardApiBase where T: HazardBaseInfo - { - protected const string DatabaseName = "2wr"; - protected const string ConnectionStringKey = "CosmosDBConnection"; - - private readonly IApiAuthentication _apiAuthentication; - - protected HazardApiBase(IApiAuthentication apiAuthentication) - { - _apiAuthentication = apiAuthentication; - } - - protected async Task GetDocuments(HttpRequest req, DocumentClient client, ILogger log, string collectionName) - { - log.LogInformation($"Getting {collectionName} list"); - - var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); - if (authorizationResult.Failed) - { - log.LogWarning(authorizationResult.FailureReason); - return new UnauthorizedResult(); - } - - Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); - var query = client.CreateDocumentQuery(collectionUri).AsDocumentQuery(); - var documents = new List(); - - while (query.HasMoreResults) - { - var result = await query.ExecuteNextAsync(); - documents.AddRange(result); - } - - return new OkObjectResult(documents); - } - - protected async Task GetDocument( - HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName) - { - var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); - if (authorizationResult.Failed) - { - log.LogWarning(authorizationResult.FailureReason); - return new UnauthorizedResult(); - } - - if (String.IsNullOrWhiteSpace(id)) - { - return new BadRequestObjectResult($"{collectionName}: id was not specified."); - } - - log.LogInformation($"Getting {collectionName} document, id = {id}"); - Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); - var feedOptions = new FeedOptions {EnableCrossPartitionQuery = false}; - var document = client.CreateDocumentQuery(collectionUri, feedOptions) - .Where(d => d.Id == id) - .AsEnumerable().FirstOrDefault(); - - if (document == null) - { - log.LogWarning($"{collectionName}: {id} not found."); - return new BadRequestObjectResult($"{collectionName} not found."); - } - - return new OkObjectResult(document); - } - - protected async Task CreateDocument(HttpRequest req, DocumentClient client, ILogger log, string collectionName) - { - log.LogInformation($"Creating an {collectionName} document"); - - var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); - if (authorizationResult.Failed) - { - log.LogWarning(authorizationResult.FailureReason); - return new UnauthorizedResult(); - } - - var content = await new StreamReader(req.Body).ReadToEndAsync(); - var newDocument = JsonConvert.DeserializeObject(content); - - Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); - ResourceResponse createdDocument = await client.CreateDocumentAsync(collectionUri, newDocument); - - return new OkObjectResult(createdDocument.Resource); - } - - protected async Task UpdateDocument( - HttpRequest req, DocumentClient client, ILogger log, string collectionName) - { - log.LogInformation($"Updating an {collectionName} document"); - var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); - if (authorizationResult.Failed) - { - log.LogWarning(authorizationResult.FailureReason); - return new UnauthorizedResult(); - } - - var content = await new StreamReader(req.Body).ReadToEndAsync(); - var documentUpdate = JsonConvert.DeserializeObject(content); - - Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); - - //verify existing document (not upserting as this is an update only function) - var feedOptions = new FeedOptions {EnableCrossPartitionQuery = false}; - var existingDocument = client.CreateDocumentQuery(collectionUri, feedOptions) - .Where(d => d.Id == documentUpdate.Id) - .AsEnumerable().FirstOrDefault(); - - if (existingDocument == null) - { - log.LogWarning($"{collectionName}: {documentUpdate.Id} not found."); - return new BadRequestObjectResult($"{collectionName} not found."); - } - - var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, documentUpdate.Id); - await client.ReplaceDocumentAsync(documentUri, documentUpdate); - - return new OkObjectResult(documentUpdate); - } - - protected async Task DeleteDocument( - HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName) - { - log.LogInformation($"Deleting {collectionName} document: id = {id}"); - var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); - if (authorizationResult.Failed) - { - log.LogWarning(authorizationResult.FailureReason); - return new UnauthorizedResult(); - } - - if (String.IsNullOrWhiteSpace(id)) - { - return new BadRequestObjectResult($"{collectionName}: id was not specified."); - } - - Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); - - //verify existing document (not upserting as this is an update only function) - var feedOptions = new FeedOptions {EnableCrossPartitionQuery = false}; - var existingDocument = client.CreateDocumentQuery(collectionUri, feedOptions) - .Where(d => d.Id == id) - .AsEnumerable().FirstOrDefault(); - - if (existingDocument == null) - { - log.LogWarning($"{collectionName}: {id} not found."); - return new BadRequestObjectResult($"{collectionName} not found."); - } - - var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, id); - - await client.DeleteDocumentAsync(documentUri, new RequestOptions - { - PartitionKey = new PartitionKey(id) - }); - - return new OkObjectResult(true); - } - } + public abstract class HazardApiBase where T : HazardBaseInfo + { + protected const string DatabaseName = "2wr"; + protected const string ConnectionStringKey = "CosmosDBConnection"; + + private readonly IApiAuthentication _apiAuthentication; + + protected HazardApiBase(IApiAuthentication apiAuthentication) + { + _apiAuthentication = apiAuthentication; + } + + protected async Task GetDocuments(HttpRequest req, DocumentClient client, ILogger log, string collectionName) + { + log.LogInformation($"Getting {collectionName} list"); + + var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); + if (authorizationResult.Failed) + { + log.LogWarning(authorizationResult.FailureReason); + return new UnauthorizedResult(); + } + + Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); + var query = client.CreateDocumentQuery(collectionUri).AsDocumentQuery(); + var documents = new List(); + + while (query.HasMoreResults) + { + var result = await query.ExecuteNextAsync(); + documents.AddRange(result); + } + + return new OkObjectResult(documents); + } + + protected async Task GetDocument( + HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName) + { + var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); + if (authorizationResult.Failed) + { + log.LogWarning(authorizationResult.FailureReason); + return new UnauthorizedResult(); + } + + if (String.IsNullOrWhiteSpace(id)) + { + return new BadRequestObjectResult($"{collectionName}: id was not specified."); + } + + log.LogInformation($"Getting {collectionName} document, id = {id}"); + Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); + var feedOptions = new FeedOptions { EnableCrossPartitionQuery = false }; + var document = client.CreateDocumentQuery(collectionUri, feedOptions) + .Where(d => d.Id == id) + .AsEnumerable().FirstOrDefault(); + + if (document == null) + { + log.LogWarning($"{collectionName}: {id} not found."); + return new BadRequestObjectResult($"{collectionName} not found."); + } + + return new OkObjectResult(document); + } + + protected async Task CreateDocument(HttpRequest req, DocumentClient client, ILogger log, string collectionName, string requiredRole = null) + { + log.LogInformation($"Creating an {collectionName} document"); + + var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); + if (authorizationResult.Failed) + { + log.LogWarning(authorizationResult.FailureReason); + return new UnauthorizedResult(); + } + + if (!string.IsNullOrEmpty(requiredRole) && !authorizationResult.IsInRole(requiredRole)) + { + log.LogWarning($"User is not in the {requiredRole} role"); + return new UnauthorizedResult(); + } + + var content = await new StreamReader(req.Body).ReadToEndAsync(); + var newDocument = JsonConvert.DeserializeObject(content); + + Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); + ResourceResponse createdDocument = await client.CreateDocumentAsync(collectionUri, newDocument); + + return new OkObjectResult(createdDocument.Resource); + } + + protected async Task UpdateDocument( + HttpRequest req, DocumentClient client, ILogger log, string collectionName, string requiredRole = null) + { + log.LogInformation($"Updating an {collectionName} document"); + var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); + if (authorizationResult.Failed) + { + log.LogWarning(authorizationResult.FailureReason); + return new UnauthorizedResult(); + } + + if (!string.IsNullOrEmpty(requiredRole) && !authorizationResult.IsInRole(requiredRole)) + { + log.LogWarning($"User is not in the {requiredRole} role"); + return new UnauthorizedResult(); + } + + var content = await new StreamReader(req.Body).ReadToEndAsync(); + var documentUpdate = JsonConvert.DeserializeObject(content); + + Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); + + //verify existing document (not upserting as this is an update only function) + var feedOptions = new FeedOptions { EnableCrossPartitionQuery = false }; + var existingDocument = client.CreateDocumentQuery(collectionUri, feedOptions) + .Where(d => d.Id == documentUpdate.Id) + .AsEnumerable().FirstOrDefault(); + + if (existingDocument == null) + { + log.LogWarning($"{collectionName}: {documentUpdate.Id} not found."); + return new BadRequestObjectResult($"{collectionName} not found."); + } + + var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, documentUpdate.Id); + await client.ReplaceDocumentAsync(documentUri, documentUpdate); + + return new OkObjectResult(documentUpdate); + } + + protected async Task DeleteDocument( + HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName) + { + log.LogInformation($"Deleting {collectionName} document: id = {id}"); + var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers); + if (authorizationResult.Failed) + { + log.LogWarning(authorizationResult.FailureReason); + return new UnauthorizedResult(); + } + + if (String.IsNullOrWhiteSpace(id)) + { + return new BadRequestObjectResult($"{collectionName}: id was not specified."); + } + + Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName); + + //verify existing document (not upserting as this is an update only function) + var feedOptions = new FeedOptions { EnableCrossPartitionQuery = false }; + var existingDocument = client.CreateDocumentQuery(collectionUri, feedOptions) + .Where(d => d.Id == id) + .AsEnumerable().FirstOrDefault(); + + if (existingDocument == null) + { + log.LogWarning($"{collectionName}: {id} not found."); + return new BadRequestObjectResult($"{collectionName} not found."); + } + + var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, id); + + await client.DeleteDocumentAsync(documentUri, new RequestOptions + { + PartitionKey = new PartitionKey(id) + }); + + return new OkObjectResult(true); + } + } } \ No newline at end of file diff --git a/api/TwoWeeksReady/Hazards/HazardInfoApi.cs b/api/TwoWeeksReady/Hazards/HazardInfoApi.cs index e7c36cd..9318619 100644 --- a/api/TwoWeeksReady/Hazards/HazardInfoApi.cs +++ b/api/TwoWeeksReady/Hazards/HazardInfoApi.cs @@ -6,6 +6,7 @@ using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using AzureFunctions.OidcAuthentication; +using TwoWeeksReady.Authorization; namespace TwoWeeksReady.Hazards { @@ -52,7 +53,7 @@ public async Task CreateDocument( DocumentClient client, ILogger log) { - return await CreateDocument(req, client, log, CollectionName); + return await CreateDocument(req, client, log, CollectionName, Roles.Admin); } @@ -64,7 +65,7 @@ public async Task UpdateDocument( DocumentClient client, ILogger log) { - return await UpdateDocument(req, client, log, CollectionName); + return await UpdateDocument(req, client, log, CollectionName, Roles.Admin); }