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

Commit ff8638d

Browse files
committed
update service, readme
1 parent e58d9ff commit ff8638d

File tree

4 files changed

+247
-342
lines changed

4 files changed

+247
-342
lines changed

3-Authorization-II/1-call-api/API/TodoListAPI/Controllers/TodoListController.cs

+98-148
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,13 @@
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 TodoListAPI.Models;
10-
using System.Security.Claims;
118
using Microsoft.Identity.Web;
129
using Microsoft.Identity.Web.Resource;
13-
14-
15-
16-
// using Microsoft.AspNetCore.Authorization;
17-
// using Microsoft.AspNetCore.Http;
18-
// using Microsoft.AspNetCore.Mvc;
19-
// using Microsoft.Identity.Web;
20-
// using Microsoft.Identity.Web.Resource;
21-
// using System;
22-
// using System.Collections.Generic;
23-
// using System.Linq;
24-
// using System.Security.Claims;
25-
// using TodoListClient.Models;
10+
using TodoListAPI.Models;
2611

2712
namespace TodoListAPI.Controllers
2813
{
@@ -31,30 +16,56 @@ namespace TodoListAPI.Controllers
3116
[ApiController]
3217
public class TodoListController : ControllerBase
3318
{
34-
// The Web API will only accept tokens 1) for users, and
35-
// 2) having the access_as_user scope for this API
36-
static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
37-
3819
private readonly TodoContext _context;
3920

21+
private const string _todoListRead = "TodoList.Read";
22+
private const string _todoListReadWrite = "TodoList.ReadWrite";
23+
private const string _todoListReadAll = "TodoList.Read.All";
24+
private const string _todoListReadWriteAll = "TodoList.ReadWrite.All";
25+
4026
public TodoListController(TodoContext context)
4127
{
4228
_context = context;
4329
}
4430

4531
// GET: api/TodoItems
4632
[HttpGet]
33+
/// <summary>
34+
/// Access tokens that have neither the 'scp' (for delegated permissions) nor
35+
/// 'roles' (for application permissions) claim are not to be honored.
36+
///
37+
/// An access token issued by Azure AD will have at least one of the two claims. Access tokens
38+
/// issued to a user will have the 'scp' claim. Access tokens issued to an application will have
39+
/// the roles claim. Access tokens that contain both claims are issued only to users, where the scp
40+
/// claim designates the delegated permissions, while the roles claim designates the user's role.
41+
///
42+
/// To determine whether an access token was issued to a user (i.e delegated) or an application
43+
/// more easily, we recommend enabling the optional claim 'idtyp'. For more information, see:
44+
/// https://docs.microsoft.com/azure/active-directory/develop/access-tokens#user-and-application-tokens
45+
/// </summary>
4746
[RequiredScopeOrAppPermission(
48-
AcceptedScope = new string[] { "TodoList.Read", "TodoList.ReadWrite" },
49-
AcceptedAppPermission = new string[] { "TodoList.Read.All", "TodoList.ReadWrite.All" }
47+
AcceptedScope = new string[] { _todoListRead, _todoListReadWrite },
48+
AcceptedAppPermission = new string[] { _todoListReadAll, _todoListReadWriteAll }
5049
)]
5150
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
5251
{
53-
if (HasDelegatedPermissions(new string[] { "TodoList.Read", "TodoList.ReadWrite" }))
52+
if (HasDelegatedPermissions(new string[] { _todoListRead, _todoListReadWrite }))
5453
{
54+
/// <summary>
55+
/// The 'oid' (object id) is the only claim that should be used to uniquely identify
56+
/// a user in an Azure AD tenant. The token might have one or more of the following claim,
57+
/// that might seem like a unique identifier, but is not and should not be used as such:
58+
///
59+
/// - upn (user principal name): might be unique amongst the active set of users in a tenant
60+
/// but tend to get reassigned to new employees as employees leave the organization and others
61+
/// take their place or might change to reflect a personal change like marriage.
62+
///
63+
/// - email: might be unique amongst the active set of users in a tenant but tend to get reassigned
64+
/// to new employees as employees leave the organization and others take their place.
65+
/// </summary>
5566
return await _context.TodoItems.Where(x => x.Owner == HttpContext.User.GetObjectId()).ToListAsync();
5667
}
57-
else if (HasApplicationPermissions(new string[] { "TodoList.Read.All", "TodoList.ReadWrite.All" }))
68+
else if (HasApplicationPermissions(new string[] { _todoListReadAll, _todoListReadWriteAll }))
5869
{
5970
return await _context.TodoItems.ToListAsync();
6071
}
@@ -64,55 +75,49 @@ public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
6475

6576
// GET: api/TodoItems/5
6677
[HttpGet("{id}")]
78+
[RequiredScopeOrAppPermission(
79+
AcceptedScope = new string[] { _todoListRead, _todoListReadWrite },
80+
AcceptedAppPermission = new string[] { _todoListReadAll, _todoListReadWriteAll }
81+
)]
6782
public async Task<ActionResult<TodoItem>> GetTodoItem(int id)
6883
{
69-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
70-
71-
var todoItem = await _context.TodoItems.FindAsync(id);
72-
73-
if (todoItem == null)
84+
// if it only has delegated permissions, then it will be t.id==id && x.Owner == owner
85+
// if it has app permissions the it will return t.id==id
86+
if (HasDelegatedPermissions(new string[] { _todoListRead, _todoListReadWrite }))
7487
{
75-
return NotFound();
88+
return await _context.TodoItems.FirstOrDefaultAsync(t => t.Id == id && t.Owner == HttpContext.User.GetObjectId());
89+
}
90+
else if (HasApplicationPermissions(new string[] { _todoListReadAll, _todoListReadWriteAll }))
91+
{
92+
return await _context.TodoItems.FirstOrDefaultAsync(t => t.Id == id);
7693
}
7794

78-
return todoItem;
95+
return null;
7996
}
8097

81-
// [HttpGet("{id}", Name = "Get")]
82-
// [RequiredScopeOrAppPermission(
83-
// AcceptedScope = new string[] { "ToDoList.Read", "ToDoList.ReadWrite" },
84-
// AcceptedAppPermission = new string[] { "ToDoList.Read.All", "ToDoList.ReadWrite.All" })]
85-
// public Todo Get(int id)
86-
// {
87-
// //if it only has delegated permissions
88-
// //then it will be t.id==id && x.Owner == owner
89-
// //if it has app permissions the it will return t.id==id
90-
91-
// if (HasDelegatedPermissions(new string[] { "ToDoList.Read", "ToDoList.ReadWrite" }))
92-
// {
93-
// return TodoStore.Values.FirstOrDefault(t => t.Id == id && t.Owner == _contextAccessor.HttpContext.User.GetObjectId());
94-
// }
95-
// else if (HasApplicationPermissions(new string[] { "ToDoList.Read.All", "ToDoList.ReadWrite.All" }))
96-
// {
97-
// return TodoStore.Values.FirstOrDefault(t => t.Id == id);
98-
// }
99-
100-
// return null;
101-
// }
102-
10398
// PUT: api/TodoItems/5
10499
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
105100
// more details see https://aka.ms/RazorPagesCRUD.
106101
[HttpPut("{id}")]
102+
[RequiredScopeOrAppPermission(
103+
AcceptedScope = new string[] { _todoListReadWrite },
104+
AcceptedAppPermission = new string[] { _todoListReadWriteAll }
105+
)]
107106
public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
108107
{
109-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
110-
111-
if (id != todoItem.Id)
108+
if (id != todoItem.Id || !_context.TodoItems.Any(x => x.Id == id))
112109
{
113-
return BadRequest();
110+
return NotFound();
114111
}
115112

113+
114+
if (HasDelegatedPermissions(new string[] { _todoListReadWrite })
115+
&& _context.TodoItems.Any(x => x.Id == id && x.Owner == HttpContext.User.GetObjectId())
116+
&& todoItem.Owner == HttpContext.User.GetObjectId()
117+
||
118+
HasApplicationPermissions(new string[] { _todoListReadWriteAll })
119+
)
120+
{
116121
_context.Entry(todoItem).State = EntityState.Modified;
117122

118123
try
@@ -121,7 +126,7 @@ public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
121126
}
122127
catch (DbUpdateConcurrencyException)
123128
{
124-
if (!TodoItemExists(id))
129+
if (!_context.TodoItems.Any(e => e.Id == id))
125130
{
126131
return NotFound();
127132
}
@@ -130,49 +135,29 @@ public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
130135
throw;
131136
}
132137
}
138+
}
133139

134140
return NoContent();
135141
}
136142

137-
// [HttpPatch("{id}")]
138-
// [RequiredScopeOrAppPermission(
139-
// AcceptedScope = new string[] { "ToDoList.ReadWrite" },
140-
// AcceptedAppPermission = new string[] { "ToDoList.ReadWrite.All" })]
141-
// public IActionResult Patch(int id, [FromBody] Todo todo)
142-
// {
143-
// if (id != todo.Id || !TodoStore.Values.Any(x => x.Id == id))
144-
// {
145-
// return NotFound();
146-
// }
147-
148-
// if (
149-
// HasDelegatedPermissions(new string[] { "ToDoList.ReadWrite" })
150-
// && TodoStore.Values.Any(x => x.Id == id && x.Owner == _contextAccessor.HttpContext.User.GetObjectId())
151-
// && todo.Owner == _contextAccessor.HttpContext.User.GetObjectId()
152-
153-
// ||
154-
155-
// HasApplicationPermissions(new string[] { "ToDoList.ReadWrite.All" })
156-
157-
// )
158-
// {
159-
// TodoStore.Remove(id);
160-
// TodoStore.Add(id, todo);
161-
162-
// return Ok(todo);
163-
// }
164-
165-
// return BadRequest();
166-
// }
167-
168143
// POST: api/TodoItems
169144
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
170145
// more details see https://aka.ms/RazorPagesCRUD.
171146
[HttpPost]
147+
[RequiredScopeOrAppPermission(
148+
AcceptedScope = new string[] { _todoListReadWrite },
149+
AcceptedAppPermission = new string[] { _todoListReadWriteAll }
150+
)]
172151
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
173152
{
174-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
175-
string owner = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
153+
string owner = HttpContext.User.GetObjectId();
154+
155+
if (HasApplicationPermissions(new string[] { _todoListReadWriteAll }))
156+
{
157+
// with such a permission any owner name is accepted
158+
owner = todoItem.Owner;
159+
}
160+
176161
todoItem.Owner = owner;
177162
todoItem.Status = false;
178163

@@ -182,83 +167,48 @@ public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
182167
return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
183168
}
184169

185-
// [HttpPost]
186-
// [RequiredScopeOrAppPermission(
187-
// AcceptedScope = new string[] { "ToDoList.ReadWrite" },
188-
// AcceptedAppPermission = new string[] { "ToDoList.ReadWrite.All" })]
189-
// public IActionResult Post([FromBody] Todo todo)
190-
// {
191-
// var owner = _contextAccessor.HttpContext.User.GetObjectId();
192-
193-
// if (HasApplicationPermissions(new string[] { "ToDoList.ReadWrite.All" }))
194-
// {
195-
// //with such a permission any owner name is accepted from UI
196-
// owner = todo.Owner;
197-
// }
198-
199-
// int id = TodoStore.Values.OrderByDescending(x => x.Id).FirstOrDefault().Id + 1;
200-
// Todo todonew = new Todo() { Id = id, Owner = owner, Title = todo.Title };
201-
// TodoStore.Add(id, todonew);
202-
203-
// return Ok(todo);
204-
// }
205-
206170
// DELETE: api/TodoItems/5
207171
[HttpDelete("{id}")]
172+
[RequiredScopeOrAppPermission(
173+
AcceptedScope = new string[] { _todoListReadWrite },
174+
AcceptedAppPermission = new string[] { _todoListReadWriteAll }
175+
)]
208176
public async Task<ActionResult<TodoItem>> DeleteTodoItem(int id)
209177
{
210-
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
178+
TodoItem todoItem = await _context.TodoItems.FindAsync(id);
211179

212-
var todoItem = await _context.TodoItems.FindAsync(id);
213180
if (todoItem == null)
214181
{
215182
return NotFound();
216183
}
217184

218-
_context.TodoItems.Remove(todoItem);
219-
await _context.SaveChangesAsync();
220-
221-
return todoItem;
222-
}
223-
224-
// [HttpDelete("{id}")]
225-
// [RequiredScopeOrAppPermission(
226-
// AcceptedScope = new string[] { "ToDoList.ReadWrite" },
227-
// AcceptedAppPermission = new string[] { "ToDoList.ReadWrite.All" })]
228-
// public void Delete(int id)
229-
// {
230-
// if (
231-
// (
232-
233-
// HasDelegatedPermissions(new string[] { "ToDoList.ReadWrite" }) && TodoStore.Values.Any(x => x.Id == id && x.Owner == _contextAccessor.HttpContext.User.GetObjectId()))
234-
235-
// ||
236-
237-
// HasApplicationPermissions(new string[] { "ToDoList.ReadWrite.All" })
238-
// )
239-
// {
240-
// TodoStore.Remove(id);
241-
// }
242-
// }
243-
244-
private bool TodoItemExists(int id)
245-
{
246-
return _context.TodoItems.Any(e => e.Id == id);
185+
if ((HasDelegatedPermissions(new string[] { _todoListReadWrite })
186+
&& _context.TodoItems.Any(x => x.Id == id && x.Owner == HttpContext.User.GetObjectId()))
187+
|| HasApplicationPermissions(new string[] { _todoListReadWriteAll }))
188+
{
189+
_context.TodoItems.Remove(todoItem);
190+
await _context.SaveChangesAsync();
191+
return todoItem;
192+
}
193+
else
194+
{
195+
return BadRequest();
196+
}
247197
}
248198

249-
//Checks if the presented token has application permissions
199+
// Checks if the presented token has application permissions
250200
private bool HasApplicationPermissions(string[] permissionsNames)
251201
{
252202
var rolesClaim = User.Claims.Where(
253-
c => c.Type == ClaimConstants.Roles || c.Type == ClaimConstants.Role)
254-
.SelectMany(c => c.Value.Split(' '));
203+
c => c.Type == ClaimConstants.Roles || c.Type == ClaimConstants.Role)
204+
.SelectMany(c => c.Value.Split(' '));
255205

256206
var result = rolesClaim.Any(v => permissionsNames.Any(p => p.Equals(v)));
257207

258208
return result;
259209
}
260210

261-
//Checks if the presented token has delegated permissions
211+
// Checks if the presented token has delegated permissions
262212
private bool HasDelegatedPermissions(string[] scopesNames)
263213
{
264214
var result = (User.FindFirst(ClaimConstants.Scp) ?? User.FindFirst(ClaimConstants.Scope))?

0 commit comments

Comments
 (0)