Browse Source

Github Login

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
0c30b11eac
  1. 42
      src/Squidex/Config/Identity/GithubHandler.cs
  2. 39
      src/Squidex/Config/Identity/GithubIdentityUsage.cs
  3. 47
      src/Squidex/Config/Identity/GoogleHandler.cs
  4. 39
      src/Squidex/Config/Identity/GoogleIdentityUsage.cs
  5. 3
      src/Squidex/Config/Identity/IdentityServices.cs
  6. 121
      src/Squidex/Config/Identity/IdentityUsage.cs
  7. 25
      src/Squidex/Config/Identity/MyIdentityOptions.cs
  8. 35
      src/Squidex/Controllers/UI/Account/AccountController.cs
  9. 1
      src/Squidex/Squidex.csproj
  10. 1
      src/Squidex/Startup.cs
  11. 9
      src/Squidex/Views/Account/Login.cshtml
  12. 4
      src/Squidex/app/theme/_static.scss
  13. 4
      src/Squidex/appsettings.json

42
src/Squidex/Config/Identity/GithubHandler.cs

@ -0,0 +1,42 @@
// ==========================================================================
// GitHubHandler.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Squidex.Core.Identity;
namespace Squidex.Config.Identity
{
public sealed class GitHubHandler : OAuthEvents
{
public override Task CreatingTicket(OAuthCreatingTicketContext context)
{
var userLogin = context.User.Value<string>("login");
var userName = context.User.Value<string>("name");
if (!string.IsNullOrEmpty(userName))
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexDisplayName, userName));
}
else if (!string.IsNullOrWhiteSpace(userLogin))
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexDisplayName, userName));
}
var pictureUrl = context.User.Value<string>("avatar_url");
if (!string.IsNullOrEmpty(pictureUrl))
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl));
}
return base.CreatingTicket(context);
}
}
}

39
src/Squidex/Config/Identity/GithubIdentityUsage.cs

@ -0,0 +1,39 @@
// ==========================================================================
// GithubIdentityUsage.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using AspNet.Security.OAuth.GitHub;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
// ReSharper disable InvertIf
namespace Squidex.Config.Identity
{
public static class GitHubIdentityUsage
{
public static IApplicationBuilder UseMyGithubAuthentication(this IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value;
if (options.IsGithubAuthConfigured())
{
var githubOptions =
new GitHubAuthenticationOptions
{
ClientId = options.GithubClient,
ClientSecret = options.GithubSecret
};
app.UseGitHubAuthentication(githubOptions);
}
return app;
}
}
}

47
src/Squidex/Config/Identity/GoogleHandler.cs

@ -0,0 +1,47 @@
// ==========================================================================
// GoogleHandler.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Squidex.Core.Identity;
using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
namespace Squidex.Config.Identity
{
public sealed class GoogleHandler : OAuthEvents
{
public override Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context)
{
context.Response.Redirect(context.RedirectUri + "&prompt=select_account");
return TaskHelper.Done;
}
public override Task CreatingTicket(OAuthCreatingTicketContext context)
{
var displayNameClaim = context.Identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
if (displayNameClaim != null)
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexDisplayName, displayNameClaim.Value));
}
var pictureUrl = context.User?.Value<string>("picture");
if (!string.IsNullOrWhiteSpace(pictureUrl))
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl));
}
return base.CreatingTicket(context);
}
}
}

39
src/Squidex/Config/Identity/GoogleIdentityUsage.cs

@ -0,0 +1,39 @@
// ==========================================================================
// GoogleIdentityUsage.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
// ReSharper disable InvertIf
namespace Squidex.Config.Identity
{
public static class GoogleIdentityUsage
{
public static IApplicationBuilder UseMyGoogleAuthentication(this IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value;
if (options.IsGoogleAuthConfigured())
{
var googleOptions =
new GoogleOptions
{
ClientId = options.GoogleClient,
ClientSecret = options.GoogleSecret,
Events = new GoogleHandler()
};
app.UseGoogleAuthentication(googleOptions);
}
return app;
}
}
}

3
src/Squidex/Config/Identity/IdentityServices.cs

@ -110,8 +110,7 @@ namespace Squidex.Config.Identity
public static IServiceCollection AddMyIdentity(this IServiceCollection services)
{
services.AddIdentity<IdentityUser, IdentityRole>()
.AddDefaultTokenProviders();
services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders();
return services;
}

121
src/Squidex/Config/Identity/IdentityUsage.cs

