Skip to content

Instantly share code, notes, and snippets.

@pksorensen
Last active August 29, 2015 13:56
Show Gist options
  • Save pksorensen/9145353 to your computer and use it in GitHub Desktop.
Save pksorensen/9145353 to your computer and use it in GitHub Desktop.
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.WsFederation;
using System.Linq;
using System.Globalization;
using System.Web;
using Microsoft.Owin.Security.Cookies;
using System.Collections.Concurrent;
using Microsoft.Owin.Logging;
using System.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
using System.Security.Claims;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Subscriptions;
using Microsoft.WindowsAzure;
using System.Threading;
[assembly: OwinStartup(typeof(WebApplication35.Startup))]
namespace WebApplication35
{
public class DatabaseIssuerNameRegistry
{
private static ConcurrentDictionary<string, DateTimeOffset> PerAppInMemoryLookup =
new ConcurrentDictionary<string, DateTimeOffset>();
private static ConcurrentBag<string> Tenants = new ConcurrentBag<string>();
internal static void CleanUpExpiredSignupTokens()
{
DateTimeOffset now = DateTimeOffset.UtcNow;
foreach (var key in PerAppInMemoryLookup.Where(token => token.Value <= now).Select(k => k.Key))
{
DateTimeOffset _;
PerAppInMemoryLookup.TryRemove(key, out _);
}
}
internal static void AddSignupToken(string signupToken, DateTimeOffset expirationTime)
{
PerAppInMemoryLookup.TryAdd(signupToken, expirationTime);
}
internal static bool ContainsTenant(string tenantId)
{
return Tenants.Any(t => t == tenantId);
}
internal static bool TryAddTenant(string tenantId, string signupToken)
{
if (!ContainsTenant(tenantId))
{
string existingToken = PerAppInMemoryLookup.Where(token => token.Key == signupToken)
.Select(token => token.Key).FirstOrDefault();
if (existingToken != null)
{
DateTimeOffset _;
PerAppInMemoryLookup.TryRemove(existingToken, out _);
Tenants.Add(tenantId);
return true;
}
}
return false;
}
}
public class Startup
{
private const string TenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid";
private const string LoginUrl = "https://login.windows.net/{0}";
private const string GraphUrl = "https://graph.windows.net";
private const string GraphUserUrl = "https://graph.windows.net/{0}/users/{1}?api-version=2013-04-05";
private const string ConsentUrlFormat = "https://account.activedirectory.windowsazure.com/Consent.aspx?ClientId={0}";
private static readonly string AppPrincipalId = ConfigurationManager.AppSettings["ida:ClientID"];
private static readonly string AppKey = ConfigurationManager.AppSettings["ida:Password"];
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
//UPDATE Bug has been confirmed that this is not working as intented.
// I will update this demo as more information is avalibe.
//ValidateIssuer = false -> this causes IssuereValidator to not be run.
IssuerValidator = (issuer, token) =>
{
return false; // it still sign in the user even though its false.
return DatabaseIssuerNameRegistry.ContainsTenant(issuer);
}
},
Wtrealm = "https://owindemo.s-innovations.net",
MetadataAddress = "https://login.windows.net/802626c6-0f5c-4293-a8f5-198ecd481fe3/FederationMetadata/2007-06/FederationMetadata.xml"
});
app.Map("/login", map =>
{
map.Run(async ctx =>
{
if (ctx.Authentication.User == null ||
!ctx.Authentication.User.Identity.IsAuthenticated)
{
ctx.Response.StatusCode = 401;
}
else
{
ctx.Response.Redirect("/");
}
});
});
app.Map("/logout", map =>
{
map.Run(async ctx =>
{
ctx.Authentication.SignOut();
ctx.Response.Redirect("/");
});
});
app.Map("/signup", map =>
{
map.Run(async ctx =>
{
string signupToken = Guid.NewGuid().ToString();
UriBuilder replyUrl = new UriBuilder(ctx.Request.Uri);
replyUrl.Path = "/signup-callback";
replyUrl.Query = "signupToken=" + signupToken;
DatabaseIssuerNameRegistry.CleanUpExpiredSignupTokens();
DatabaseIssuerNameRegistry.AddSignupToken(signupToken: signupToken, expirationTime: DateTimeOffset.UtcNow.AddMinutes(5));
ctx.Response.Redirect(CreateConsentUrl(
clientId: AppPrincipalId,
requestedPermissions: "DirectoryReaders",//, CompanyAdministrator",
consentReturnUrl: replyUrl.Uri.AbsoluteUri));
});
});
app.Map("/signup-callback", map =>
{
map.Run(async ctx =>
{
string tenantId = ctx.Request.Query["TenantId"];
string signupToken = ctx.Request.Query["signupToken"];
if (DatabaseIssuerNameRegistry.ContainsTenant(tenantId))
{
ctx.Response.Redirect("/login");
}
string consent = ctx.Request.Query["Consent"];
if (!String.IsNullOrEmpty(tenantId) &&
String.Equals(consent, "Granted", StringComparison.OrdinalIgnoreCase))
{
if (DatabaseIssuerNameRegistry.TryAddTenant(tenantId, signupToken))
{
ctx.Response.Redirect("/login");
}
}
await ctx.Response.WriteAsync(@"Sorry, you have to press accept :)");
});
});
app.Map("/azure-management-token", map =>
{
map.Run(async ctx =>
{
if (!ctx.Authentication.User.Identity.IsAuthenticated)
{
ctx.Response.Redirect("/login");
return;
}
UriBuilder replyUrl = new UriBuilder(ctx.Request.Uri);
replyUrl.Path = "/show-azure-powers";
string authorizationUrl = string.Format(
"https://login.windows.net/{0}/oauth2/authorize?api-version=1.0&response_type=code&client_id={1}&resource={2}&redirect_uri={3}",
ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value,
AppPrincipalId,
"https://management.core.windows.net/",
replyUrl.Uri.AbsoluteUri);
ctx.Response.Redirect(authorizationUrl);
});
});
app.Map("/show-azure-powers", map => {
map.Run(async ctx =>
{
if(!ctx.Authentication.User.Identity.IsAuthenticated)
{
ctx.Response.Redirect("/login");
return;
}
string code = ctx.Request.Query["code"];
if (code == null)
{
ctx.Response.Redirect("/azure-management-token");
return;
}
AuthenticationContext ac =
new AuthenticationContext(string.Format("https://login.windows.net/{0}",
ctx.Authentication.User.FindFirst(TenantIdClaimType).Value));
UriBuilder replyUrl = new UriBuilder(ctx.Request.Uri);
replyUrl.Path = "/show-azure-powers";
replyUrl.Query = "";
ClientCredential clcred =
new ClientCredential(AppPrincipalId, AppKey);
AuthenticationResult ar = null;
try
{
ar = ac.AcquireTokenByAuthorizationCode(code,
replyUrl.Uri, clcred);
}catch(Exception)
{
ctx.Response.Redirect("/login");
return;
}
ctx.Response.ContentType = "text/html";
using(var azure = new SubscriptionClient(new TokenCloudCredentials(ar.AccessToken)))
{
var subscriptions = await azure.Subscriptions.ListAsync();
await ctx.Response.WriteAsync(string.Join("<br/>", subscriptions.Select(s => s.SubscriptionId)));
foreach(var cred in subscriptions.Select(id => new TokenCloudCredentials(id.SubscriptionId,ar.AccessToken)))
{
await ctx.Response.WriteAsync("<b>Webspaces</b><br/>");
using(var management = CloudContext.Clients.CreateWebSiteManagementClient(cred))
{
var websites = await management.WebSpaces.ListAsync(new System.Threading.CancellationToken());
await ctx.Response.WriteAsync("<br/>" + string.Join("<br/>", websites.Select(w =>
string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}", w.Name, w.Plan, w.Status, w.WorkerSize, w.GeoRegion, w.GeoLocation))));
}
await ctx.Response.WriteAsync("<br/><b>Storage Accounts</b><br/>");
using(var storage = CloudContext.Clients.CreateStorageManagementClient(cred))
{
var storages = await storage.StorageAccounts.ListAsync(new CancellationToken());
await ctx.Response.WriteAsync(string.Join("<br/>", storages.Select(s => string.Format("{0}",s.ServiceName)))+"<br/>");
}
}
}
await ctx.Response.WriteAsync(
@"
<div>
<a href=""/home"">home></a>
<a href=""/signup"">signup</a>
<a href=""/login"">login</a>
<a href=""/show-azure-powers"">show azure powers</a>
</div>");
});
});
app.Run(async ctx =>
{
var user = ctx.Authentication.User;
var response = ctx.Response;
response.ContentType = "text/html";
if (user.Identity.IsAuthenticated)
{
var userProfile = GetUserProfile(user);
await response.WriteAsync(string.Format("<h2>{0}</h2>",
user.Claims.First().Issuer));
await response.WriteAsync("<dl>");
foreach (var claim in user.Claims)
{
await response.WriteAsync(string.Format(
"<dt>{0}</dt> <dd>{1}</dd>",
claim.Type,
claim.Value));
}
await response.WriteAsync("</dl>");
await response.WriteAsync(JsonConvert.SerializeObject(await userProfile));
await response.WriteAsync(@"<br/><a href=""/logout"" > logout</a>");
}
else
{
await ctx.Response.WriteAsync("<h2>anonymous</h2>");
}
await ctx.Response.WriteAsync(
@"
<div>
<a href=""/home"">home></a>
<a href=""/signup"">signup</a>
<a href=""/login"">login</a>
<a href=""/show-azure-powers"">show azure powers</a>
</div>");
});
}
private async Task<UserProfile> GetUserProfile(ClaimsPrincipal user)
{
string tenantId = user.FindFirst(TenantIdClaimType).Value;
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.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();
return JsonConvert.DeserializeObject<UserProfile>(responseString);
}
private string CreateConsentUrl(string clientId, string requestedPermissions,
string consentReturnUrl)
{
string consentUrl = String.Format(
CultureInfo.InvariantCulture,
ConsentUrlFormat,
HttpUtility.UrlEncode(clientId));
if (!String.IsNullOrEmpty(requestedPermissions))
{
consentUrl += "&RequestedPermissions=" + HttpUtility.UrlEncode(requestedPermissions);
}
if (!String.IsNullOrEmpty(consentReturnUrl))
{
consentUrl += "&ConsentReturnURL=" + HttpUtility.UrlEncode(consentReturnUrl);
}
return consentUrl;
}
}
public class UserProfile
{
public string DisplayName { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
public string[] OtherMails { get; set; }
public string[] ProxyAddresses { get; set; }
public string MailNickname { get; set; }
public bool? AccountEnabled { get; set; }
public bool? DirSyncEnabled { get; set; }
public string JobTitle { get; set; }
public DateTime? LastDirSyncTime { get; set; }
public string Mobile { get; set; }
public string UsageLocation { get; set; }
public string TelephoneNumber { get; set; }
public string StreetAddress { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment