Skip to content

Commit 9d49530

Browse files
authored
Merge pull request #113 from HTBox/admin-api-security
Checking for admin role when creating or updating hazard info
2 parents 0dd28ed + 78a6906 commit 9d49530

4 files changed

Lines changed: 198 additions & 163 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using AzureFunctions.OidcAuthentication;
2+
using System.Linq;
3+
4+
namespace TwoWeeksReady.Authorization
5+
{
6+
public static class AuthorizationExtensions
7+
{
8+
public static bool IsInRole(this ApiAuthenticationResult authenticationResult, string roleName)
9+
{
10+
var roles = authenticationResult.User.FindAll("https://schemas.2wradmin.com/role");
11+
return roles.Any(r => r.Value == roleName);
12+
}
13+
}
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace TwoWeeksReady.Authorization
2+
{
3+
public static class Roles
4+
{
5+
public static string Admin = "admin";
6+
}
7+
}

api/TwoWeeksReady/Hazards/HazardApiBase.cs

Lines changed: 174 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -11,168 +11,181 @@
1111
using Microsoft.Azure.Documents.Linq;
1212
using Microsoft.Extensions.Logging;
1313
using Newtonsoft.Json;
14+
using TwoWeeksReady.Authorization;
1415

1516
namespace TwoWeeksReady.Hazards
1617
{
17-
public abstract class HazardApiBase<T> where T: HazardBaseInfo
18-
{
19-
protected const string DatabaseName = "2wr";
20-
protected const string ConnectionStringKey = "CosmosDBConnection";
21-
22-
private readonly IApiAuthentication _apiAuthentication;
23-
24-
protected HazardApiBase(IApiAuthentication apiAuthentication)
25-
{
26-
_apiAuthentication = apiAuthentication;
27-
}
28-
29-
protected async Task<IActionResult> GetDocuments(HttpRequest req, DocumentClient client, ILogger log, string collectionName)
30-
{
31-
log.LogInformation($"Getting {collectionName} list");
32-
33-
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
34-
if (authorizationResult.Failed)
35-
{
36-
log.LogWarning(authorizationResult.FailureReason);
37-
return new UnauthorizedResult();
38-
}
39-
40-
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
41-
var query = client.CreateDocumentQuery<T>(collectionUri).AsDocumentQuery();
42-
var documents = new List<T>();
43-
44-
while (query.HasMoreResults)
45-
{
46-
var result = await query.ExecuteNextAsync<T>();
47-
documents.AddRange(result);
48-
}
49-
50-
return new OkObjectResult(documents);
51-
}
52-
53-
protected async Task<IActionResult> GetDocument(
54-
HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName)
55-
{
56-
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
57-
if (authorizationResult.Failed)
58-
{
59-
log.LogWarning(authorizationResult.FailureReason);
60-
return new UnauthorizedResult();
61-
}
62-
63-
if (String.IsNullOrWhiteSpace(id))
64-
{
65-
return new BadRequestObjectResult($"{collectionName}: id was not specified.");
66-
}
67-
68-
log.LogInformation($"Getting {collectionName} document, id = {id}");
69-
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
70-
var feedOptions = new FeedOptions {EnableCrossPartitionQuery = false};
71-
var document = client.CreateDocumentQuery<T>(collectionUri, feedOptions)
72-
.Where(d => d.Id == id)
73-
.AsEnumerable().FirstOrDefault();
74-
75-
if (document == null)
76-
{
77-
log.LogWarning($"{collectionName}: {id} not found.");
78-
return new BadRequestObjectResult($"{collectionName} not found.");
79-
}
80-
81-
return new OkObjectResult(document);
82-
}
83-
84-
protected async Task<IActionResult> CreateDocument(HttpRequest req, DocumentClient client, ILogger log, string collectionName)
85-
{
86-
log.LogInformation($"Creating an {collectionName} document");
87-
88-
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
89-
if (authorizationResult.Failed)
90-
{
91-
log.LogWarning(authorizationResult.FailureReason);
92-
return new UnauthorizedResult();
93-
}
94-
95-
var content = await new StreamReader(req.Body).ReadToEndAsync();
96-
var newDocument = JsonConvert.DeserializeObject<T>(content);
97-
98-
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
99-
ResourceResponse<Document> createdDocument = await client.CreateDocumentAsync(collectionUri, newDocument);
100-
101-
return new OkObjectResult(createdDocument.Resource);
102-
}
103-
104-
protected async Task<IActionResult> UpdateDocument(
105-
HttpRequest req, DocumentClient client, ILogger log, string collectionName)
106-
{
107-
log.LogInformation($"Updating an {collectionName} document");
108-
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
109-
if (authorizationResult.Failed)
110-
{
111-
log.LogWarning(authorizationResult.FailureReason);
112-
return new UnauthorizedResult();
113-
}
114-
115-
var content = await new StreamReader(req.Body).ReadToEndAsync();
116-
var documentUpdate = JsonConvert.DeserializeObject<T>(content);
117-
118-
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
119-
120-
//verify existing document (not upserting as this is an update only function)
121-
var feedOptions = new FeedOptions {EnableCrossPartitionQuery = false};
122-
var existingDocument = client.CreateDocumentQuery<T>(collectionUri, feedOptions)
123-
.Where(d => d.Id == documentUpdate.Id)
124-
.AsEnumerable().FirstOrDefault();
125-
126-
if (existingDocument == null)
127-
{
128-
log.LogWarning($"{collectionName}: {documentUpdate.Id} not found.");
129-
return new BadRequestObjectResult($"{collectionName} not found.");
130-
}
131-
132-
var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, documentUpdate.Id);
133-
await client.ReplaceDocumentAsync(documentUri, documentUpdate);
134-
135-
return new OkObjectResult(documentUpdate);
136-
}
137-
138-
protected async Task<IActionResult> DeleteDocument(
139-
HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName)
140-
{
141-
log.LogInformation($"Deleting {collectionName} document: id = {id}");
142-
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
143-
if (authorizationResult.Failed)
144-
{
145-
log.LogWarning(authorizationResult.FailureReason);
146-
return new UnauthorizedResult();
147-
}
148-
149-
if (String.IsNullOrWhiteSpace(id))
150-
{
151-
return new BadRequestObjectResult($"{collectionName}: id was not specified.");
152-
}
153-
154-
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
155-
156-
//verify existing document (not upserting as this is an update only function)
157-
var feedOptions = new FeedOptions {EnableCrossPartitionQuery = false};
158-
var existingDocument = client.CreateDocumentQuery<T>(collectionUri, feedOptions)
159-
.Where(d => d.Id == id)
160-
.AsEnumerable().FirstOrDefault();
161-
162-
if (existingDocument == null)
163-
{
164-
log.LogWarning($"{collectionName}: {id} not found.");
165-
return new BadRequestObjectResult($"{collectionName} not found.");
166-
}
167-
168-
var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, id);
169-
170-
await client.DeleteDocumentAsync(documentUri, new RequestOptions
171-
{
172-
PartitionKey = new PartitionKey(id)
173-
});
174-
175-
return new OkObjectResult(true);
176-
}
177-
}
18+
public abstract class HazardApiBase<T> where T : HazardBaseInfo
19+
{
20+
protected const string DatabaseName = "2wr";
21+
protected const string ConnectionStringKey = "CosmosDBConnection";
22+
23+
private readonly IApiAuthentication _apiAuthentication;
24+
25+
protected HazardApiBase(IApiAuthentication apiAuthentication)
26+
{
27+
_apiAuthentication = apiAuthentication;
28+
}
29+
30+
protected async Task<IActionResult> GetDocuments(HttpRequest req, DocumentClient client, ILogger log, string collectionName)
31+
{
32+
log.LogInformation($"Getting {collectionName} list");
33+
34+
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
35+
if (authorizationResult.Failed)
36+
{
37+
log.LogWarning(authorizationResult.FailureReason);
38+
return new UnauthorizedResult();
39+
}
40+
41+
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
42+
var query = client.CreateDocumentQuery<T>(collectionUri).AsDocumentQuery();
43+
var documents = new List<T>();
44+
45+
while (query.HasMoreResults)
46+
{
47+
var result = await query.ExecuteNextAsync<T>();
48+
documents.AddRange(result);
49+
}
50+
51+
return new OkObjectResult(documents);
52+
}
53+
54+
protected async Task<IActionResult> GetDocument(
55+
HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName)
56+
{
57+
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
58+
if (authorizationResult.Failed)
59+
{
60+
log.LogWarning(authorizationResult.FailureReason);
61+
return new UnauthorizedResult();
62+
}
63+
64+
if (String.IsNullOrWhiteSpace(id))
65+
{
66+
return new BadRequestObjectResult($"{collectionName}: id was not specified.");
67+
}
68+
69+
log.LogInformation($"Getting {collectionName} document, id = {id}");
70+
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
71+
var feedOptions = new FeedOptions { EnableCrossPartitionQuery = false };
72+
var document = client.CreateDocumentQuery<T>(collectionUri, feedOptions)
73+
.Where(d => d.Id == id)
74+
.AsEnumerable().FirstOrDefault();
75+
76+
if (document == null)
77+
{
78+
log.LogWarning($"{collectionName}: {id} not found.");
79+
return new BadRequestObjectResult($"{collectionName} not found.");
80+
}
81+
82+
return new OkObjectResult(document);
83+
}
84+
85+
protected async Task<IActionResult> CreateDocument(HttpRequest req, DocumentClient client, ILogger log, string collectionName, string requiredRole = null)
86+
{
87+
log.LogInformation($"Creating an {collectionName} document");
88+
89+
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
90+
if (authorizationResult.Failed)
91+
{
92+
log.LogWarning(authorizationResult.FailureReason);
93+
return new UnauthorizedResult();
94+
}
95+
96+
if (!string.IsNullOrEmpty(requiredRole) && !authorizationResult.IsInRole(requiredRole))
97+
{
98+
log.LogWarning($"User is not in the {requiredRole} role");
99+
return new UnauthorizedResult();
100+
}
101+
102+
var content = await new StreamReader(req.Body).ReadToEndAsync();
103+
var newDocument = JsonConvert.DeserializeObject<T>(content);
104+
105+
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
106+
ResourceResponse<Document> createdDocument = await client.CreateDocumentAsync(collectionUri, newDocument);
107+
108+
return new OkObjectResult(createdDocument.Resource);
109+
}
110+
111+
protected async Task<IActionResult> UpdateDocument(
112+
HttpRequest req, DocumentClient client, ILogger log, string collectionName, string requiredRole = null)
113+
{
114+
log.LogInformation($"Updating an {collectionName} document");
115+
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
116+
if (authorizationResult.Failed)
117+
{
118+
log.LogWarning(authorizationResult.FailureReason);
119+
return new UnauthorizedResult();
120+
}
121+
122+
if (!string.IsNullOrEmpty(requiredRole) && !authorizationResult.IsInRole(requiredRole))
123+
{
124+
log.LogWarning($"User is not in the {requiredRole} role");
125+
return new UnauthorizedResult();
126+
}
127+
128+
var content = await new StreamReader(req.Body).ReadToEndAsync();
129+
var documentUpdate = JsonConvert.DeserializeObject<T>(content);
130+
131+
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
132+
133+
//verify existing document (not upserting as this is an update only function)
134+
var feedOptions = new FeedOptions { EnableCrossPartitionQuery = false };
135+
var existingDocument = client.CreateDocumentQuery<T>(collectionUri, feedOptions)
136+
.Where(d => d.Id == documentUpdate.Id)
137+
.AsEnumerable().FirstOrDefault();
138+
139+
if (existingDocument == null)
140+
{
141+
log.LogWarning($"{collectionName}: {documentUpdate.Id} not found.");
142+
return new BadRequestObjectResult($"{collectionName} not found.");
143+
}
144+
145+
var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, documentUpdate.Id);
146+
await client.ReplaceDocumentAsync(documentUri, documentUpdate);
147+
148+
return new OkObjectResult(documentUpdate);
149+
}
150+
151+
protected async Task<IActionResult> DeleteDocument(
152+
HttpRequest req, string id, DocumentClient client, ILogger log, string collectionName)
153+
{
154+
log.LogInformation($"Deleting {collectionName} document: id = {id}");
155+
var authorizationResult = await _apiAuthentication.AuthenticateAsync(req.Headers);
156+
if (authorizationResult.Failed)
157+
{
158+
log.LogWarning(authorizationResult.FailureReason);
159+
return new UnauthorizedResult();
160+
}
161+
162+
if (String.IsNullOrWhiteSpace(id))
163+
{
164+
return new BadRequestObjectResult($"{collectionName}: id was not specified.");
165+
}
166+
167+
Uri collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, collectionName);
168+
169+
//verify existing document (not upserting as this is an update only function)
170+
var feedOptions = new FeedOptions { EnableCrossPartitionQuery = false };
171+
var existingDocument = client.CreateDocumentQuery<T>(collectionUri, feedOptions)
172+
.Where(d => d.Id == id)
173+
.AsEnumerable().FirstOrDefault();
174+
175+
if (existingDocument == null)
176+
{
177+
log.LogWarning($"{collectionName}: {id} not found.");
178+
return new BadRequestObjectResult($"{collectionName} not found.");
179+
}
180+
181+
var documentUri = UriFactory.CreateDocumentUri(DatabaseName, collectionName, id);
182+
183+
await client.DeleteDocumentAsync(documentUri, new RequestOptions
184+
{
185+
PartitionKey = new PartitionKey(id)
186+
});
187+
188+
return new OkObjectResult(true);
189+
}
190+
}
178191
}

api/TwoWeeksReady/Hazards/HazardInfoApi.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.Azure.WebJobs.Extensions.Http;
77
using Microsoft.Extensions.Logging;
88
using AzureFunctions.OidcAuthentication;
9+
using TwoWeeksReady.Authorization;
910

1011
namespace TwoWeeksReady.Hazards
1112
{
@@ -52,7 +53,7 @@ public async Task<IActionResult> CreateDocument(
5253
DocumentClient client,
5354
ILogger log)
5455
{
55-
return await CreateDocument(req, client, log, CollectionName);
56+
return await CreateDocument(req, client, log, CollectionName, Roles.Admin);
5657
}
5758

5859

@@ -64,7 +65,7 @@ public async Task<IActionResult> UpdateDocument(
6465
DocumentClient client,
6566
ILogger log)
6667
{
67-
return await UpdateDocument(req, client, log, CollectionName);
68+
return await UpdateDocument(req, client, log, CollectionName, Roles.Admin);
6869
}
6970

7071

0 commit comments

Comments
 (0)