This repository was archived by the owner on May 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathProfileController.cs
More file actions
170 lines (147 loc) · 6.54 KB
/
ProfileController.cs
File metadata and controls
170 lines (147 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using ProfileAPI.Models;
namespace ProfileAPI.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ProfileController : ControllerBase
{
private readonly ProfileContext _context;
private readonly ITokenAcquisition _tokenAcquisition;
private readonly GraphServiceClient _graphServiceClient;
private readonly IOptions<MicrosoftGraphOptions> _graphOptions;
public ProfileController(ProfileContext context, ITokenAcquisition tokenAcquisition, GraphServiceClient graphServiceClient, IOptions<MicrosoftGraphOptions> graphOptions)
{
_context = context;
_tokenAcquisition = tokenAcquisition;
_graphServiceClient = graphServiceClient;
_graphOptions = graphOptions;
}
// GET: api/ProfileItems/5
[HttpGet("{id}")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:RequiredScopes")]
public async Task<ActionResult<ProfileItem>> GetProfileItem(string id)
{
ProfileItem profileItem = await _context.ProfileItems.FindAsync(id);
if (profileItem == null)
{
return NotFound();
}
return profileItem;
}
// POST api/values
[HttpPost]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:RequiredScopes")]
public async Task<ActionResult<ProfileItem>> PostProfileItem(ProfileItem profileItem)
{
profileItem.FirstLogin = false;
try
{
User profile = await _graphServiceClient.Me.Request().GetAsync();
// populate with data from Graph
profileItem.Id = profile.Id;
profileItem.UserPrincipalName = profile.UserPrincipalName;
profileItem.GivenName = profile.GivenName;
profileItem.Surname = profile.Surname;
profileItem.JobTitle = profile.JobTitle;
profileItem.MobilePhone = profile.MobilePhone;
profileItem.PreferredLanguage = profile.PreferredLanguage;
}
catch (MsalException ex)
{
return BadRequest("An authentication error occurred while acquiring a token for downstream API\n" + ex.ErrorCode + "\n" + ex.Message);
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
// append the WWW-Authenticate header from the eSTS response to the response to the client app
// to learn more, visit: https://learn.microsoft.com/azure/active-directory/develop/v2-conditional-access-dev-guide
_tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeader(_graphOptions.Value.Scopes.Split(' '), ex.MsalUiRequiredException);
return Unauthorized(ex.MsalUiRequiredException.ResponseBody);
}
catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge"))
{
if (IsClientCapableofClaimsChallenge(HttpContext))
{
// append the WWW-Authenticate header from the Microsoft Graph response to the response to the client app
// to learn more, visit: https://learn.microsoft.com/azure/active-directory/develop/app-resilience-continuous-access-evaluation?tabs=dotnet
HttpContext.Response.Headers.Add("WWW-Authenticate", svcex.ResponseHeaders.WwwAuthenticate.ToString());
return Unauthorized(svcex.RawResponseBody);
}
else
{
return Unauthorized("Continuous access evaluation resulted in claims challenge but the client is not capable. Please enable client capabilities and try again.");
}
}
catch (Exception ex)
{
return BadRequest("An error occurred while calling the downstream API\n" + ex.Message);
}
_context.ProfileItems.Add(profileItem);
await _context.SaveChangesAsync();
return CreatedAtAction("GetProfileItem", new { id = profileItem.Id }, profileItem);
}
// PUT: api/ProfileItems/5
[HttpPut("{id}")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:RequiredScopes")]
public async Task<IActionResult> PutProfileItem(string id, ProfileItem profileItem)
{
if (id != profileItem.Id)
{
return BadRequest();
}
// NOTE: We only update the entry in the local Db, and not in MS graph ..
_context.Entry(profileItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProfileItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
private bool ProfileItemExists(string id)
{
return _context.ProfileItems.Any(e => e.Id == id);
}
/// <summary>
/// Evaluates for the presence of the client capabilities claim (xms_cc) and accordingly returns a response if present.
/// </summary>
private bool IsClientCapableofClaimsChallenge(HttpContext context)
{
string clientCapabilitiesClaim = "xms_cc";
if (context == null || context.User == null || context.User.Claims == null || !context.User.Claims.Any())
{
throw new ArgumentNullException(nameof(context), "No user context is available to pick claims from");
}
var ccClaim = context.User.FindAll(clientCapabilitiesClaim).FirstOrDefault(x => x.Type == "xms_cc");
if (ccClaim != null && (ccClaim.Value == "cp1" || ccClaim.Value == "CP1"))
{
return true;
}
return false;
}
}
}