Skip to content
This repository was archived by the owner on May 17, 2024. It is now read-only.

Commit 35e6ca9

Browse files
committed
first commit
1 parent 1f493cf commit 35e6ca9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+6500
-11031
lines changed
Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using Xunit;
3-
using System.Text.RegularExpressions;
43
using Microsoft.Extensions.Configuration;
54

65
namespace TodoListAPI.Tests
@@ -17,37 +16,30 @@ public static IConfiguration InitConfiguration()
1716
}
1817

1918
[Fact]
20-
public void ShouldNotContainClientId()
19+
public void ShouldContainClientId()
2120
{
2221
var myConfiguration = ConfigurationTests.InitConfiguration();
23-
string clientId = myConfiguration.GetSection("AzureAd")["ClientId"];
22+
var clientId = myConfiguration.GetSection("AzureAd")["ClientId"];
2423

25-
string pattern = @"(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}";
26-
var regex = new Regex(pattern);
27-
Assert.DoesNotMatch(regex, clientId);
24+
Assert.True(Guid.TryParse(clientId, out var theGuid));
2825
}
2926

3027
[Fact]
31-
public void ShouldNotContainTenantId()
28+
public void ShouldContainTenantId()
3229
{
3330
var myConfiguration = ConfigurationTests.InitConfiguration();
34-
string tenantId = myConfiguration.GetSection("AzureAd")["TenantId"];
31+
var tenantId = myConfiguration.GetSection("AzureAd")["TenantId"];
3532

36-
string pattern = @"(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}";
37-
var regex = new Regex(pattern);
38-
Assert.DoesNotMatch(regex, tenantId);
33+
Assert.True(Guid.TryParse(tenantId, out var theGuid));
3934
}
4035

4136
[Fact]
42-
public void ShouldNotContainDomain()
37+
public void ShouldContainDomain()
4338
{
4439
var myConfiguration = ConfigurationTests.InitConfiguration();
45-
string domain = myConfiguration.GetSection("AzureAd")["Domain"];
40+
var domain = $"https://{myConfiguration.GetSection("AzureAd")["Domain"]}";
4641

47-
string pattern = @"(^http[s]?:\/\/|[a-z]*\.[a-z]{3}\.[a-z]{2})|([a-z]*\.[a-z]{3}$)";
48-
var regex = new Regex(pattern);
49-
50-
Assert.DoesNotMatch(regex, domain);
42+
Assert.True(Uri.TryCreate(domain, UriKind.Absolute, out var uri));
5143
}
5244
}
5345
}

5-AccessControl/1-call-api-roles/API/TodoListAPI.Tests/TodoListAPI.Tests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp3.1</TargetFramework>
4+
<TargetFramework>net6.0</TargetFramework>
55

66
<IsPackable>false</IsPackable>
77
</PropertyGroup>

