Skip to content

Instantly share code, notes, and snippets.

@ahelland
Last active August 29, 2015 13:58
Show Gist options
  • Save ahelland/10165845 to your computer and use it in GitHub Desktop.
Save ahelland/10165845 to your computer and use it in GitHub Desktop.
Code for the blog post "Extending Your Azure Active Directory - Part 1" - on http://mobilitydojo.net/2014/04/08/extending-your-azure-active-directory-part-1/
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json;
using System;
using System.Configuration;
using System.Globalization;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using DirectoryExtensions.Models;
using System.Net;
namespace DirectoryExtensions.Controllers
{
[Authorize]
public class AzureADController : Controller
{
private const string TenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid";
private const string LoginUrl = "https://login.windows.net/{0}";
private const string ApiVersion = "1.21-preview";
private const string GraphUrl = "https://graph.windows.net";
private const string GraphUserUrl = "https://graph.windows.net/{0}/users/{1}?api-version=" + ApiVersion;
private const string GraphExtensionValueUrl = "https://graph.windows.net/{0}/users/{1}?api-version=" + ApiVersion;
private const string GraphApps = "https://graph.windows.net/{0}/applications?api-version=" + ApiVersion;
private const string GraphAppUrl = "https://graph.windows.net/{0}/applications/{1}/extensionProperties?api-version=" + ApiVersion;
private const string GraphExtensionUrl = "https://graph.windows.net/{0}/applications/{1}/extensionProperties?api-version=" + ApiVersion;
private static readonly string AppPrincipalId = ConfigurationManager.AppSettings["ida:ClientID"];
private static readonly string AppKey = ConfigurationManager.AppSettings["ida:Password"];
public async Task<ActionResult> Index()
{
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
// Get a token for calling the Windows Azure Active Directory Graph
AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId));
ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey);
AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential);
string authHeader = assertionCredential.CreateAuthorizationHeader();
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
GraphUserUrl,
HttpUtility.UrlEncode(tenantId),
HttpUtility.UrlEncode(User.Identity.Name));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
UserDetails user = JsonConvert.DeserializeObject<UserDetails>(responseString);
//Since the extension name isn't known in advance it's not included in the default serialization,
//so we extract it manually after looking up the name.
string appObjectId = await getAppObjectId(tenantId, authHeader);
string extensionName = string.Empty;
extensionName = await checkExtensionRegistered(tenantId, authHeader, appObjectId);
Newtonsoft.Json.Linq.JObject jUser = Newtonsoft.Json.Linq.JObject.Parse(responseString);
string YubiKeyValue = (string)jUser[extensionName];
user.YubiKeyId = YubiKeyValue;
return View(user);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Index(UserDetails user, string YubiKeyAction)
{
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
// Get a token for calling the Windows Azure Active Directory Graph
AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId));
ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey);
AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential);
string authHeader = assertionCredential.CreateAuthorizationHeader();
string appObjectId = await getAppObjectId(tenantId, authHeader);
string extensionName = string.Empty;
//Check if YubiKeyId extension is registered by trying to get the id
extensionName = await checkExtensionRegistered(tenantId, authHeader, appObjectId);
if (extensionName == "false")
extensionName = await registerExtension(tenantId,authHeader,appObjectId);
if (YubiKeyAction == "Register")
await setExtensionValue(tenantId, authHeader, user.userPrincipalName , extensionName, user.YubiKeyId);
if (YubiKeyAction == "Unregister")
{
bool unregOK = await setExtensionValue(tenantId, authHeader, user.userPrincipalName, extensionName, "");
if (unregOK)
user.YubiKeyId = string.Empty;
}
//We want to reload the page so we return RedirectToAction(...) instead of View(...)
return RedirectToAction("Index");
}
private async Task<bool> setExtensionValue(string tenantId, string authHeader, string upn, string extensionName, string extensionValue)
{
//Get the objectId for this particular app
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
GraphExtensionValueUrl,
HttpUtility.UrlEncode(tenantId),
HttpUtility.UrlEncode(upn));
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.ExpectContinue = false;
//PATCH isn't a default method
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUrl);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
string extensionProperty = "{\"" + extensionName + "\":\"" + extensionValue + "\"}";
request.Content = new StringContent(extensionProperty, System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.NoContent)
{
return true;
}
else
return false;
}
private async Task<string> getAppObjectId(string tenantId, string authHeader)
{
string appObjectId = string.Empty;
//Get the objectId for this particular app
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
GraphApps,
HttpUtility.UrlEncode(tenantId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
var apps = JsonConvert.DeserializeObject<AppContext>(responseString);
//Iterate through the list to find the correct application,
//and retrieve it's object id
for (int i = 0; i < apps.value.Count; i++)
{
if (apps.value[i].appId == AppPrincipalId)
{
appObjectId = apps.value[i].objectId;
}
}
return appObjectId;
}
private async Task<string> checkExtensionRegistered(string tenantId, string authHeader, string appObjectId)
{
//Get extensions for this app
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
GraphAppUrl,
HttpUtility.UrlEncode(tenantId),
HttpUtility.UrlEncode(appObjectId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
var extensionproperties = JsonConvert.DeserializeObject<ExtensionPropertiesContext>(responseString);
if (extensionproperties.value.Count == 0)
return "false";
else
{
//Hardcoded "YubiKeyId" as extension value
var extensions = extensionproperties.value;
for (int i=0; i<extensionproperties.value.Count;i++)
{
if (extensionproperties.value[i].name.Contains("YubiKeyId"))
return extensionproperties.value[i].name;
}
}
return "false";
}
private async Task<string> registerExtension(string tenantId, string authHeader, string appObjectId)
{
//Get the objectId for this particular app
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
GraphExtensionUrl,
HttpUtility.UrlEncode(tenantId),
HttpUtility.UrlEncode(appObjectId));
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.ExpectContinue = false;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
request.Content = new StringContent("{\"name\": \"YubiKeyId\",\"dataType\": \"String\",\"targetObjects\": [\"User\"]}", System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.Created)
{
var extension = JsonConvert.DeserializeObject<ExtensionProperty>(responseString);
return extension.name;
}
else
return string.Empty;
}
}
}
using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel;
namespace DirectoryExtensions.Models
{
public class UserContext
{
[JsonProperty("odata.metadata")]
public string userContext;
[JsonProperty("value")]
public List<UserDetails> value;
}
public class UserDetails
{
[JsonProperty("objectId")]
public string objectId { get; set; }
[DisplayName("Display Name")]
public string displayName { get; set; }
[DisplayName("Given Name")]
public string givenName { get; set; }
[DisplayName("Surname")]
public string surname { get; set; }
[DisplayName("Job Title")]
public string jobTitle { get; set; }
[DisplayName("Department")]
public string department { get; set; }
[DisplayName("Mobile")]
public string mobile { get; set; }
[DisplayName("City")]
public string city { get; set; }
[DisplayName("Street Address")]
public string streetAddress { get; set; }
[DisplayName("Country")]
public string country { get; set; }
[DisplayName("Postal Code")]
public string postalCode { get; set; }
[DisplayName("Phone Number")]
public string telephoneNumber { get; set; }
[DisplayName("Email Address")]
public string mail { get; set; }
[DisplayName("UPN")]
public string userPrincipalName { get; set; }
[DisplayName("Last DirSync")]
public string lastDirSyncTime { get; set; }
[DisplayName("YubiKey ID")]
public string YubiKeyId { get; set; }
}
public class AppContext
{
[JsonProperty("odata.metadata")]
public string metadata { get; set; }
public List<AppDetails> value { get; set; }
}
public class AppDetails
{
public string objectId { get; set; }
public string appId { get; set; }
}
public class ExtensionPropertiesContext
{
[JsonProperty("odata.metadata")]
public string metadata { get; set; }
public List<ExtensionProperty> value { get; set; }
}
public class ExtensionProperty
{
public string objectId { get; set; }
public string objectType { get; set; }
public string name { get; set; }
public string dataType { get; set; }
[JsonProperty("odata.metadata")]
public string odataMetadata { get; set; }
[JsonProperty("odata.type")]
public string odataType { get; set; }
public List<string> targetObjects { get; set; }
}
}
@model DirectoryExtensions.Models.UserDetails
@{
ViewBag.Title = "UserProfile - " + User.Identity.Name;
}
<h4>Current User: @User.Identity.Name</h4>
@{
using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
//We want all values except the YubiKey to be read-only,
//yet we want to POST them so we include them as hidden fields as well
@Html.HiddenFor(model => model.userPrincipalName)
@Html.HiddenFor(model => model.displayName)
@Html.HiddenFor(model => model.givenName)
@Html.HiddenFor(model => model.surname)
@Html.HiddenFor(model => model.jobTitle)
@Html.HiddenFor(model => model.department)
@Html.HiddenFor(model => model.mobile)
@Html.HiddenFor(model => model.city)
@Html.HiddenFor(model => model.streetAddress)
@Html.HiddenFor(model => model.country)
@Html.HiddenFor(model => model.postalCode)
@Html.HiddenFor(model => model.telephoneNumber)
@Html.HiddenFor(model => model.lastDirSyncTime)
<table class="table">
<tr>
<td>@Html.DisplayNameFor(model => model.displayName)</td>
<td>@Html.DisplayFor(model => model.displayName)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.givenName)</td>
<td>@Html.DisplayFor(model => model.givenName)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.surname)</td>
<td>@Html.DisplayFor(model => model.surname)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.jobTitle)</td>
<td>@Html.DisplayFor(model => model.jobTitle)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.department)</td>
<td>@Html.DisplayFor(model => model.department)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.mobile)</td>
<td>@Html.DisplayFor(model => model.mobile)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.city)</td>
<td>@Html.DisplayFor(model => model.city)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.streetAddress)</td>
<td>@Html.DisplayFor(model => model.streetAddress)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.country)</td>
<td>@Html.DisplayFor(model => model.country)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.postalCode)</td>
<td>@Html.DisplayFor(model => model.postalCode)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.telephoneNumber)</td>
<td>@Html.DisplayFor(model => model.telephoneNumber)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.lastDirSyncTime) </td>
<td>@Html.DisplayFor(model => model.lastDirSyncTime)</td>
</tr>
<tr>
<td>@Html.DisplayNameFor(model => model.YubiKeyId)</td>
<td>@Html.EditorFor(model => model.YubiKeyId)</td>
</tr>
</table>
if (Model.YubiKeyId.IsEmpty())
{
@Html.Hidden("YubiKeyAction","Register")
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Register YubiKey" class="btn btn-default" />
</div>
</div>
}
if (!Model.YubiKeyId.IsEmpty())
{
@Html.Hidden("YubiKeyAction","Unregister")
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Unregister YubiKey" class="btn btn-default" />
</div>
</div>
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment