Skip to content

Commit c334116

Browse files
author
Tiago Brenck
committed
Edit TodoItem
Calling Graph Few refactoring
1 parent e065eea commit c334116

File tree

14 files changed

+314
-46
lines changed

14 files changed

+314
-46
lines changed

1-WebApp-OIDC/1-2-AnyOrg/AppCreationScripts/sample.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
"HomePage": "https://localhost:44321/",
2121
"ReplyUrls": "https://localhost:44321/, https://localhost:44321/signin-oidc",
2222
"LogoutUrl": "https://localhost:44321/signout-oidc",
23+
"RequiredResourcesAccess": [
24+
{
25+
"Resource": "Microsoft Graph",
26+
"DelegatedPermissions": [ "User.Read" ]
27+
}
28+
]
2329
}
2430
],
2531

@@ -46,6 +52,10 @@
4652
{
4753
"key": "Domain",
4854
"value": "$tenantName"
55+
},
56+
{
57+
"key": "ClientSecret",
58+
"value": ".AppKey"
4959
}
5060
]
5161
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.AspNetCore.Mvc.Rendering;
2+
using Microsoft.Graph;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace WebApp_OpenIDConnect_DotNet.BLL
9+
{
10+
public interface IMSGraphService
11+
{
12+
Task<IEnumerable<User>> GetUsersAsync(string accessToken);
13+
}
14+
}

1-WebApp-OIDC/1-2-AnyOrg/BLL/ITodoItemService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ namespace WebApp_OpenIDConnect_DotNet.BLL
99
{
1010
public interface ITodoItemService
1111
{
12-
Task<TodoItem> Create(string text, ClaimsPrincipal loggedUser);
13-
Task<IEnumerable<TodoItem>> List(bool showAll, ClaimsPrincipal loggedUser);
14-
Task<TodoItem> Get(int id, ClaimsPrincipal loggedUser);
15-
Task Delete(int id, ClaimsPrincipal loggedUser);
12+
Task<TodoItem> Create(string text, ClaimsPrincipal user);
13+
Task<IEnumerable<TodoItem>> List(bool showAll, ClaimsPrincipal user);
14+
Task<TodoItem> Get(int id, ClaimsPrincipal user);
15+
Task Delete(int id, ClaimsPrincipal user);
16+
Task<TodoItem> Edit(TodoItem todoItem, ClaimsPrincipal user);
1617
}
1718
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using Microsoft.AspNetCore.Mvc.Rendering;
2+
using Microsoft.Graph;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Net.Http.Headers;
8+
using System.Threading.Tasks;
9+
10+
namespace WebApp_OpenIDConnect_DotNet.BLL
11+
{
12+
public class MSGraphService : IMSGraphService
13+
{
14+
// the Graph SDK's GraphServiceClient
15+
private GraphServiceClient graphServiceClient;
16+
17+
/// <summary>
18+
/// Gets the users in a tenant.
19+
/// </summary>
20+
/// <param name="accessToken">The access token for MS Graph.</param>
21+
/// <returns>
22+
/// A list of users
23+
/// </returns>
24+
public async Task<IEnumerable<User>> GetUsersAsync(string accessToken)
25+
{
26+
var usersDropDown = new List<SelectListItem>();
27+
28+
try
29+
{
30+
PrepareAuthenticatedClient(accessToken);
31+
IGraphServiceUsersCollectionPage users = await graphServiceClient.Users.Request()
32+
.Filter($"accountEnabled eq true")
33+
.Select("id, userPrincipalName")
34+
.GetAsync();
35+
36+
if (users?.CurrentPage.Count > 0)
37+
{
38+
usersDropDown = users.Select(u => new SelectListItem
39+
{
40+
Text = u.UserPrincipalName,
41+
Value = u.Id
42+
}).ToList();
43+
}
44+
}
45+
catch (ServiceException e)
46+
{
47+
Debug.WriteLine("We could not retrieve the user's list: " + $"{e}");
48+
return null;
49+
}
50+
51+
return usersDropDown;
52+
}
53+
54+
/// <summary>
55+
/// Prepares the authenticated client.
56+
/// </summary>
57+
/// <param name="accessToken">The access token.</param>
58+
private void PrepareAuthenticatedClient(string accessToken)
59+
{
60+
try
61+
{
62+
graphServiceClient = new GraphServiceClient("https://graph.microsoft.com/beta",
63+
new DelegateAuthenticationProvider(
64+
async (requestMessage) =>
65+
{
66+
await Task.Run(() =>
67+
{
68+
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
69+
});
70+
}));
71+
}
72+
catch (Exception ex)
73+
{
74+
Debug.WriteLine($"Could not create a graph client {ex}");
75+
}
76+
}
77+
}
78+
}

1-WebApp-OIDC/1-2-AnyOrg/BLL/TodoItemService.cs

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,45 @@ public TodoItemService(SampleDbContext sampleDbContext)
1818
this.sampleDbContext = sampleDbContext;
1919
}
2020

21-
public async Task<TodoItem> Create(string text, ClaimsPrincipal loggedUser)
21+
/// <summary>
22+
/// Lists only items from the user's tenant
23+
/// </summary>
24+
/// <param name="showAll"></param>
25+
/// <param name="user"></param>
26+
/// <returns></returns>
27+
public async Task<IEnumerable<TodoItem>> List(bool showAll, ClaimsPrincipal user)
2228
{
29+
var tenantId = user.GetTenantId();
30+
var userIdentifier = user.GetObjectId();
31+
32+
var filteredResult = sampleDbContext.TodoItems.Where(x => x.TenantId == tenantId);
33+
34+
if (showAll)
35+
return await filteredResult.ToListAsync();
36+
else
37+
return await filteredResult.Where(x => x.AssignedTo == userIdentifier).ToListAsync();
38+
}
39+
40+
public async Task<TodoItem> Get(int id, ClaimsPrincipal user)
41+
{
42+
var tenantId = user.GetTenantId();
43+
var userIdentifier = user.GetObjectId();
44+
45+
return await sampleDbContext.TodoItems.SingleOrDefaultAsync(x =>
46+
x.Id == id
47+
&& x.TenantId == tenantId
48+
&& x.AssignedTo == userIdentifier);
49+
}
50+
51+
public async Task<TodoItem> Create(string text, ClaimsPrincipal user)
52+
{
53+
//TodoItem table has the TenantId column so we can separate data from each different tenant, preserving its confidentiality
2354
TodoItem todoItem = new TodoItem
24-
{
55+
{
2556
Text = text,
26-
UserName = loggedUser.Identity.Name,
27-
AssignedTo = loggedUser.GetObjectId(),
28-
TenantId = loggedUser.GetTenantId()
57+
UserName = user.Identity.Name,
58+
AssignedTo = user.GetObjectId(),
59+
TenantId = user.GetTenantId()
2960
};
3061

3162
sampleDbContext.TodoItems.Add(todoItem);
@@ -34,38 +65,43 @@ public async Task<TodoItem> Create(string text, ClaimsPrincipal loggedUser)
3465
return todoItem;
3566
}
3667

37-
public async Task Delete(int id, ClaimsPrincipal loggedUser)
68+
public async Task<TodoItem> Edit(TodoItem todoItem, ClaimsPrincipal user)
69+
{
70+
//Validate item ownership
71+
if (!IsAuthorizedToModify(todoItem.Id, user))
72+
throw new InvalidOperationException();
73+
74+
sampleDbContext.TodoItems.Update(todoItem);
75+
await sampleDbContext.SaveChangesAsync();
76+
77+
return todoItem;
78+
}
79+
80+
public async Task Delete(int id, ClaimsPrincipal user)
3881
{
39-
var todoItem = await Get(id, loggedUser);
82+
//Validate item ownership
83+
if (!IsAuthorizedToModify(id, user))
84+
throw new InvalidOperationException();
85+
86+
var todoItem = await Get(id, user);
4087
if (todoItem != null)
4188
{
4289
sampleDbContext.TodoItems.Remove(todoItem);
4390
await sampleDbContext.SaveChangesAsync();
4491
}
4592
}
46-
47-
public async Task<TodoItem> Get(int id, ClaimsPrincipal loggedUser)
48-
{
49-
var tenantId = loggedUser.GetTenantId();
50-
var userIdentifier = loggedUser.GetObjectId();
51-
52-
return await sampleDbContext.TodoItems.SingleOrDefaultAsync(x =>
53-
x.Id == id
54-
&& x.TenantId == tenantId
55-
&& x.AssignedTo == userIdentifier);
56-
}
57-
58-
public async Task<IEnumerable<TodoItem>> List(bool showAll, ClaimsPrincipal loggedUser)
93+
94+
private bool IsAuthorizedToModify(int itemId, ClaimsPrincipal user)
5995
{
60-
var tenantId = loggedUser.GetTenantId();
61-
var userIdentifier = loggedUser.GetObjectId();
62-
63-
var filteredResult = sampleDbContext.TodoItems.Where(x => x.TenantId == tenantId);
96+
var tenantId = user.GetTenantId();
97+
var userIdentifier = user.GetObjectId();
6498

65-
if (showAll)
66-
return await filteredResult.ToListAsync();
67-
else
68-
return await filteredResult.Where(x => x.AssignedTo == userIdentifier).ToListAsync();
99+
// The user can only modify their own items
100+
return sampleDbContext.TodoItems
101+
.AsNoTracking()
102+
.Where(x => x.Id == itemId
103+
&& x.TenantId == tenantId
104+
&& x.AssignedTo == userIdentifier).Count() == 1;
69105
}
70106
}
71107
}

1-WebApp-OIDC/1-2-AnyOrg/Controllers/TodoListController.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,31 @@
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Authorization;
66
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.AspNetCore.Mvc.Rendering;
78
using Microsoft.Identity.Web;
89
using WebApp_OpenIDConnect_DotNet.BLL;
910
using WebApp_OpenIDConnect_DotNet.Models;
11+
using WebApp_OpenIDConnect_DotNet.Utils;
1012

