From a07646e608819fad4f4ce405a868f2e6ee7d56bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Sad=C4=B1k=20=C3=96zbek?= Date: Thu, 2 May 2019 00:41:28 +0300 Subject: [PATCH] Initial code commit --- .dockerignore | 9 +++ Dockerfile | 18 ++++++ SteamOpenIdConnectProxy.sln | 25 +++++++++ src/AppInMemoryDbContext.cs | 15 +++++ src/ExternalLoginController.cs | 89 ++++++++++++++++++++++++++++++ src/IdentityServerConfig.cs | 45 +++++++++++++++ src/Program.cs | 32 +++++++++++ src/Startup.cs | 61 ++++++++++++++++++++ src/SteamOpenIdConnectProxy.csproj | 22 ++++++++ src/appsettings.Development.json | 9 +++ src/appsettings.json | 14 +++++ 11 files changed, 339 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 SteamOpenIdConnectProxy.sln create mode 100644 src/AppInMemoryDbContext.cs create mode 100644 src/ExternalLoginController.cs create mode 100644 src/IdentityServerConfig.cs create mode 100644 src/Program.cs create mode 100644 src/Startup.cs create mode 100644 src/SteamOpenIdConnectProxy.csproj create mode 100644 src/appsettings.Development.json create mode 100644 src/appsettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..df2e0fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +.env +.git +.gitignore +.vs +.vscode +*/bin +*/obj +**/.toolstarget \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6b083f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build +WORKDIR /src +COPY ["src/SteamOpenIdConnectProxy/SteamOpenIdConnectProxy.csproj", "SteamOpenIdConnectProxy/"] +RUN dotnet restore "SteamOpenIdConnectProxy/SteamOpenIdConnectProxy.csproj" +COPY src/ . +WORKDIR "/src/SteamOpenIdConnectProxy" +RUN dotnet build "SteamOpenIdConnectProxy.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "SteamOpenIdConnectProxy.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "SteamOpenIdConnectProxy.dll"] \ No newline at end of file diff --git a/SteamOpenIdConnectProxy.sln b/SteamOpenIdConnectProxy.sln new file mode 100644 index 0000000..3fcc71e --- /dev/null +++ b/SteamOpenIdConnectProxy.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.202 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamOpenIdConnectProxy", "src\SteamOpenIdConnectProxy.csproj", "{39261E1A-B9AB-45C1-991E-B88ED3E076A2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {39261E1A-B9AB-45C1-991E-B88ED3E076A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39261E1A-B9AB-45C1-991E-B88ED3E076A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39261E1A-B9AB-45C1-991E-B88ED3E076A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39261E1A-B9AB-45C1-991E-B88ED3E076A2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9BCD5B86-0661-4AF9-A17E-7710A46BF914} + EndGlobalSection +EndGlobal diff --git a/src/AppInMemoryDbContext.cs b/src/AppInMemoryDbContext.cs new file mode 100644 index 0000000..44cb50b --- /dev/null +++ b/src/AppInMemoryDbContext.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace SteamOpenIdConnectProxy +{ + // This is completely in-memory, we do not need a persistent store. + public class AppInMemoryDbContext : IdentityDbContext + { + public AppInMemoryDbContext(DbContextOptions options) + : base(options) + { + } + } +} diff --git a/src/ExternalLoginController.cs b/src/ExternalLoginController.cs new file mode 100644 index 0000000..4b13fe5 --- /dev/null +++ b/src/ExternalLoginController.cs @@ -0,0 +1,89 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace SteamOpenIdConnectProxy +{ + [AllowAnonymous] + public class ExternalLoginController : Controller + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public ExternalLoginController( + SignInManager signInManager, + UserManager userManager, + ILogger logger) + { + _signInManager = signInManager; + _userManager = userManager; + _logger = logger; + } + + + [HttpGet] + [Route("/ExternalLogin")] + public async Task Login(string returnUrl = null) + { + string provider = "Steam"; + + // Request a redirect to the external login provider. + var redirectUrl = "/ExternalLoginCallback?returnUrl=" + Uri.EscapeUriString(returnUrl); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return new ChallengeResult(provider, properties); + } + + [HttpGet] + [Route("/ExternalLoginCallback")] + public async Task Callback(string returnUrl = null, string remoteError = null) + { + returnUrl = returnUrl ?? Url.Content("~/"); + if (remoteError != null) + { + return Content($"Error from external provider: {remoteError}"); + } + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + return Content($"Error loading external login information."); + } + + // Sign in the user with this external login provider if the user already has a login. + var externalLoginResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); + if (externalLoginResult.Succeeded) + { + _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); + return LocalRedirect(returnUrl); + } + + + var userName = info.Principal.FindFirstValue(ClaimTypes.Name); + var userId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier); + + var user = new IdentityUser { UserName = userName, Id = userId }; + var result = await _userManager.CreateAsync(user); + if (result.Succeeded) + { + result = await _userManager.AddLoginAsync(user, info); + if (result.Succeeded) + { + await _signInManager.SignInAsync(user, isPersistent: false); + _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); + return LocalRedirect(returnUrl); + } + } + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + + return BadRequest(); + } + } +} diff --git a/src/IdentityServerConfig.cs b/src/IdentityServerConfig.cs new file mode 100644 index 0000000..0897331 --- /dev/null +++ b/src/IdentityServerConfig.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using IdentityServer4; +using IdentityServer4.Models; + +namespace SteamOpenIdConnectProxy +{ + public class IdentityServerConfig + { + public static IEnumerable GetClients(string clientId, string secret, string redirectUri, string logoutRedirectUri) + { + yield return new Client + { + ClientId = clientId, + ClientName = "Proxy Client", + AllowedGrantTypes = GrantTypes.Code, + + ClientSecrets = + { + new Secret(secret.Sha256()) + }, + + // where to redirect to after login + RedirectUris = { redirectUri }, + + // where to redirect to after logout + PostLogoutRedirectUris = { logoutRedirectUri }, + + AllowedScopes = new List + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + } + }; + } + + public static IEnumerable GetIdentityResources() + { + return new List + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + }; + } + } +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..b19d91e --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Serilog; +using Serilog.Events; +using Serilog.Sinks.SystemConsole.Themes; + +namespace SteamOpenIdConnectProxy +{ + public class Program + { + public static void Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate) + .CreateLogger(); + + + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel() + .UseSerilog() + .UseStartup(); + } +} diff --git a/src/Startup.cs b/src/Startup.cs new file mode 100644 index 0000000..c3b6b2c --- /dev/null +++ b/src/Startup.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace SteamOpenIdConnectProxy +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + + services.AddDbContext(options => + options.UseInMemoryDatabase("default") + ); + + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddIdentityServer(options => + { + options.UserInteraction.LoginUrl = "/ExternalLogin"; + }) + .AddAspNetIdentity() + .AddInMemoryClients(IdentityServerConfig.GetClients(Configuration["OpenID:ClientID"], Configuration["OpenID:ClientSecret"], Configuration["OpenID:RedirectUri"], Configuration["OpenID:PostLogoutRedirectUri"])) + .AddInMemoryPersistedGrants() + .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()); + + services.AddAuthentication() + .AddSteam(options => + { + options.ApplicationKey = Configuration["Authentication:Steam:ApplicationKey"]; + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseIdentityServer(); + app.UseMvc(); + } + } +} diff --git a/src/SteamOpenIdConnectProxy.csproj b/src/SteamOpenIdConnectProxy.csproj new file mode 100644 index 0000000..89e73ff --- /dev/null +++ b/src/SteamOpenIdConnectProxy.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.2 + InProcess + 9f8cb9ce-f696-422f-901a-563af9413684 + + + + + + + + + + + + + + + + diff --git a/src/appsettings.Development.json b/src/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/src/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/appsettings.json b/src/appsettings.json new file mode 100644 index 0000000..5ea4b94 --- /dev/null +++ b/src/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "OpenID": { + "ClientID": "proxy", + "ClientSecret": "secret", + "RedirectUri": "http://localhost:8080/auth/realms/master/broker/steam/endpoint", + "PostLogoutRedirectUri": "" + }, + "AllowedHosts": "*" +} \ No newline at end of file