@ -6,20 +6,14 @@
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.MongoDB;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using Squidex.Core.Identity;
using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
@ -41,56 +35,53 @@ namespace Squidex.Config.Identity
return app;
}
public static IApplicationBuilder UseMyDefaultUser(this IApplicationBuilder app)
public static IApplicationBuilder UseAdminRole(this IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value;
var username = options.DefaultUsername;
var userManager = app.ApplicationServices.GetService<UserManager<IdentityUser>>();
if (!string.IsNullOrWhiteSpace(options.DefaultUsername) &&
!string.IsNullOrWhiteSpace(options.DefaultPassword))
{
Task.Run(async () =>
{
if (userManager.SupportsQueryableUsers && !userManager.Users.Any())
{
var user = new IdentityUser { UserName = username, Email = username, EmailConfirmed = true };
var roleManager = app.ApplicationServices.GetRequiredService<RoleManager<IdentityRole>>();
await userManager.CreateAsync(user, options.DefaultPassword);
}
}).Wait();
}
roleManager.CreateAsync(new IdentityRole { Name = SquidexRoles.Administrator, NormalizedName = SquidexRoles.Administrator }).Wait();
return app;
}
public static IApplicationBuilder UseMyGoogleAuthentication(this IApplicationBuilder app)
public static IApplicationBuilder UseMyAdmin(this IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value;
if (!string.IsNullOrWhiteSpace(options.GoogleClient) &&
!string.IsNullOrWhiteSpace(options.GoogleSecret))
var userManager = app.ApplicationServices.GetService<UserManager<IdentityUser>>();
if (options.IsAdminConfigured())
{
var googleOptions =
new GoogleOptions
{
Events = new GoogleHandler(),
ClientId = options.GoogleClient,
ClientSecret = options.GoogleSecret
};
var adminEmail = options.AdminEmail;
var adminPass = options.AdminPassword;
app.UseGoogleAuthentication(googleOptions);
}
Task.Run(async () =>
{
var user = await userManager.FindByEmailAsync(adminPass);
return app;
}
async Task userInitAsync(IdentityUser theUser)
{
await userManager.RemovePasswordAsync(theUser);
await userManager.ChangePasswordAsync(theUser, null, adminEmail);
await userManager.AddToRoleAsync(theUser, SquidexRoles.Administrator);
}
public static IApplicationBuilder UseAdminRole(this IApplicationBuilder app)
{
var roleManager = app.ApplicationServices.GetRequiredService<RoleManager<IdentityRole>>();
if (user != null)
{
if (options.EnforceAdmin)
{
await userInitAsync(user);
}
}
else if ((userManager.SupportsQueryableUsers && !userManager.Users.Any()) || options.EnforceAdmin)
{
user = new IdentityUser { UserName = adminEmail, Email = adminEmail, EmailConfirmed = true };
roleManager.CreateAsync(new IdentityRole { Name = SquidexRoles.Administrator, NormalizedName = SquidexRoles.Administrator }).Wait();
await userManager.CreateAsync(user);
await userInitAsync(user);
}
}).Wait();
}
return app;
}
@ -118,51 +109,5 @@ namespace Squidex.Config.Identity
return app;
}
private class RetrieveClaimsHandler : OAuthEvents
{
public override Task CreatingTicket(OAuthCreatingTicketContext context)
{
var displayNameClaim = context.Identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
if (displayNameClaim != null)
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexDisplayName, displayNameClaim.Value));
}
return base.CreatingTicket(context);
}
}
private sealed class GoogleHandler : RetrieveClaimsHandler
{
private static readonly HttpClient HttpClient = new HttpClient();
public override Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context)
{
context.Response.Redirect(context.RedirectUri + "&prompt=select_account");
return TaskHelper.Done;
}
public override async Task CreatingTicket(OAuthCreatingTicketContext context)
{
if (!string.IsNullOrWhiteSpace(context.AccessToken))
{
var apiRequestUri = new Uri($"https://www.googleapis.com/oauth2/v2/userinfo?access_token={context.AccessToken}");
var jsonReponseString = await HttpClient.GetStringAsync(apiRequestUri);
var jsonResponse = JToken.Parse(jsonReponseString);
var pictureUrl = jsonResponse["picture"]?.Value<string>();
if (!string.IsNullOrWhiteSpace(pictureUrl))
{
context.Identity.AddClaim(new Claim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl));
}
}
await base.CreatingTicket(context);
}
}
}
}

25
src/Squidex/Config/Identity/MyIdentityOptions.cs