1113
namespace WebApp_OpenIDConnect_DotNet.Controllers
1214
{
1315
[Authorize]
1416
public class TodoListController : Controller
1517
{
1618
private readonly ITodoItemService _todoItemService;
17-
public TodoListController(ITodoItemService todoItemService)
19+
private readonly ITokenAcquisition _tokenAcquisition;
20+
private readonly IMSGraphService _msGraphService;
21+
22+
public TodoListController(ITodoItemService todoItemService, ITokenAcquisition tokenAcquisition, IMSGraphService msGraphService)
1823
{
1924
_todoItemService = todoItemService;
25+
_tokenAcquisition = tokenAcquisition;
26+
_msGraphService = msGraphService;
2027
}
2128

2229
public async Task<IActionResult> Index(bool showAllFilter)
2330
{
24-
@TempData["ShowAllFilter"] = showAllFilter;
31+
ViewData["ShowAllFilter"] = showAllFilter;
2532

2633
var items = await _todoItemService.List(showAllFilter, User);
2734
return View(items);
@@ -34,19 +41,60 @@ public IActionResult Create()
3441
{
3542
AssignedTo = User.GetObjectId(),
3643
TenantId = User.GetTenantId(),
37-
UserName = User.Identity.Name
44+
UserName = User.GetDisplayName()
3845
};
3946

4047
return View(model);
4148
}
4249

4350
[HttpPost]
51+
[ValidateAntiForgeryToken]
4452
public async Task<IActionResult> Create(TodoItem model)
4553
{
54+
if (!ModelState.IsValid)
55+
{
56+
return View(model);
57+
}
58+
4659
await _todoItemService.Create(model.Text, User);
4760
return RedirectToAction("Index");
4861
}
4962

63+
[HttpGet]
64+
[AuthorizeForScopes(Scopes = new string[] { GraphScope.DirectoryReadAll })]
65+
public async Task<IActionResult> Edit(int id)
66+
{
67+
TodoItem todoItem = await _todoItemService.Get(id, User);
68+
69+
if(todoItem == null)
70+
{
71+
TempData["ErrorMessage"] = "Item not found";
72+
return RedirectToAction("Error", "Home");
73+
}
74+
75+
var userTenant = User.GetTenantId();
76+
77+
// Acquiring token for graph using the user's tenantId, so it can return all the users from their tenant
78+
var graphAccessToken = await _tokenAcquisition.GetAccessTokenOnBehalfOfUserAsync(new string[] { GraphScope.DirectoryReadAll }, userTenant);
79+
80+
TempData["UsersDropDown"] = (await _msGraphService.GetUsersAsync(graphAccessToken))
81+
.Select(u => new SelectListItem
82+
{
83+
Text = u.UserPrincipalName,
84+
Value = u.Id
85+
}).ToList();
86+
87+
return View(todoItem);
88+
}
89+
90+
[HttpPost]
91+
[ValidateAntiForgeryToken]
92+
public async Task<IActionResult> Edit(TodoItem todoItem)
93+
{
94+
await _todoItemService.Edit(todoItem, User);
95+
return RedirectToAction("Index");
96+
}
97+
5098
[HttpGet]
5199
public async Task<IActionResult> Delete(int id)
52100
{

1-WebApp-OIDC/1-2-AnyOrg/Models/TodoItem.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ public class TodoItem
1212
[Key]
1313
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
1414
public int Id { get; set; }
15+
[Required]
1516
public string Text { get; set; }
17+
[Required]
1618
public string UserName { get; set; }
19+
[Required]
1720
public string AssignedTo { get; set; }
21+
[Required]
1822
public string TenantId { get; set; }
1923
}
2024
}

1-WebApp-OIDC/1-2-AnyOrg/Startup.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Extensions.DependencyInjection;
1212
using Microsoft.Identity.Web;
1313
using Microsoft.Identity.Web.Resource;
14+
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
1415
using Microsoft.IdentityModel.Logging;
1516
using Microsoft.IdentityModel.Tokens;
1617
using System;
@@ -48,9 +49,12 @@ public void ConfigureServices(IServiceCollection services)
4849
services.AddDbContext<SampleDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SampleDbConnStr")));
4950

5051
services.AddScoped<ITodoItemService, TodoItemService>();
52+
services.AddScoped<IMSGraphService, MSGraphService>();
5153

5254
// Sign-in users with the Microsoft identity platform
53-
services.AddMicrosoftIdentityPlatformAuthentication(Configuration);
55+
services.AddMicrosoftIdentityPlatformAuthentication(Configuration)
56+
.AddMsal(Configuration, new string[] { GraphScope.DirectoryReadAll })
57+
.AddInMemoryTokenCaches();
5458

5559
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
5660
{
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
6+
namespace WebApp_OpenIDConnect_DotNet.Utils
7+
{
8+
public static class GraphScope
9+
{
10+
public const string DirectoryReadAll = "Directory.Read.All";
11+
}
12+
}

0 commit comments

Comments
 (0)