5-AccessControl/1-call-api-roles/API/TodoListAPI.sln

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,29 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 16
44
VisualStudioVersion = 16.0.31005.135
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoListAPI.Tests", "TodoListAPI.Tests\TodoListAPI.Tests.csproj", "{4B329B4A-AA8B-4FF0-857C-507A5D308D55}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoListAPI", "TodoListAPI\TodoListAPI.csproj", "{3E0BC18D-E25D-4E0E-9F6C-712C30350DF8}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoListAPI", "TodoListAPI\TodoListAPI.csproj", "{D6DE3BD7-4A2F-4CD7-9F32-6529F1C2DE57}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoListAPI.Tests", "TodoListAPI.Tests\TodoListAPI.Tests.csproj", "{FD40F6A5-7735-4AA5-ACA8-ADE3FBE65378}"
99
EndProject
1010
Global
1111
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1212
Debug|Any CPU = Debug|Any CPU
1313
Release|Any CPU = Release|Any CPU
1414
EndGlobalSection
1515
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16-
{4B329B4A-AA8B-4FF0-857C-507A5D308D55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17-
{4B329B4A-AA8B-4FF0-857C-507A5D308D55}.Debug|Any CPU.Build.0 = Debug|Any CPU
18-
{4B329B4A-AA8B-4FF0-857C-507A5D308D55}.Release|Any CPU.ActiveCfg = Release|Any CPU
19-
{4B329B4A-AA8B-4FF0-857C-507A5D308D55}.Release|Any CPU.Build.0 = Release|Any CPU
20-
{D6DE3BD7-4A2F-4CD7-9F32-6529F1C2DE57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21-
{D6DE3BD7-4A2F-4CD7-9F32-6529F1C2DE57}.Debug|Any CPU.Build.0 = Debug|Any CPU
22-
{D6DE3BD7-4A2F-4CD7-9F32-6529F1C2DE57}.Release|Any CPU.ActiveCfg = Release|Any CPU
23-
{D6DE3BD7-4A2F-4CD7-9F32-6529F1C2DE57}.Release|Any CPU.Build.0 = Release|Any CPU
16+
{3E0BC18D-E25D-4E0E-9F6C-712C30350DF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{3E0BC18D-E25D-4E0E-9F6C-712C30350DF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{3E0BC18D-E25D-4E0E-9F6C-712C30350DF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{3E0BC18D-E25D-4E0E-9F6C-712C30350DF8}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{FD40F6A5-7735-4AA5-ACA8-ADE3FBE65378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{FD40F6A5-7735-4AA5-ACA8-ADE3FBE65378}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{FD40F6A5-7735-4AA5-ACA8-ADE3FBE65378}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{FD40F6A5-7735-4AA5-ACA8-ADE3FBE65378}.Release|Any CPU.Build.0 = Release|Any CPU
2424
EndGlobalSection
2525
GlobalSection(SolutionProperties) = preSolution
2626
HideSolutionNode = FALSE
2727
EndGlobalSection
2828
GlobalSection(ExtensibilityGlobals) = postSolution
29-
SolutionGuid = {A4F71EA5-B405-4289-A85E-CB882FA2D9C8}
29+
SolutionGuid = {44AB506D-AF3A-4AE5-90E9-CFECC92533A6}
3030
EndGlobalSection
3131
EndGlobal
Lines changed: 79 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
using System;
1+
using System.Linq;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using System.Threading.Tasks;
54
using Microsoft.AspNetCore.Http;
65
using Microsoft.AspNetCore.Mvc;
76
using Microsoft.AspNetCore.Authorization;
87
using Microsoft.EntityFrameworkCore;
9-
using System.Security.Claims;
8+
using Microsoft.Identity.Web;
109
using Microsoft.Identity.Web.Resource;
1110
using TodoListAPI.Models;
1211
using TodoListAPI.Utils;
@@ -18,12 +17,11 @@ namespace TodoListAPI.Controllers
1817
[ApiController]
1918
public class TodoListController : ControllerBase
2019
{
21-
// The Web API will only accept tokens 1) for users, and
22-
// 2) having the access_as_user scope for this API
23-
static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
24-
2520
private readonly TodoContext _context;
2621

22+
private const string _todoListRead = "TodoList.Read";
23+
private const string _todoListReadWrite = "TodoList.ReadWrite";
24+
2725
public TodoListController(TodoContext context)
2826
{
2927
_context = context;
@@ -33,117 +31,142 @@ public TodoListController(TodoContext context)
3331
[HttpGet]
3432
[Route("getAll")]
3533
[Authorize(Policy = AuthorizationPolicies.AssignmentToTaskAdminRoleRequired)]
34+
[RequiredScopeOrAppPermission(
35+
AcceptedScope = new string[] { _todoListRead }
36+
)]
3637
public async Task<ActionResult<IEnumerable<TodoItem>>> GetAll()
3738
{
38-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
3939
return await _context.TodoItems.ToListAsync();
4040
}
4141

42-
// GET: api/todolist
42+
// GET: api/TodoItems
4343
[HttpGet]
4444
[Authorize(Policy = AuthorizationPolicies.AssignmentToTaskUserRoleRequired)]
45+
/// <summary>
46+
/// Access tokens that have neither the 'scp' (for delegated permissions) nor
47+
/// 'roles' (for application permissions) claim are not to be honored.
48+
///
49+
/// An access token issued by Azure AD will have at least one of the two claims. Access tokens
50+
/// issued to a user will have the 'scp' claim. Access tokens issued to an application will have
51+
/// the roles claim. Access tokens that contain both claims are issued only to users, where the scp
52+
/// claim designates the delegated permissions, while the roles claim designates the user's role.
53+
///
54+
/// To determine whether an access token was issued to a user (i.e delegated) or an application
55+
/// more easily, we recommend enabling the optional claim 'idtyp'. For more information, see:
56+
/// https://docs.microsoft.com/azure/active-directory/develop/access-tokens#user-and-application-tokens
57+
/// </summary>
58+
[RequiredScopeOrAppPermission(
59+
AcceptedScope = new string[] { _todoListRead, _todoListReadWrite }
60+
)]
4561
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
4662
{
47-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
48-
string owner = User.FindFirst("preferred_username")?.Value;
49-
return await _context.TodoItems.Where(item => item.Owner == owner).ToListAsync();
63+
/// <summary>
64+
/// The 'oid' (object id) is the only claim that should be used to uniquely identify
65+
/// a user in an Azure AD tenant. The token might have one or more of the following claim,
66+
/// that might seem like a unique identifier, but is not and should not be used as such:
67+
///
68+
/// - upn (user principal name): might be unique amongst the active set of users in a tenant
69+
/// but tend to get reassigned to new employees as employees leave the organization and others
70+
/// take their place or might change to reflect a personal change like marriage.
71+
///
72+
/// - email: might be unique amongst the active set of users in a tenant but tend to get reassigned
73+
/// to new employees as employees leave the organization and others take their place.
74+
/// </summary>
75+
return await _context.TodoItems.Where(x => x.Owner == HttpContext.User.GetObjectId()).ToListAsync();
5076
}
5177

52-
// GET: api/todolist/5
78+
// GET: api/TodoItems/5
5379
[HttpGet("{id}")]
5480
[Authorize(Policy = AuthorizationPolicies.AssignmentToTaskUserRoleRequired)]
81+
[RequiredScopeOrAppPermission(
82+
AcceptedScope = new string[] { _todoListRead, _todoListReadWrite }
83+
)]
5584
public async Task<ActionResult<TodoItem>> GetTodoItem(int id)
5685
{
57-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
58-
59-
var todoItem = await _context.TodoItems.FindAsync(id);
60-
61-
if (todoItem == null)
62-
{
63-
return NotFound();
64-
}
65-
66-
return todoItem;
86+
return await _context.TodoItems.FirstOrDefaultAsync(t => t.Id == id && t.Owner == HttpContext.User.GetObjectId());
6787
}
6888

69-
// PUT: api/todolist/5
89+
// PUT: api/TodoItems/5
7090
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
7191
// more details see https://aka.ms/RazorPagesCRUD.
7292
[HttpPut("{id}")]
7393
[Authorize(Policy = AuthorizationPolicies.AssignmentToTaskUserRoleRequired)]
94+
[RequiredScopeOrAppPermission(
95+
AcceptedScope = new string[] { _todoListReadWrite }
96+
)]
7497
public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
7598
{
76-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
77-
78-
if (id != todoItem.Id)
99+
if (id != todoItem.Id || !_context.TodoItems.Any(x => x.Id == id))
79100
{
80-
return BadRequest();
101+
return NotFound();
81102
}
82103

83-
_context.Entry(todoItem).State = EntityState.Modified;
84104

85-
try
86-
{
87-
await _context.SaveChangesAsync();
88-
}
89-
catch (DbUpdateConcurrencyException)
105+
if (_context.TodoItems.Any(x => x.Id == id && x.Owner == HttpContext.User.GetObjectId()))
90106
{
91-
if (!TodoItemExists(id))
107+
_context.Entry(todoItem).State = EntityState.Modified;
108+
109+
try
92110
{
93-
return NotFound();
111+
await _context.SaveChangesAsync();
94112
}
95-
else
113+
catch (DbUpdateConcurrencyException)
96114
{
97-
throw;
115+
if (!_context.TodoItems.Any(e => e.Id == id))
116+
{
117+
return NotFound();
118+
}
119+
else
120+
{
121+
throw;
122+
}
98123
}
99124
}
100125

101126
return NoContent();
102127
}
103128

104-
// POST: api/todolist
129+
// POST: api/TodoItems
105130
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
106131
// more details see https://aka.ms/RazorPagesCRUD.
107132
[HttpPost]
108133
[Authorize(Policy = AuthorizationPolicies.AssignmentToTaskUserRoleRequired)]
134+
[RequiredScopeOrAppPermission(
135+
AcceptedScope = new string[] { _todoListReadWrite }
136+
)]
109137
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
110138
{
111-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
112-
113-
string owner = User.FindFirst("preferred_username")?.Value;
114-
todoItem.Owner = owner;
115-
139+
todoItem.Owner = HttpContext.User.GetObjectId();
116140
todoItem.Status = false;
117141

118-
119142
_context.TodoItems.Add(todoItem);
120143
await _context.SaveChangesAsync();
121144

122145
return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
123146
}
124147

125-
// DELETE: api/todolist/5
148+
// DELETE: api/TodoItems/5
126149
[HttpDelete("{id}")]
127150
[Authorize(Policy = AuthorizationPolicies.AssignmentToTaskUserRoleRequired)]
151+
[RequiredScopeOrAppPermission(
152+
AcceptedScope = new string[] { _todoListReadWrite }
153+
)]
128154
public async Task<ActionResult<TodoItem>> DeleteTodoItem(int id)
129155
{
130-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
156+
TodoItem todoItem = await _context.TodoItems.FindAsync(id);
131157

132-
var todoItem = await _context.TodoItems.FindAsync(id);
133158
if (todoItem == null)
134159
{
135160
return NotFound();
136161
}
137162

138-
_context.TodoItems.Remove(todoItem);
139-
await _context.SaveChangesAsync();
140-
141-
return todoItem;
142-
}
163+
if (_context.TodoItems.Any(x => x.Id == id && x.Owner == HttpContext.User.GetObjectId()))
164+
{
165+
_context.TodoItems.Remove(todoItem);
166+
await _context.SaveChangesAsync();
167+
}
143168

144-
private bool TodoItemExists(int id)
145-
{
146-
return _context.TodoItems.Any(e => e.Id == id);
169+
return NoContent();
147170
}
148171
}
149172
}

0 commit comments

Comments
 (0)