@ -10,16 +10,37 @@ namespace Squidex.Config.Identity
{
public sealed class MyIdentityOptions
{
public string DefaultUsername { get; set; }
public string AdminEmail { get; set; }
public string DefaultPassword { get; set; }
public string AdminPassword { get; set; }
public string GoogleClient { get; set; }
public string GoogleSecret { get; set; }
public string GithubClient { get; set; }
public string GithubSecret { get; set; }
public bool EnforceAdmin { get; set; }
public bool RequiresHttps { get; set; }
public bool LockAutomatically { get; set; }
public bool IsAdminConfigured()
{
return !string.IsNullOrWhiteSpace(AdminEmail) && !string.IsNullOrWhiteSpace(AdminPassword);
}
public bool IsGithubAuthConfigured()
{
return !string.IsNullOrWhiteSpace(GithubClient) && !string.IsNullOrWhiteSpace(GithubSecret);
}
public bool IsGoogleAuthConfigured()
{
return !string.IsNullOrWhiteSpace(GoogleClient) && !string.IsNullOrWhiteSpace(GoogleSecret);
}
}
}

35
src/Squidex/Controllers/UI/Account/AccountController.cs

@ -169,16 +169,29 @@ namespace Squidex.Controllers.UI.Account
if (!isLoggedIn)
{
var user = CreateUser(externalLogin);
var email = externalLogin.Principal.FindFirst(ClaimTypes.Email).Value;
var isFirst = userManager.Users.LongCount() == 0;
var user = await userManager.FindByEmailAsync(email);
isLoggedIn =
await AddUserAsync(user) &&
await AddLoginAsync(user, externalLogin) &&
await MakeAdminAsync(user, isFirst) &&
await LockAsync(user, isFirst) &&
await LoginAsync(externalLogin);
if (user != null)
{
isLoggedIn =
await AddLoginAsync(user, externalLogin) &&
await LoginAsync(externalLogin);
}
else
{
user = CreateUser(externalLogin, email);
var isFirst = userManager.Users.LongCount() == 0;
isLoggedIn =
await AddUserAsync(user) &&
await AddLoginAsync(user, externalLogin) &&
await MakeAdminAsync(user, isFirst) &&
await LockAsync(user, isFirst) &&
await LoginAsync(externalLogin);
}
}
if (!isLoggedIn)
@ -232,11 +245,9 @@ namespace Squidex.Controllers.UI.Account
return MakeIdentityOperation(() => userManager.AddToRoleAsync(user, SquidexRoles.Administrator));
}
private static IdentityUser CreateUser(ExternalLoginInfo externalLogin)
private static IdentityUser CreateUser(ExternalLoginInfo externalLogin, string email)
{
var mail = externalLogin.Principal.FindFirst(ClaimTypes.Email).Value;
var user = new IdentityUser { Email = mail, UserName = mail };
var user = new IdentityUser { Email = email, UserName = email };
foreach (var squidexClaim in externalLogin.Principal.Claims.Where(c => c.Type.StartsWith(SquidexClaimTypes.Prefix)))
{

1
src/Squidex/Squidex.csproj

@ -38,6 +38,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="1.0.0-rc1-final" />
<PackageReference Include="Autofac" Version="4.5.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="IdentityServer4" Version="1.5.0" />

1
src/Squidex/Startup.cs

@ -143,6 +143,7 @@ namespace Squidex
identityApp.UseAdminRole();
identityApp.UseMyApiProtection();
identityApp.UseMyGoogleAuthentication();
identityApp.UseMyGithubAuthentication();
identityApp.UseStaticFiles();
identityApp.MapWhen(x => IsIdentityRequest(x), mvcApp =>

9
src/Squidex/Views/Account/Login.cshtml

@ -12,12 +12,17 @@
<p>
@foreach (var provider in Model.ExternalProviders)
{
<button class="redirect-button" type="submit" name="provider" id="loginButton" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
<button class="redirect-button" type="submit" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
}
</p>
</div>
</form>
<script>
document.getElementById("loginButton").click();
var redirectButtons = document.getElementsByClassName("redirect-button");
if (redirectButtons.length === 1) {
debugger;
redirectButtons[0].click();
}
</script>

4
src/Squidex/app/theme/_static.scss

@ -27,8 +27,4 @@ noscript {
font-size: 30px;
font-weight: lighter;
margin-bottom: 20px;
}
.redirect-button {
display: none;
}

4
src/Squidex/appsettings.json

@ -48,6 +48,8 @@
"identity": {
"googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com",
"googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg",
"githubClient": "211ea00e726baf754c78",
"githubSecret": "d0a0d0fe2c26469ae20987ac265b3a339fd73132",
"lockAutomatically": true,
"keysStore": {
"type": "InMemory",
@ -57,6 +59,6 @@
"folder": {
"path": "keys"
}
}
}
}
}
Loading…
Cancel
Save