diff --git a/src/Services/auth/O2NextGen.Auth.Web/Extensions/IdentityExtensions.cs b/src/Services/auth/O2NextGen.Auth.Web/Extensions/IdentityExtensions.cs new file mode 100644 index 00000000..8f7cd08e --- /dev/null +++ b/src/Services/auth/O2NextGen.Auth.Web/Extensions/IdentityExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using O2NextGen.Auth.Web.Data; +using O2NextGen.Auth.Web.Helpers; +using O2NextGen.Auth.Web.Utilities; + +namespace O2NextGen.Auth.Web.Extensions +{ + public static class IdentityExtensions + { + public static IServiceCollection AddConfiguredIdentity(this IServiceCollection services, + IConfiguration configuration) + { + services.AddDbContext(options => + options.UseSqlServer(configuration["ConnectionString"])); + + services + .AddIdentity(options => + { + options.Password.RequireDigit = false; + //TODO: uncomment after some tests + //options.Password.RequiredLength = 12; + options.Password.RequireLowercase = false; + options.Password.RequireUppercase = false; + options.Password.RequireNonAlphanumeric = false; + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddSingleton(); + services.AddSingleton(); + + + return services; + } + } +} \ No newline at end of file diff --git a/src/Services/auth/O2NextGen.Auth.Web/Extensions/IdentityServerExtensions.cs b/src/Services/auth/O2NextGen.Auth.Web/Extensions/IdentityServerExtensions.cs new file mode 100644 index 00000000..35ad5ff3 --- /dev/null +++ b/src/Services/auth/O2NextGen.Auth.Web/Extensions/IdentityServerExtensions.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using IdentityServer4; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using O2NextGen.Auth.Web.Data; + +namespace O2NextGen.Auth.Web.Extensions +{ + public static class IdentityServerExtensions + { + public static IServiceCollection AddConfiguredIdentityServer(this IServiceCollection services, + IHostingEnvironment environment, IConfiguration configuration) + { + var builder = services.AddIdentityServer(options => + { + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; + }) + // using in memory, but we could also get it, for example, from the database + + // access to data regarding the user's identity + .AddInMemoryIdentityResources(GetIdentityResources()) + // APIs that may be accessed + .AddInMemoryApiResources(GetApis()) + // client applications that may access users data and APIs on the user's behalf + .AddInMemoryClients(GetClients()) + // configures IdentityServer integration with ASP.NET Core Identity + .AddAspNetIdentity() + + // to avoid bombarding the db with checks, make use of cache + .AddInMemoryCaching(); + // more about EF integration: + // - http://docs.identityserver.io/en/latest/quickstarts/7_entity_framework.html + // - http://docs.identityserver.io/en/latest/reference/ef.html?highlight=dbcontext + + return services; + } + + private static IEnumerable GetIdentityResources() + { + var profile = new IdentityResources.Profile(); + profile.Required = true; + return new IdentityResource[] + { + new IdentityResources.OpenId(), + profile + }; + } + + private static IEnumerable GetApis() + { + var apiResource = new ApiResource("GroupManagement", "Group Management"); + apiResource.Scopes.First().Required = true; + return new[] + { + apiResource + }; + } + + private static IEnumerable GetClients() + { + return new[] + { + new Client + { + ClientId = "WebFrontend", + AllowedGrantTypes = GrantTypes.Code, + ClientSecrets = {new Secret("secret".Sha256())}, + RedirectUris = new[] {"https://localhost:1001/signin-oidc"}, + RefreshTokenUsage = TokenUsage.OneTimeOnly, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + "GroupManagement", + IdentityServerConstants.StandardScopes.OfflineAccess + }, + AllowOfflineAccess = true, + AccessTokenLifetime = 60, + RefreshTokenExpiration = TokenExpiration.Sliding, + //RequireConsent = false + } + }; + } + } +} \ No newline at end of file diff --git a/src/Services/auth/O2NextGen.Auth.Web/O2NextGen.Auth.Web.csproj b/src/Services/auth/O2NextGen.Auth.Web/O2NextGen.Auth.Web.csproj index bf8be0b5..1caba525 100644 --- a/src/Services/auth/O2NextGen.Auth.Web/O2NextGen.Auth.Web.csproj +++ b/src/Services/auth/O2NextGen.Auth.Web/O2NextGen.Auth.Web.csproj @@ -10,6 +10,8 @@ + + @@ -23,6 +25,7 @@ + diff --git a/src/Services/auth/O2NextGen.Auth.Web/Program.cs b/src/Services/auth/O2NextGen.Auth.Web/Program.cs index 6b406198..f3ca085a 100644 --- a/src/Services/auth/O2NextGen.Auth.Web/Program.cs +++ b/src/Services/auth/O2NextGen.Auth.Web/Program.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using O2NextGen.Auth.Web.StartupHelpers; using Serilog; namespace O2NextGen.Auth.Web @@ -20,6 +21,7 @@ public static async Task Main(string[] args) var host = CreateWebHostBuilder(args).Build(); Log.Information($"############### {AppName} ##############"); Log.Information("################# Starting Application #################"); + await host.EnsureDbUpToDateAsync(); await host.RunAsync(); Log.Information($"============== {AppName} - state is started ====================="); return 0; diff --git a/src/Services/auth/O2NextGen.Auth.Web/Startup.cs b/src/Services/auth/O2NextGen.Auth.Web/Startup.cs index d37a7998..63216870 100644 --- a/src/Services/auth/O2NextGen.Auth.Web/Startup.cs +++ b/src/Services/auth/O2NextGen.Auth.Web/Startup.cs @@ -15,12 +15,13 @@ namespace O2NextGen.Auth.Web { public class Startup { + private readonly IConfiguration _configuration; + public Startup(IConfiguration configuration) { - Configuration = configuration; + _configuration = configuration; } - - public IConfiguration Configuration { get; } + public void ConfigureServices(IServiceCollection services) { services.AddMvc() @@ -30,30 +31,15 @@ public void ConfigureServices(IServiceCollection services) options.Conventions.AuthorizeFolder("/Account"); } ); - services.AddDbContext(options => - options.UseSqlServer(Configuration["ConnectionString"])); - - //Todo: will change vars to Auth-Config envs - services - .AddIdentity(options => - { - options.Password.RequireDigit = true; - options.Password.RequireLowercase = true; - options.Password.RequireNonAlphanumeric = false; - options.Password.RequireUppercase = true; - options.Password.RequiredLength = 6; - }) - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.AddApplicationServices(Configuration); + services.AddApplicationServices(_configuration); services.ConfigureApplicationCookie(options => { options.LoginPath = "/Login"; options.LogoutPath = "/Logout"; options.AccessDeniedPath = "/AccessDenied"; - }); + }) + .AddConfiguredIdentity( _configuration); services.AddConfiguredLocalization(); services.AddSingleton(); } @@ -64,13 +50,14 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseDeveloperExceptionPage(); } - + app.UseHsts(); + app.UseHttpsRedirection(); app.UseStaticFiles(); + //app.UseIdentityServer(); var v = app.ApplicationServices .GetRequiredService>().Value; app.UseRequestLocalization(v); app.UseCookiePolicy(); - app.UseAuthentication(); app.UseMvcWithDefaultRoute(); } diff --git a/src/Services/auth/O2NextGen.Auth.Web/StartupHelpers/DatabaseExtensions.cs b/src/Services/auth/O2NextGen.Auth.Web/StartupHelpers/DatabaseExtensions.cs new file mode 100644 index 00000000..e5f51590 --- /dev/null +++ b/src/Services/auth/O2NextGen.Auth.Web/StartupHelpers/DatabaseExtensions.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using O2NextGen.Auth.Web.Data; + +namespace O2NextGen.Auth.Web.StartupHelpers +{ + internal static class DatabaseExtensions + { + internal static async Task EnsureDbUpToDateAsync(this IWebHost host) + { + using (var scope = host.Services.CreateScope()) + { + var hostingEnvironment = scope.ServiceProvider.GetRequiredService(); + var authDbContext = scope.ServiceProvider.GetRequiredService(); + await authDbContext.Database.MigrateAsync(); + + // var grantDbContext = scope.ServiceProvider.GetRequiredService(); + // await grantDbContext.Database.MigrateAsync(); + } + } + } +} \ No newline at end of file diff --git a/src/Services/auth/O2NextGen.Auth.Web/Utilities/Base64QrCodeGenerator.cs b/src/Services/auth/O2NextGen.Auth.Web/Utilities/Base64QrCodeGenerator.cs new file mode 100644 index 00000000..3a09c481 --- /dev/null +++ b/src/Services/auth/O2NextGen.Auth.Web/Utilities/Base64QrCodeGenerator.cs @@ -0,0 +1,30 @@ +using System; +using SkiaSharp; +using SkiaSharp.QrCode; + +namespace O2NextGen.Auth.Web.Utilities +{ + public class Base64QrCodeGenerator : IBase64QrCodeGenerator + { + public string Generate(Uri target) + { + using (var generator = new QRCodeGenerator()) + { + var code = generator.CreateQrCode(target.OriginalString, ECCLevel.Q); + + var info = new SKImageInfo(256, 256); + using (var surface = SKSurface.Create(info)) + { + var canvas = surface.Canvas; + canvas.Render(code, info.Width, info.Height); + + using (var image = surface.Snapshot()) + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + { + return Convert.ToBase64String(data.ToArray()); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Services/auth/O2NextGen.Auth.Web/Utilities/IBase64QrCodeGenerator.cs b/src/Services/auth/O2NextGen.Auth.Web/Utilities/IBase64QrCodeGenerator.cs new file mode 100644 index 00000000..50db78c8 --- /dev/null +++ b/src/Services/auth/O2NextGen.Auth.Web/Utilities/IBase64QrCodeGenerator.cs @@ -0,0 +1,9 @@ +using System; + +namespace O2NextGen.Auth.Web.Utilities +{ + public interface IBase64QrCodeGenerator + { + string Generate(Uri target); + } +} \ No newline at end of file