1
- using System ;
1
+ using System . Linq ;
2
2
using System . Collections . Generic ;
3
- using System . Linq ;
4
3
using System . Threading . Tasks ;
5
4
using Microsoft . AspNetCore . Http ;
6
5
using Microsoft . AspNetCore . Mvc ;
7
6
using Microsoft . AspNetCore . Authorization ;
8
7
using Microsoft . EntityFrameworkCore ;
9
- using TodoListAPI . Models ;
10
- using System . Security . Claims ;
11
8
using Microsoft . Identity . Web ;
12
9
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 ;
26
11
27
12
namespace TodoListAPI . Controllers
28
13
{
@@ -31,30 +16,56 @@ namespace TodoListAPI.Controllers
31
16
[ ApiController ]
32
17
public class TodoListController : ControllerBase
33
18
{
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
-
38
19
private readonly TodoContext _context ;
39
20
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
+
40
26
public TodoListController ( TodoContext context )
41
27
{
42
28
_context = context ;
43
29
}
44
30
45
31
// GET: api/TodoItems
46
32
[ 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>
47
46
[ 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 }
50
49
) ]
51
50
public async Task < ActionResult < IEnumerable < TodoItem > > > GetTodoItems ( )
52
51
{
53
- if ( HasDelegatedPermissions ( new string [ ] { "TodoList.Read" , "TodoList.ReadWrite" } ) )
52
+ if ( HasDelegatedPermissions ( new string [ ] { _todoListRead , _todoListReadWrite } ) )
54
53
{
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>
55
66
return await _context . TodoItems . Where ( x => x . Owner == HttpContext . User . GetObjectId ( ) ) . ToListAsync ( ) ;
56
67
}
57
- else if ( HasApplicationPermissions ( new string [ ] { "TodoList.Read.All" , "TodoList.ReadWrite.All" } ) )
68
+ else if ( HasApplicationPermissions ( new string [ ] { _todoListReadAll , _todoListReadWriteAll } ) )
58
69
{
59
70
return await _context . TodoItems . ToListAsync ( ) ;
60
71
}
@@ -64,55 +75,49 @@ public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
64
75
65
76
// GET: api/TodoItems/5
66
77
[ HttpGet ( "{id}" ) ]
78
+ [ RequiredScopeOrAppPermission (
79
+ AcceptedScope = new string [ ] { _todoListRead , _todoListReadWrite } ,
80
+ AcceptedAppPermission = new string [ ] { _todoListReadAll , _todoListReadWriteAll }
81
+ ) ]
67
82
public async Task < ActionResult < TodoItem > > GetTodoItem ( int id )
68
83
{
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 } ) )
74
87
{
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 ) ;
76
93
}
77
94
78
- return todoItem ;
95
+ return null ;
79
96
}
80
97
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
-
103
98
// PUT: api/TodoItems/5
104
99
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
105
100
// more details see https://aka.ms/RazorPagesCRUD.
106
101
[ HttpPut ( "{id}" ) ]
102
+ [ RequiredScopeOrAppPermission (
103
+ AcceptedScope = new string [ ] { _todoListReadWrite } ,
104
+ AcceptedAppPermission = new string [ ] { _todoListReadWriteAll }
105
+ ) ]
107
106
public async Task < IActionResult > PutTodoItem ( int id , TodoItem todoItem )
108
107
{
109
- HttpContext . VerifyUserHasAnyAcceptedScope ( scopeRequiredByApi ) ;
110
-
111
- if ( id != todoItem . Id )
108
+ if ( id != todoItem . Id || ! _context . TodoItems . Any ( x => x . Id == id ) )
112
109
{
113
- return BadRequest ( ) ;
110
+ return NotFound ( ) ;
114
111
}
115
112
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
+ {
116
121
_context . Entry ( todoItem ) . State = EntityState . Modified ;
117
122
118
123
try
@@ -121,7 +126,7 @@ public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
121
126
}
122
127
catch ( DbUpdateConcurrencyException )
123
128
{
124
- if ( ! TodoItemExists ( id ) )
129
+ if ( ! _context . TodoItems . Any ( e => e . Id == id ) )
125
130
{
126
131
return NotFound ( ) ;
127
132
}
@@ -130,49 +135,29 @@ public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
130
135
throw ;
131
136
}
132
137
}
138
+ }
133
139
134
140
return NoContent ( ) ;
135
141
}
136
142
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
-
168
143
// POST: api/TodoItems
169
144
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
170
145
// more details see https://aka.ms/RazorPagesCRUD.
171
146
[ HttpPost ]
147
+ [ RequiredScopeOrAppPermission (
148
+ AcceptedScope = new string [ ] { _todoListReadWrite } ,
149
+ AcceptedAppPermission = new string [ ] { _todoListReadWriteAll }
150
+ ) ]
172
151
public async Task < ActionResult < TodoItem > > PostTodoItem ( TodoItem todoItem )
173
152
{
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
+
176
161
todoItem . Owner = owner ;
177
162
todoItem . Status = false ;
178
163
@@ -182,83 +167,48 @@ public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
182
167
return CreatedAtAction ( "GetTodoItem" , new { id = todoItem . Id } , todoItem ) ;
183
168
}
184
169
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
-
206
170
// DELETE: api/TodoItems/5
207
171
[ HttpDelete ( "{id}" ) ]
172
+ [ RequiredScopeOrAppPermission (
173
+ AcceptedScope = new string [ ] { _todoListReadWrite } ,
174
+ AcceptedAppPermission = new string [ ] { _todoListReadWriteAll }
175
+ ) ]
208
176
public async Task < ActionResult < TodoItem > > DeleteTodoItem ( int id )
209
177
{
210
- HttpContext . VerifyUserHasAnyAcceptedScope ( scopeRequiredByApi ) ;
178
+ TodoItem todoItem = await _context . TodoItems . FindAsync ( id ) ;
211
179
212
- var todoItem = await _context . TodoItems . FindAsync ( id ) ;
213
180
if ( todoItem == null )
214
181
{
215
182
return NotFound ( ) ;
216
183
}
217
184
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
+ }
247
197
}
248
198
249
- //Checks if the presented token has application permissions
199
+ // Checks if the presented token has application permissions
250
200
private bool HasApplicationPermissions ( string [ ] permissionsNames )
251
201
{
252
202
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 ( ' ' ) ) ;
255
205
256
206
var result = rolesClaim . Any ( v => permissionsNames . Any ( p => p . Equals ( v ) ) ) ;
257
207
258
208
return result ;
259
209
}
260
210
261
- //Checks if the presented token has delegated permissions
211
+ // Checks if the presented token has delegated permissions
262
212
private bool HasDelegatedPermissions ( string [ ] scopesNames )
263
213
{
264
214
var result = ( User . FindFirst ( ClaimConstants . Scp ) ?? User . FindFirst ( ClaimConstants . Scope ) ) ?
0 commit comments