1
0
mirror of https://git.teknik.io/Teknikode/Teknik.git synced 2023-08-02 14:16:22 +02:00

Added Identity Server for authentication

This commit is contained in:
Uncled1023 2018-10-25 22:22:53 -07:00
parent 7ec30d1e39
commit 62e4476897
233 changed files with 15249 additions and 3553 deletions

5
.gitignore vendored
View File

@ -262,6 +262,7 @@ __pycache__/
/Teknik/App_Data/MachineKey.config /Teknik/App_Data/MachineKey.config
/Teknik/App_Data/ConnectionStrings.config /Teknik/App_Data/ConnectionStrings.config
/Teknik/App_Data/Config.json /Teknik/App_Data/Config.json
/Teknik/appsettings.Production.json
/Teknik/appsettings.Development.json
/Teknik/App_Data/version.json /Teknik/App_Data/version.json
**/appsettings.*.json
**/tempkey.rsa

View File

@ -19,38 +19,38 @@ namespace Teknik.Configuration
private ReaderWriterLockSlim _ConfigFileRWLock; private ReaderWriterLockSlim _ConfigFileRWLock;
private JsonSerializerSettings _JsonSettings; private JsonSerializerSettings _JsonSettings;
private bool _DevEnvironment; private bool _DevEnvironment;
private bool _Migrate; private bool _Migrate;
private bool _UseCdn; private bool _UseCdn;
private string _Title; private string _Title;
private string _Description; private string _Description;
private string _Author; private string _Author;
private string _Host; private string _Host;
private string _SupportEmail; private string _SupportEmail;
private string _NoReplyEmail; private string _NoReplyEmail;
private string _BitcoinAddress; private string _BitcoinAddress;
private string _Salt1; private string _Salt1;
private string _Salt2; private string _Salt2;
private string _CdnHost; private string _CdnHost;
private string _IPBlacklistFile; private string _IPBlacklistFile;
private string _ReferrerBlacklistFile; private string _ReferrerBlacklistFile;
private List<string> _PublicKeys; private List<string> _PublicKeys;
private UserConfig _UserConfig; private UserConfig _UserConfig;
private ContactConfig _ContactConfig; private ContactConfig _ContactConfig;
private EmailConfig _EmailConfig; private EmailConfig _EmailConfig;
private GitConfig _GitConfig; private GitConfig _GitConfig;
private UploadConfig _UploadConfig; private UploadConfig _UploadConfig;
private PasteConfig _PasteConfig; private PasteConfig _PasteConfig;
private BlogConfig _BlogConfig; private BlogConfig _BlogConfig;
private ApiConfig _ApiConfig; private ApiConfig _ApiConfig;
private PodcastConfig _PodcastConfig; private PodcastConfig _PodcastConfig;
private StreamConfig _StreamConfig; private StreamConfig _StreamConfig;
private ShortenerConfig _ShortenerConfig; private ShortenerConfig _ShortenerConfig;
private VaultConfig _VaultConfig; private VaultConfig _VaultConfig;
private StatsConfig _StatsConfig; private StatsConfig _StatsConfig;
private LoggingConfig _LoggingConfig; private LoggingConfig _LoggingConfig;
private PiwikConfig _PiwikConfig; private PiwikConfig _PiwikConfig;
private IRCConfig _IRCConfig; private IRCConfig _IRCConfig;
public bool DevEnvironment { get { return _DevEnvironment; } set { _DevEnvironment = value; } } public bool DevEnvironment { get { return _DevEnvironment; } set { _DevEnvironment = value; } }
public bool Migrate { get { return _Migrate; } set { _Migrate = value; } } public bool Migrate { get { return _Migrate; } set { _Migrate = value; } }
@ -73,52 +73,52 @@ namespace Teknik.Configuration
public List<string> PublicKeys { get { return _PublicKeys; } set { _PublicKeys = value; } } public List<string> PublicKeys { get { return _PublicKeys; } set { _PublicKeys = value; } }
// User Configuration // User Configuration
public UserConfig UserConfig { get { return _UserConfig; } set { _UserConfig = value; } } public UserConfig UserConfig { get { return _UserConfig; } set { _UserConfig = value; } }
// Contact Configuration // Contact Configuration
public ContactConfig ContactConfig { get { return _ContactConfig; } set { _ContactConfig = value; } } public ContactConfig ContactConfig { get { return _ContactConfig; } set { _ContactConfig = value; } }
// Mail Server Configuration // Mail Server Configuration
public EmailConfig EmailConfig { get { return _EmailConfig; } set { _EmailConfig = value; } } public EmailConfig EmailConfig { get { return _EmailConfig; } set { _EmailConfig = value; } }
// Git Service Configuration // Git Service Configuration
public GitConfig GitConfig { get { return _GitConfig; } set { _GitConfig = value; } } public GitConfig GitConfig { get { return _GitConfig; } set { _GitConfig = value; } }
// Blog Configuration // Blog Configuration
public BlogConfig BlogConfig { get { return _BlogConfig; } set { _BlogConfig = value; } } public BlogConfig BlogConfig { get { return _BlogConfig; } set { _BlogConfig = value; } }
// Upload Configuration // Upload Configuration
public UploadConfig UploadConfig { get { return _UploadConfig; } set { _UploadConfig = value; } } public UploadConfig UploadConfig { get { return _UploadConfig; } set { _UploadConfig = value; } }
// Paste Configuration // Paste Configuration
public PasteConfig PasteConfig { get { return _PasteConfig; } set { _PasteConfig = value; } } public PasteConfig PasteConfig { get { return _PasteConfig; } set { _PasteConfig = value; } }
// API Configuration // API Configuration
public ApiConfig ApiConfig { get { return _ApiConfig; } set { _ApiConfig = value; } } public ApiConfig ApiConfig { get { return _ApiConfig; } set { _ApiConfig = value; } }
// Podcast Configuration // Podcast Configuration
public PodcastConfig PodcastConfig { get { return _PodcastConfig; } set { _PodcastConfig = value; } } public PodcastConfig PodcastConfig { get { return _PodcastConfig; } set { _PodcastConfig = value; } }
// Stream Configuration // Stream Configuration
public StreamConfig StreamConfig { get { return _StreamConfig; } set { _StreamConfig = value; } } public StreamConfig StreamConfig { get { return _StreamConfig; } set { _StreamConfig = value; } }
// Shortener Configuration // Shortener Configuration
public ShortenerConfig ShortenerConfig { get { return _ShortenerConfig; } set { _ShortenerConfig = value; } } public ShortenerConfig ShortenerConfig { get { return _ShortenerConfig; } set { _ShortenerConfig = value; } }
// Vault Configuration // Vault Configuration
public VaultConfig VaultConfig { get { return _VaultConfig; } set { _VaultConfig = value; } } public VaultConfig VaultConfig { get { return _VaultConfig; } set { _VaultConfig = value; } }
// Status Configuration // Status Configuration
public StatsConfig StatsConfig { get { return _StatsConfig; } set { _StatsConfig = value; } } public StatsConfig StatsConfig { get { return _StatsConfig; } set { _StatsConfig = value; } }
// Logging Configuration // Logging Configuration
public LoggingConfig LoggingConfig { get { return _LoggingConfig; } set { _LoggingConfig = value; } } public LoggingConfig LoggingConfig { get { return _LoggingConfig; } set { _LoggingConfig = value; } }
// Piwik Configuration // Piwik Configuration
public PiwikConfig PiwikConfig { get { return _PiwikConfig; } set { _PiwikConfig = value; } } public PiwikConfig PiwikConfig { get { return _PiwikConfig; } set { _PiwikConfig = value; } }
// Piwik Configuration // Piwik Configuration
public IRCConfig IRCConfig { get { return _IRCConfig; } set { _IRCConfig = value; } } public IRCConfig IRCConfig { get { return _IRCConfig; } set { _IRCConfig = value; } }
public Config() public Config()
{ {

View File

@ -5,6 +5,7 @@
<RootNamespace>Teknik.Configuration</RootNamespace> <RootNamespace>Teknik.Configuration</RootNamespace>
<AssemblyName>Teknik.Configuration</AssemblyName> <AssemblyName>Teknik.Configuration</AssemblyName>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers>
<Configurations>Debug;Release;Test</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Teknik.Configuration
{
public class IdentityServerConfig
{
public string Authority { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public List<string> RedirectUris { get; set; }
public List<string> PostLogoutRedirectUris { get; set; }
public string APIName { get; set; }
public string APISecret { get; set; }
public IdentityServerConfig()
{
Authority = "https://localhost:5002";
ClientId = "mvc.client";
ClientSecret = "mysecret";
RedirectUris = new List<string>();
PostLogoutRedirectUris = new List<string>();
APIName = "api";
APISecret = "secret";
}
}
}

View File

@ -13,6 +13,7 @@ namespace Teknik.Configuration
public decimal PremiumAccountPrice { get; set; } public decimal PremiumAccountPrice { get; set; }
public string PaymentType { get; set; } public string PaymentType { get; set; }
public bool InviteCodeRequired { get; set; } public bool InviteCodeRequired { get; set; }
public IdentityServerConfig IdentityServerConfig { get; set; }
public UserConfig() public UserConfig()
{ {
@ -27,6 +28,7 @@ namespace Teknik.Configuration
PremiumAccountPrice = 0; PremiumAccountPrice = 0;
PaymentType = "Donation"; PaymentType = "Donation";
InviteCodeRequired = false; InviteCodeRequired = false;
IdentityServerConfig = new IdentityServerConfig();
} }
} }
} }

View File

@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MySql.Data" Version="8.0.11" /> <PackageReference Include="MySql.Data" Version="8.0.12" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Teknik.IdentityServer.Models;
namespace Teknik.IdentityServer
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
}
}

View File

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using Teknik.Configuration;
namespace Teknik.IdentityServer.Configuration
{
internal class Clients
{
public static IEnumerable<Client> Get(Config config)
{
return new List<Client> {
new Client
{
ClientId = config.UserConfig.IdentityServerConfig.ClientId,
ClientName = "Teknik Web Services",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret(config.UserConfig.IdentityServerConfig.ClientSecret.Sha256())
},
RequireConsent = false,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"role",
"account-info",
"security-info",
"teknik-api.read",
"teknik-api.write",
"auth-api"
},
AllowOfflineAccess = true
}
};
}
}
internal class Resources
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource> {
new IdentityResources.OpenId(),
new IdentityResource
{
Name = "account-info",
DisplayName = "Account Info",
UserClaims = new List<string>
{
"username",
"email",
"creation-date",
"last-seen",
"account-type",
"account-status"
}
},
new IdentityResource
{
Name = "security-info",
DisplayName = "Security Info",
UserClaims = new List<string>
{
"recovery-email",
"recovery-verified",
"pgp-public-key"
}
},
new IdentityResource {
Name = "role",
DisplayName = "Role",
UserClaims = new List<string> {"role"}
}
};
}
public static IEnumerable<ApiResource> GetApiResources(Config config)
{
return new List<ApiResource> {
new ApiResource {
Name = config.UserConfig.IdentityServerConfig.APIName,
DisplayName = "Teknik API",
Description = "Teknik API Access for end users",
UserClaims = new List<string> {"role"},
ApiSecrets = new List<Secret> {new Secret(config.UserConfig.IdentityServerConfig.APISecret.Sha256()) },
Scopes = new List<Scope> {
new Scope("teknik-api.read", "Teknik API Read Access"),
new Scope("teknik-api.write", "Teknik API Write Access")
}
},
new ApiResource {
Name = "auth-api",
DisplayName = "Auth Server API",
Description = "Auth Server API Access for managing the Auth Server",
Scopes = new List<Scope> {
new Scope()
{
Name = "auth-api",
ShowInDiscoveryDocument = false,
Required = true
}
}
}
};
}
}
internal class Policies
{
public static IEnumerable<Policy> Get()
{
return new List<Policy>
{
new Policy
{
Name = "Internal",
Scopes = { "auth-api" }
}
};
}
}
internal class Policy
{
public string Name { get; set; }
public ICollection<string> Scopes { get; set; }
public Policy()
{
Name = string.Empty;
Scopes = new List<string>();
}
}
}

View File

@ -0,0 +1,85 @@
body {
margin-top: 15px;
}
.navbar-header {
position: relative;
top: -4px;
}
.navbar-brand > .icon-banner {
position: relative;
top: -2px;
display: inline;
}
label {
font-weight: normal !important;
}
.icon {
position: relative;
top: -10px;
}
.logged-out iframe {
display: none;
width: 0;
height: 0;
}
.page-consent .client-logo {
float: left;
}
.page-consent .client-logo img {
width: 80px;
height: 80px;
}
.page-consent .consent-buttons {
margin-top: 25px;
}
.page-consent .consent-form .consent-scopecheck {
display: inline-block;
margin-right: 5px;
}
.page-consent .consent-form .consent-description {
margin-left: 25px;
}
.page-consent .consent-form .consent-description label {
font-weight: normal;
}
.page-consent .consent-form .consent-remember {
padding-left: 16px;
}
.grants .page-header {
margin-bottom: 10px;
}
.grants .grant {
margin-top: 20px;
padding-bottom: 20px;
border-bottom: 1px solid lightgray;
}
.grants .grant img {
width: 100px;
height: 100px;
}
.grants .grant .clientname {
font-size: 140%;
font-weight: bold;
}
.grants .grant .granttype {
font-size: 120%;
font-weight: bold;
}
.grants .grant .created {
font-size: 120%;
font-weight: bold;
}
.grants .grant .expires {
font-size: 120%;
font-weight: bold;
}
.grants .grant li {
list-style-type: none;
display: inline;
}
.grants .grant li:after {
content: ', ';
}
.grants .grant li:last-child:after {
content: '';
}

View File

@ -0,0 +1,307 @@
using IdentityModel;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using IdentityServer4.Test;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using IdentityServer4.Events;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Identity;
using Teknik.IdentityServer.Security;
using Teknik.IdentityServer.Services;
using Teknik.IdentityServer.ViewModels;
using Teknik.IdentityServer.Options;
using Teknik.IdentityServer.Models;
using Microsoft.Extensions.Logging;
using Teknik.Logging;
using Teknik.Configuration;
namespace Teknik.IdentityServer.Controllers
{
public class AccountController : DefaultController
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IIdentityServerInteractionService _interaction;
private readonly IEventService _events;
private readonly AccountService _account;
public AccountController(
ILogger<Logger> logger,
Config config,
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IHttpContextAccessor httpContextAccessor,
IAuthenticationSchemeProvider schemeProvider,
IEventService events,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) : base(logger, config)
{
// if the TestUserStore is not in DI, then we'll just use the global users collection
_userManager = userManager;
_signInManager = signInManager;
_interaction = interaction;
_events = events;
_account = new AccountService(interaction, httpContextAccessor, schemeProvider, clientStore);
}
/// <summary>
/// Show login page
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await _account.BuildLoginViewModelAsync(returnUrl);
return View(vm);
}
/// <summary>
/// Handle postback from username/password login
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string button, string returnUrl = null)
{
if (button != "login")
{
// the user clicked the "cancel" button
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context != null)
{
// if the user cancels, send a result back into IdentityServer as if they
// denied the consent (even if this client does not require consent).
// this will send back an access denied OIDC error response to the client.
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied);
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return Redirect(returnUrl);
}
else
{
// since we don't have a valid context, then we just go back to the home page
return Redirect("~/");
}
}
if (ModelState.IsValid)
{
// Check to see if the user is banned
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
if (foundUser.AccountStatus == Utilities.AccountStatus.Banned)
{
// Redirect to banned page
return RedirectToAction(nameof(Banned));
}
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
// make sure the returnUrl is still valid, and if so redirect back to authorize endpoint or a local page
if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("~/");
}
if (result.RequiresTwoFactor)
{
// Redirect to 2FA page
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
// Redirect to locked out page
return RedirectToAction(nameof(Lockout));
}
}
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
}
// something went wrong, show form with error
var vm = await _account.BuildLoginViewModelAsync(model);
return View(vm);
}
[HttpGet]
public async Task<IActionResult> LoginWith2fa(bool rememberMe, string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var model = new LoginWith2faViewModel { RememberMe = rememberMe };
ViewData["ReturnUrl"] = returnUrl;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
if (result.Succeeded)
{
return RedirectToLocal(returnUrl);
}
else if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return View();
}
}
[HttpGet]
public async Task<IActionResult> LoginWithRecoveryCode(string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LoginWithRecoveryCode(LoginWithRecoveryCodeViewModel model, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new ApplicationException($"Unable to load two-factor authentication user.");
}
var recoveryCode = model.RecoveryCode.Replace(" ", string.Empty);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
if (result.Succeeded)
{
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return View();
}
}
[HttpGet]
public IActionResult Lockout()
{
return View();
}
[HttpGet]
public IActionResult Banned()
{
return View();
}
/// <summary>
/// Show logout page
/// </summary>
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
// build a model so the logout page knows what to display
var vm = await _account.BuildLogoutViewModelAsync(logoutId);
if (vm.ShowLogoutPrompt == false)
{
// if the request for logout was properly authenticated from IdentityServer, then
// we don't need to show the prompt and can just log the user out directly.
return await Logout(vm);
}
return View(vm);
}
/// <summary>
/// Handle logout page postback
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated == true)
{
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
return View("LoggedOut", vm);
return RedirectToLocal(model.ReturnURL);
}
private IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction(nameof(HomeController.Index), "Home");
}
}
}
}

View File

@ -0,0 +1,75 @@
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.IdentityServer.Models;
using Teknik.IdentityServer.Security;
using Teknik.IdentityServer.Services;
using Teknik.Logging;
namespace Teknik.IdentityServer.Controllers
{
/// <summary>
/// This controller processes the consent UI
/// </summary>
public class ConsentController : DefaultController
{
private readonly ConsentService _consent;
public ConsentController(
ILogger<Logger> logger,
Config config,
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IResourceStore resourceStore) : base(logger, config)
{
_consent = new ConsentService(interaction, clientStore, resourceStore, logger);
}
/// <summary>
/// Shows the consent screen
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Index(string returnUrl)
{
var vm = await _consent.BuildViewModelAsync(returnUrl);
if (vm != null)
{
return View("Index", vm);
}
return View("Error");
}
/// <summary>
/// Handles the consent screen postback
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ConsentInputModel model)
{
var result = await _consent.ProcessConsent(model);
if (result.IsRedirect)
{
return Redirect(result.RedirectUri);
}
if (result.HasValidationError)
{
ModelState.AddModelError("", result.ValidationError);
}
if (result.ShowView)
{
return View("Index", result.ViewModel);
}
return View("Error");
}
}
}

View File

@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.Logging;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Controllers
{
public class DefaultController : Controller
{
protected readonly ILogger<Logger> _logger;
protected readonly Config _config;
public DefaultController(ILogger<Logger> logger, Config config)
{
_logger = logger;
_config = config;
ViewBag.Title = "Teknik Authentication";
ViewBag.Description = "Teknik Authentication Service";
}
// Get the Favicon
[HttpGet]
[AllowAnonymous]
[ResponseCache(Duration = 31536000, Location = ResponseCacheLocation.Any)]
public IActionResult Favicon([FromServices] IHostingEnvironment env)
{
string imageFile = FileHelper.MapPath(env, Constants.FAVICON_PATH);
FileStream fs = new FileStream(imageFile, FileMode.Open, FileAccess.Read);
return File(fs, "image/x-icon");
}
// Get the Robots.txt
[HttpGet]
[AllowAnonymous]
public IActionResult Robots([FromServices] IHostingEnvironment env)
{
//string file = FileHelper.MapPath(env, Constants.ROBOTS_PATH);
return File(Constants.ROBOTS_PATH, "text/plain");
}
protected IActionResult GenerateActionResult(object json)
{
return GenerateActionResult(json, View());
}
protected IActionResult GenerateActionResult(object json, IActionResult result)
{
if (Request.IsAjaxRequest())
{
return Json(json);
}
return result;
}
}
}

View File

@ -0,0 +1,237 @@
using IdentityServer4.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.IdentityServer.ViewModels;
using Teknik.Logging;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Controllers
{
public class ErrorController : DefaultController
{
private readonly IIdentityServerInteractionService _interaction;
public ErrorController(ILogger<Logger> logger, Config config, IIdentityServerInteractionService interaction) : base(logger, config)
{
_interaction = interaction;
}
public IActionResult HttpError(int statusCode)
{
switch (statusCode)
{
case 401:
return Http401();
case 403:
return Http403();
case 404:
return Http404();
default:
return HttpGeneral(statusCode);
}
}
public IActionResult HttpGeneral(int statusCode)
{
ViewBag.Title = statusCode + " - " + _config.Title;
LogError(LogLevel.Error, "HTTP Error Code: " + statusCode);
ErrorViewModel model = new ErrorViewModel();
model.StatusCode = statusCode;
return GenerateActionResult(CreateErrorObj("Http", statusCode, "Invalid HTTP Response"), View("~/Views/Error/HttpGeneral.cshtml", model));
}
[AllowAnonymous]
public IActionResult Http401()
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
ViewBag.Title = "401 - " + _config.Title;
ViewBag.Description = "Unauthorized";
LogError(LogLevel.Error, "Unauthorized");
ErrorViewModel model = new ErrorViewModel();
model.StatusCode = StatusCodes.Status401Unauthorized;
return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status401Unauthorized, "Unauthorized"), View("~/Views/Error/Http401.cshtml", model));
}
[AllowAnonymous]
public IActionResult Http403()
{
Response.StatusCode = StatusCodes.Status403Forbidden;
ViewBag.Title = "403 - " + _config.Title;
ViewBag.Description = "Access Denied";
LogError(LogLevel.Error, "Access Denied");
ErrorViewModel model = new ErrorViewModel();
model.StatusCode = StatusCodes.Status403Forbidden;
return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status403Forbidden, "Access Denied"), View("~/Views/Error/Http403.cshtml", model));
}
[AllowAnonymous]
public IActionResult Http404()
{
Response.StatusCode = StatusCodes.Status404NotFound;
ViewBag.Title = "404 - " + _config.Title;
ViewBag.Description = "Uh Oh, can't find it!";
LogError(LogLevel.Warning, "Page Not Found");
ErrorViewModel model = new ErrorViewModel();
model.StatusCode = StatusCodes.Status404NotFound;
return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status404NotFound, "Page Not Found"), View("~/Views/Error/Http404.cshtml", model));
}
[AllowAnonymous]
public IActionResult Http500(Exception exception)
{
if (HttpContext != null)
{
var ex = HttpContext.Features.Get<IExceptionHandlerFeature>();
if (ex != null)
{
exception = ex.Error;
}
HttpContext.Session.Set("Exception", exception);
}
Response.StatusCode = StatusCodes.Status500InternalServerError;
ViewBag.Title = "500 - " + _config.Title;
ViewBag.Description = "Something Borked";
LogError(LogLevel.Error, "Server Error", exception);
ErrorViewModel model = new ErrorViewModel();
model.StatusCode = StatusCodes.Status500InternalServerError;
model.Exception = exception;
return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status500InternalServerError, exception.Message), View("~/Views/Error/Http500.cshtml", model));
}
[AllowAnonymous]
public async Task<IActionResult> IdentityError(string errorId)
{
var message = await _interaction.GetErrorContextAsync(errorId);
Response.StatusCode = StatusCodes.Status500InternalServerError;
ViewBag.Title = "Identity Error - " + _config.Title;
ViewBag.Description = "The Identity Service threw an error";
LogError(LogLevel.Error, "Identity Error: " + message.Error);
IdentityErrorViewModel model = new IdentityErrorViewModel();
model.Title = message.Error;
model.Description = message.ErrorDescription;
return GenerateActionResult(CreateErrorObj("Http", StatusCodes.Status500InternalServerError, message.Error), View("~/Views/Error/IdentityError.cshtml", model));
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public IActionResult SubmitErrorReport(SubmitReportViewModel model)
{
try
{
string exceptionMsg = model.Exception;
// Try to grab the actual exception that occured
Exception ex = HttpContext.Session.Get<Exception>("Exception");
if (ex != null)
{
exceptionMsg = string.Format(@"
Exception: {0}
Source: {1}
Stack Trace:
{2}
", ex.GetFullMessage(true), ex.Source, ex.StackTrace);
}
// Let's also email the message to support
SmtpClient client = new SmtpClient();
client.Host = _config.ContactConfig.EmailAccount.Host;
client.Port = _config.ContactConfig.EmailAccount.Port;
client.EnableSsl = _config.ContactConfig.EmailAccount.SSL;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = true;
client.Credentials = new System.Net.NetworkCredential(_config.ContactConfig.EmailAccount.Username, _config.ContactConfig.EmailAccount.Password);
client.Timeout = 5000;
MailMessage mail = new MailMessage(new MailAddress(_config.NoReplyEmail, _config.NoReplyEmail), new MailAddress(_config.SupportEmail, "Teknik Support"));
mail.Sender = new MailAddress(_config.ContactConfig.EmailAccount.EmailAddress);
mail.Subject = "[Exception] Application Exception Occured";
mail.Body = @"
An exception has occured at: " + model.CurrentUrl + @"
----------------------------------------
User Message:
" + model.Message + @"
----------------------------------------
" + exceptionMsg;
mail.BodyEncoding = UTF8Encoding.UTF8;
mail.DeliveryNotificationOptions = DeliveryNotificationOptions.Never;
client.Send(mail);
}
catch (Exception ex)
{
return Json(new { error = "Error submitting report. Exception: " + ex.Message });
}
return Json(new { result = "true" });
}
private object CreateErrorObj(string type, int statusCode, string message)
{
return new { error = new { type = type, status = statusCode, message = message } };
}
private void LogError(LogLevel level, string message)
{
LogError(level, message, null);
}
private void LogError(LogLevel level, string message, Exception exception)
{
if (Request != null)
{
message += " | Url: " + Request.GetDisplayUrl();
message += " | Referred Url: " + Request.Headers["Referer"].ToString();
message += " | Method: " + Request.Method;
message += " | User Agent: " + Request.Headers["User-Agent"].ToString();
}
_logger.Log(level, message, exception);
}
}
}

View File

@ -0,0 +1,92 @@
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Teknik.IdentityServer.Security;
using Teknik.IdentityServer.ViewModels;
using Teknik.Logging;
using Microsoft.Extensions.Logging;
using Teknik.Configuration;
namespace Teknik.IdentityServer.Controllers
{
/// <summary>
/// This sample controller allows a user to revoke grants given to clients
/// </summary>
[Authorize(AuthenticationSchemes = "Identity.Application")]
public class GrantsController : DefaultController
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clients;
private readonly IResourceStore _resources;
public GrantsController(
ILogger<Logger> logger,
Config config,
IIdentityServerInteractionService interaction,
IClientStore clients,
IResourceStore resources) : base(logger, config)
{
_interaction = interaction;
_clients = clients;
_resources = resources;
}
/// <summary>
/// Show list of grants
/// </summary>
[HttpGet]
public async Task<IActionResult> Index()
{
return View("Index", await BuildViewModelAsync());
}
/// <summary>
/// Handle postback to revoke a client
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Revoke(string clientId)
{
await _interaction.RevokeUserConsentAsync(clientId);
return RedirectToAction("Index");
}
private async Task<GrantsViewModel> BuildViewModelAsync()
{
var grants = await _interaction.GetAllUserConsentsAsync();
var list = new List<GrantViewModel>();
foreach(var grant in grants)
{
var client = await _clients.FindClientByIdAsync(grant.ClientId);
if (client != null)
{
var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes);
var item = new GrantViewModel()
{
ClientId = client.ClientId,
ClientName = client.ClientName ?? client.ClientId,
ClientLogoUrl = client.LogoUri,
ClientUrl = client.ClientUri,
Created = grant.CreationTime,
Expires = grant.Expiration,
IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(),
ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray()
};
list.Add(item);
}
}
return new GrantsViewModel
{
Grants = list
};
}
}
}

View File

@ -0,0 +1,29 @@
using IdentityServer4.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.IdentityServer.Security;
using Teknik.IdentityServer.ViewModels;
using Teknik.Logging;
namespace Teknik.IdentityServer.Controllers
{
public class HomeController : DefaultController
{
private readonly IIdentityServerInteractionService _interaction;
public HomeController(
ILogger<Logger> logger,
Config config,
IIdentityServerInteractionService interaction) : base(logger, config)
{
_interaction = interaction;
}
public IActionResult Index()
{
return View();
}
}
}

View File

@ -0,0 +1,420 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Teknik.Configuration;
using Teknik.IdentityServer.Models;
using Teknik.IdentityServer.Models.Manage;
using Teknik.Logging;
namespace Teknik.IdentityServer.Controllers
{
[Authorize(Policy = "Internal", AuthenticationSchemes = "Bearer")]
[Route("[controller]/[action]")]
[ApiController]
public class ManageController : DefaultController
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public ManageController(
ILogger<Logger> logger,
Config config,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) : base(logger, config)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> CreateUser(NewUserModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(model.Password))
return new JsonResult(new { success = false, message = "Password is required" });
var identityUser = new ApplicationUser(model.Username)
{
Id = Guid.NewGuid().ToString(),
UserName = model.Username,
AccountStatus = model.AccountStatus,
AccountType = model.AccountType,
Email = model.RecoveryEmail,
EmailConfirmed = model.RecoveryVerified,
PGPPublicKey = model.PGPPublicKey
};
var result = await _userManager.CreateAsync(identityUser, model.Password);
if (result.Succeeded)
{
return new JsonResult(new { success = true });
}
return new JsonResult(new { success = false, message = "Unable to create user.", identityErrors = result.Errors });
}
[HttpPost]
public async Task<IActionResult> DeleteUser(DeleteUserModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
var result = await _userManager.DeleteAsync(foundUser);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to delete user.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpGet]
public async Task<IActionResult> UserExists(string username)
{
if (string.IsNullOrEmpty(username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(username);
return new JsonResult(new { success = true, data = foundUser != null });
}
[HttpGet]
public async Task<IActionResult> GetUserInfo(string username)
{
if (string.IsNullOrEmpty(username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(username);
if (foundUser != null)
{
return new JsonResult(new { success = true, data = foundUser.ToJson() });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> CheckPassword(CheckPasswordModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(model.Password))
return new JsonResult(new { success = false, message = "Password is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
bool valid = await _userManager.CheckPasswordAsync(foundUser, model.Password);
return new JsonResult(new { success = true, data = valid });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> GeneratePasswordResetToken(GeneratePasswordResetTokenModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
string token = await _userManager.GeneratePasswordResetTokenAsync(foundUser);
return new JsonResult(new { success = true, data = token });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(model.Token))
return new JsonResult(new { success = false, message = "Token is required" });
if (string.IsNullOrEmpty(model.Password))
return new JsonResult(new { success = false, message = "Password is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
var result = await _userManager.ResetPasswordAsync(foundUser, model.Token, model.Password);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to reset password.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> UpdatePassword(UpdatePasswordModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(model.CurrentPassword))
return new JsonResult(new { success = false, message = "Current Password is required" });
if (string.IsNullOrEmpty(model.NewPassword))
return new JsonResult(new { success = false, message = "New Password is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
var result = await _userManager.ChangePasswordAsync(foundUser, model.CurrentPassword, model.NewPassword);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to update password.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> UpdateEmail(UpdateEmailModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
var result = await _userManager.SetEmailAsync(foundUser, model.Email);
if (result.Succeeded)
{
var token = await _userManager.GenerateEmailConfirmationTokenAsync(foundUser);
return new JsonResult(new { success = true, data = token });
}
else
return new JsonResult(new { success = false, message = "Unable to update email address.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> VerifyEmail(VerifyEmailModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(model.Token))
return new JsonResult(new { success = false, message = "Token is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
var result = await _userManager.ConfirmEmailAsync(foundUser, model.Token);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to verify email address.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> UpdateAccountStatus(UpdateAccountStatusModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
foundUser.AccountStatus = model.AccountStatus;
var result = await _userManager.UpdateAsync(foundUser);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to update account status.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> UpdateAccountType(UpdateAccountTypeModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
foundUser.AccountType = model.AccountType;
var result = await _userManager.UpdateAsync(foundUser);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to update account type.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> UpdatePGPPublicKey(UpdatePGPPublicKeyModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
foundUser.PGPPublicKey = model.PGPPublicKey;
var result = await _userManager.UpdateAsync(foundUser);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to update pgp public key.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpGet]
public async Task<IActionResult> Get2FAKey(string username)
{
if (string.IsNullOrEmpty(username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(username);
if (foundUser != null)
{
string unformattedKey = await _userManager.GetAuthenticatorKeyAsync(foundUser);
return new JsonResult(new { success = true, data = FormatKey(unformattedKey) });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> Reset2FAKey(Reset2FAKeyModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
await _userManager.ResetAuthenticatorKeyAsync(foundUser);
string unformattedKey = await _userManager.GetAuthenticatorKeyAsync(foundUser);
return new JsonResult(new { success = true, data = FormatKey(unformattedKey) });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> Enable2FA(Enable2FAModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(model.Code))
return new JsonResult(new { success = false, message = "Code is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
// Strip spaces and hypens
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
foundUser, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (is2faTokenValid)
{
var result = await _userManager.SetTwoFactorEnabledAsync(foundUser, true);
if (result.Succeeded)
{
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(foundUser, 10);
return new JsonResult(new { success = true, data = recoveryCodes.ToArray() });
}
else
return new JsonResult(new { success = false, message = "Unable to set Two-Factor Authentication.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "Verification code is invalid." });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> Disable2FA(Disable2FAModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
var result = await _userManager.SetTwoFactorEnabledAsync(foundUser, false);
if (result.Succeeded)
return new JsonResult(new { success = true });
else
return new JsonResult(new { success = false, message = "Unable to disable Two-Factor Authentication.", identityErrors = result.Errors });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpPost]
public async Task<IActionResult> GenerateRecoveryCodes(GenerateRecoveryCodesModel model)
{
if (string.IsNullOrEmpty(model.Username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundUser = await _userManager.FindByNameAsync(model.Username);
if (foundUser != null)
{
if (foundUser.TwoFactorEnabled)
{
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(foundUser, 10);
return new JsonResult(new { success = true, data = recoveryCodes.ToArray() });
}
return new JsonResult(new { success = false, message = "Two-Factor Authentication is not enabled." });
}
return new JsonResult(new { success = false, message = "User does not exist." });
}
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
currentPosition += 4;
}
if (currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
}
}

View File

@ -0,0 +1,242 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Teknik.IdentityServer;
namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20181015060219_InitialApplicationDbContextMigration")]
partial class InitialApplicationDbContextMigration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<int>("AccountStatus");
b.Property<int>("AccountType");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<DateTime>("CreationDate");
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<DateTime>("LastSeen");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PGPPublicKey");
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,225 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb
{
public partial class InitialApplicationDbContextMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false),
CreationDate = table.Column<DateTime>(nullable: false),
LastSeen = table.Column<DateTime>(nullable: false),
AccountType = table.Column<int>(nullable: false),
AccountStatus = table.Column<int>(nullable: false),
PGPPublicKey = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(nullable: false),
ProviderKey = table.Column<string>(nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(nullable: false),
Name = table.Column<string>(nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -0,0 +1,240 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Teknik.IdentityServer;
namespace Teknik.IdentityServer.Data.Migrations.ApplicationDb
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Teknik.IdentityServer.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<int>("AccountStatus");
b.Property<int>("AccountType");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<DateTime>("CreationDate");
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<DateTime>("LastSeen");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PGPPublicKey");
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Teknik.IdentityServer.Models.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,679 @@
// <auto-generated />
using System;
using IdentityServer4.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.ConfigurationDb
{
[DbContext(typeof(ConfigurationDbContext))]
[Migration("20180930040544_InitialIdentityServerConfigurationDbMigration")]
partial class InitialIdentityServerConfigurationDbMigration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<string>("DisplayName")
.HasMaxLength(200);
b.Property<bool>("Enabled");
b.Property<DateTime?>("LastAccessed");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Updated");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ApiResources");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.ToTable("ApiClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.ToTable("ApiProperties");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<string>("DisplayName")
.HasMaxLength(200);
b.Property<bool>("Emphasize");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.Property<bool>("Required");
b.Property<bool>("ShowInDiscoveryDocument");
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ApiScopes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiScopeId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ApiScopeId");
b.ToTable("ApiScopeClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<DateTime?>("Expiration");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(4000);
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.ToTable("ApiSecrets");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("AbsoluteRefreshTokenLifetime");
b.Property<int>("AccessTokenLifetime");
b.Property<int>("AccessTokenType");
b.Property<bool>("AllowAccessTokensViaBrowser");
b.Property<bool>("AllowOfflineAccess");
b.Property<bool>("AllowPlainTextPkce");
b.Property<bool>("AllowRememberConsent");
b.Property<bool>("AlwaysIncludeUserClaimsInIdToken");
b.Property<bool>("AlwaysSendClientClaims");
b.Property<int>("AuthorizationCodeLifetime");
b.Property<bool>("BackChannelLogoutSessionRequired");
b.Property<string>("BackChannelLogoutUri")
.HasMaxLength(2000);
b.Property<string>("ClientClaimsPrefix")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<string>("ClientName")
.HasMaxLength(200);
b.Property<string>("ClientUri")
.HasMaxLength(2000);
b.Property<int?>("ConsentLifetime");
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<bool>("EnableLocalLogin");
b.Property<bool>("Enabled");
b.Property<bool>("FrontChannelLogoutSessionRequired");
b.Property<string>("FrontChannelLogoutUri")
.HasMaxLength(2000);
b.Property<int>("IdentityTokenLifetime");
b.Property<bool>("IncludeJwtId");
b.Property<DateTime?>("LastAccessed");
b.Property<string>("LogoUri")
.HasMaxLength(2000);
b.Property<string>("PairWiseSubjectSalt")
.HasMaxLength(200);
b.Property<string>("ProtocolType")
.IsRequired()
.HasMaxLength(200);
b.Property<int>("RefreshTokenExpiration");
b.Property<int>("RefreshTokenUsage");
b.Property<bool>("RequireClientSecret");
b.Property<bool>("RequireConsent");
b.Property<bool>("RequirePkce");
b.Property<int>("SlidingRefreshTokenLifetime");
b.Property<bool>("UpdateAccessTokenClaimsOnRefresh");
b.Property<DateTime?>("Updated");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("Clients");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(250);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Origin")
.IsRequired()
.HasMaxLength(150);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientCorsOrigins");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("GrantType")
.IsRequired()
.HasMaxLength(250);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientGrantTypes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientIdPRestrictions");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("PostLogoutRedirectUri")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientPostLogoutRedirectUris");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientProperties");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("RedirectUri")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientRedirectUris");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Scope")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientScopes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(2000);
b.Property<DateTime?>("Expiration");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(4000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientSecrets");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("IdentityResourceId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("IdentityResourceId");
b.ToTable("IdentityClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<string>("DisplayName")
.HasMaxLength(200);
b.Property<bool>("Emphasize");
b.Property<bool>("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.Property<bool>("Required");
b.Property<bool>("ShowInDiscoveryDocument");
b.Property<DateTime?>("Updated");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("IdentityResources");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("IdentityResourceId");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("IdentityResourceId");
b.ToTable("IdentityProperties");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("UserClaims")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("Properties")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("Scopes")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope")
.WithMany("UserClaims")
.HasForeignKey("ApiScopeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("Secrets")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("Claims")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("AllowedCorsOrigins")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("AllowedGrantTypes")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("IdentityProviderRestrictions")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("PostLogoutRedirectUris")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("Properties")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("RedirectUris")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("AllowedScopes")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("ClientSecrets")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
.WithMany("UserClaims")
.HasForeignKey("IdentityResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
.WithMany("Properties")
.HasForeignKey("IdentityResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,602 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.ConfigurationDb
{
public partial class InitialIdentityServerConfigurationDbMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ApiResources",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Enabled = table.Column<bool>(nullable: false),
Name = table.Column<string>(maxLength: 200, nullable: false),
DisplayName = table.Column<string>(maxLength: 200, nullable: true),
Description = table.Column<string>(maxLength: 1000, nullable: true),
Created = table.Column<DateTime>(nullable: false),
Updated = table.Column<DateTime>(nullable: true),
LastAccessed = table.Column<DateTime>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiResources", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Clients",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Enabled = table.Column<bool>(nullable: false),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
ProtocolType = table.Column<string>(maxLength: 200, nullable: false),
RequireClientSecret = table.Column<bool>(nullable: false),
ClientName = table.Column<string>(maxLength: 200, nullable: true),
Description = table.Column<string>(maxLength: 1000, nullable: true),
ClientUri = table.Column<string>(maxLength: 2000, nullable: true),
LogoUri = table.Column<string>(maxLength: 2000, nullable: true),
RequireConsent = table.Column<bool>(nullable: false),
AllowRememberConsent = table.Column<bool>(nullable: false),
AlwaysIncludeUserClaimsInIdToken = table.Column<bool>(nullable: false),
RequirePkce = table.Column<bool>(nullable: false),
AllowPlainTextPkce = table.Column<bool>(nullable: false),
AllowAccessTokensViaBrowser = table.Column<bool>(nullable: false),
FrontChannelLogoutUri = table.Column<string>(maxLength: 2000, nullable: true),
FrontChannelLogoutSessionRequired = table.Column<bool>(nullable: false),
BackChannelLogoutUri = table.Column<string>(maxLength: 2000, nullable: true),
BackChannelLogoutSessionRequired = table.Column<bool>(nullable: false),
AllowOfflineAccess = table.Column<bool>(nullable: false),
IdentityTokenLifetime = table.Column<int>(nullable: false),
AccessTokenLifetime = table.Column<int>(nullable: false),
AuthorizationCodeLifetime = table.Column<int>(nullable: false),
ConsentLifetime = table.Column<int>(nullable: true),
AbsoluteRefreshTokenLifetime = table.Column<int>(nullable: false),
SlidingRefreshTokenLifetime = table.Column<int>(nullable: false),
RefreshTokenUsage = table.Column<int>(nullable: false),
UpdateAccessTokenClaimsOnRefresh = table.Column<bool>(nullable: false),
RefreshTokenExpiration = table.Column<int>(nullable: false),
AccessTokenType = table.Column<int>(nullable: false),
EnableLocalLogin = table.Column<bool>(nullable: false),
IncludeJwtId = table.Column<bool>(nullable: false),
AlwaysSendClientClaims = table.Column<bool>(nullable: false),
ClientClaimsPrefix = table.Column<string>(maxLength: 200, nullable: true),
PairWiseSubjectSalt = table.Column<string>(maxLength: 200, nullable: true),
Created = table.Column<DateTime>(nullable: false),
Updated = table.Column<DateTime>(nullable: true),
LastAccessed = table.Column<DateTime>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Clients", x => x.Id);
});
migrationBuilder.CreateTable(
name: "IdentityResources",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Enabled = table.Column<bool>(nullable: false),
Name = table.Column<string>(maxLength: 200, nullable: false),
DisplayName = table.Column<string>(maxLength: 200, nullable: true),
Description = table.Column<string>(maxLength: 1000, nullable: true),
Required = table.Column<bool>(nullable: false),
Emphasize = table.Column<bool>(nullable: false),
ShowInDiscoveryDocument = table.Column<bool>(nullable: false),
Created = table.Column<DateTime>(nullable: false),
Updated = table.Column<DateTime>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_IdentityResources", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ApiClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Type = table.Column<string>(maxLength: 200, nullable: false),
ApiResourceId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiClaims", x => x.Id);
table.ForeignKey(
name: "FK_ApiClaims_ApiResources_ApiResourceId",
column: x => x.ApiResourceId,
principalTable: "ApiResources",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ApiProperties",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Key = table.Column<string>(maxLength: 250, nullable: false),
Value = table.Column<string>(maxLength: 2000, nullable: false),
ApiResourceId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiProperties", x => x.Id);
table.ForeignKey(
name: "FK_ApiProperties_ApiResources_ApiResourceId",
column: x => x.ApiResourceId,
principalTable: "ApiResources",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ApiScopes",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(maxLength: 200, nullable: false),
DisplayName = table.Column<string>(maxLength: 200, nullable: true),
Description = table.Column<string>(maxLength: 1000, nullable: true),
Required = table.Column<bool>(nullable: false),
Emphasize = table.Column<bool>(nullable: false),
ShowInDiscoveryDocument = table.Column<bool>(nullable: false),
ApiResourceId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiScopes", x => x.Id);
table.ForeignKey(
name: "FK_ApiScopes_ApiResources_ApiResourceId",
column: x => x.ApiResourceId,
principalTable: "ApiResources",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ApiSecrets",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Description = table.Column<string>(maxLength: 1000, nullable: true),
Value = table.Column<string>(maxLength: 4000, nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Type = table.Column<string>(maxLength: 250, nullable: false),
Created = table.Column<DateTime>(nullable: false),
ApiResourceId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiSecrets", x => x.Id);
table.ForeignKey(
name: "FK_ApiSecrets_ApiResources_ApiResourceId",
column: x => x.ApiResourceId,
principalTable: "ApiResources",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Type = table.Column<string>(maxLength: 250, nullable: false),
Value = table.Column<string>(maxLength: 250, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientClaims", x => x.Id);
table.ForeignKey(
name: "FK_ClientClaims_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientCorsOrigins",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Origin = table.Column<string>(maxLength: 150, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id);
table.ForeignKey(
name: "FK_ClientCorsOrigins_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientGrantTypes",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
GrantType = table.Column<string>(maxLength: 250, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientGrantTypes", x => x.Id);
table.ForeignKey(
name: "FK_ClientGrantTypes_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientIdPRestrictions",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Provider = table.Column<string>(maxLength: 200, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id);
table.ForeignKey(
name: "FK_ClientIdPRestrictions_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientPostLogoutRedirectUris",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
PostLogoutRedirectUri = table.Column<string>(maxLength: 2000, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id);
table.ForeignKey(
name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientProperties",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Key = table.Column<string>(maxLength: 250, nullable: false),
Value = table.Column<string>(maxLength: 2000, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientProperties", x => x.Id);
table.ForeignKey(
name: "FK_ClientProperties_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientRedirectUris",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RedirectUri = table.Column<string>(maxLength: 2000, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientRedirectUris", x => x.Id);
table.ForeignKey(
name: "FK_ClientRedirectUris_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientScopes",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Scope = table.Column<string>(maxLength: 200, nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientScopes", x => x.Id);
table.ForeignKey(
name: "FK_ClientScopes_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClientSecrets",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Description = table.Column<string>(maxLength: 2000, nullable: true),
Value = table.Column<string>(maxLength: 4000, nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Type = table.Column<string>(maxLength: 250, nullable: false),
Created = table.Column<DateTime>(nullable: false),
ClientId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClientSecrets", x => x.Id);
table.ForeignKey(
name: "FK_ClientSecrets_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "IdentityClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Type = table.Column<string>(maxLength: 200, nullable: false),
IdentityResourceId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_IdentityClaims", x => x.Id);
table.ForeignKey(
name: "FK_IdentityClaims_IdentityResources_IdentityResourceId",
column: x => x.IdentityResourceId,
principalTable: "IdentityResources",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "IdentityProperties",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Key = table.Column<string>(maxLength: 250, nullable: false),
Value = table.Column<string>(maxLength: 2000, nullable: false),
IdentityResourceId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_IdentityProperties", x => x.Id);
table.ForeignKey(
name: "FK_IdentityProperties_IdentityResources_IdentityResourceId",
column: x => x.IdentityResourceId,
principalTable: "IdentityResources",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ApiScopeClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Type = table.Column<string>(maxLength: 200, nullable: false),
ApiScopeId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiScopeClaims", x => x.Id);
table.ForeignKey(
name: "FK_ApiScopeClaims_ApiScopes_ApiScopeId",
column: x => x.ApiScopeId,
principalTable: "ApiScopes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ApiClaims_ApiResourceId",
table: "ApiClaims",
column: "ApiResourceId");
migrationBuilder.CreateIndex(
name: "IX_ApiProperties_ApiResourceId",
table: "ApiProperties",
column: "ApiResourceId");
migrationBuilder.CreateIndex(
name: "IX_ApiResources_Name",
table: "ApiResources",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiScopeClaims_ApiScopeId",
table: "ApiScopeClaims",
column: "ApiScopeId");
migrationBuilder.CreateIndex(
name: "IX_ApiScopes_ApiResourceId",
table: "ApiScopes",
column: "ApiResourceId");
migrationBuilder.CreateIndex(
name: "IX_ApiScopes_Name",
table: "ApiScopes",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiSecrets_ApiResourceId",
table: "ApiSecrets",
column: "ApiResourceId");
migrationBuilder.CreateIndex(
name: "IX_ClientClaims_ClientId",
table: "ClientClaims",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientCorsOrigins_ClientId",
table: "ClientCorsOrigins",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientGrantTypes_ClientId",
table: "ClientGrantTypes",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientIdPRestrictions_ClientId",
table: "ClientIdPRestrictions",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientPostLogoutRedirectUris_ClientId",
table: "ClientPostLogoutRedirectUris",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientProperties_ClientId",
table: "ClientProperties",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientRedirectUris_ClientId",
table: "ClientRedirectUris",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_Clients_ClientId",
table: "Clients",
column: "ClientId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ClientScopes_ClientId",
table: "ClientScopes",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_ClientSecrets_ClientId",
table: "ClientSecrets",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_IdentityClaims_IdentityResourceId",
table: "IdentityClaims",
column: "IdentityResourceId");
migrationBuilder.CreateIndex(
name: "IX_IdentityProperties_IdentityResourceId",
table: "IdentityProperties",
column: "IdentityResourceId");
migrationBuilder.CreateIndex(
name: "IX_IdentityResources_Name",
table: "IdentityResources",
column: "Name",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiClaims");
migrationBuilder.DropTable(
name: "ApiProperties");
migrationBuilder.DropTable(
name: "ApiScopeClaims");
migrationBuilder.DropTable(
name: "ApiSecrets");
migrationBuilder.DropTable(
name: "ClientClaims");
migrationBuilder.DropTable(
name: "ClientCorsOrigins");
migrationBuilder.DropTable(
name: "ClientGrantTypes");
migrationBuilder.DropTable(
name: "ClientIdPRestrictions");
migrationBuilder.DropTable(
name: "ClientPostLogoutRedirectUris");
migrationBuilder.DropTable(
name: "ClientProperties");
migrationBuilder.DropTable(
name: "ClientRedirectUris");
migrationBuilder.DropTable(
name: "ClientScopes");
migrationBuilder.DropTable(
name: "ClientSecrets");
migrationBuilder.DropTable(
name: "IdentityClaims");
migrationBuilder.DropTable(
name: "IdentityProperties");
migrationBuilder.DropTable(
name: "ApiScopes");
migrationBuilder.DropTable(
name: "Clients");
migrationBuilder.DropTable(
name: "IdentityResources");
migrationBuilder.DropTable(
name: "ApiResources");
}
}
}

View File

@ -0,0 +1,677 @@
// <auto-generated />
using System;
using IdentityServer4.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.ConfigurationDb
{
[DbContext(typeof(ConfigurationDbContext))]
partial class ConfigurationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<string>("DisplayName")
.HasMaxLength(200);
b.Property<bool>("Enabled");
b.Property<DateTime?>("LastAccessed");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime?>("Updated");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ApiResources");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.ToTable("ApiClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.ToTable("ApiProperties");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<string>("DisplayName")
.HasMaxLength(200);
b.Property<bool>("Emphasize");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.Property<bool>("Required");
b.Property<bool>("ShowInDiscoveryDocument");
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ApiScopes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiScopeId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ApiScopeId");
b.ToTable("ApiScopeClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiResourceId");
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<DateTime?>("Expiration");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(4000);
b.HasKey("Id");
b.HasIndex("ApiResourceId");
b.ToTable("ApiSecrets");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("AbsoluteRefreshTokenLifetime");
b.Property<int>("AccessTokenLifetime");
b.Property<int>("AccessTokenType");
b.Property<bool>("AllowAccessTokensViaBrowser");
b.Property<bool>("AllowOfflineAccess");
b.Property<bool>("AllowPlainTextPkce");
b.Property<bool>("AllowRememberConsent");
b.Property<bool>("AlwaysIncludeUserClaimsInIdToken");
b.Property<bool>("AlwaysSendClientClaims");
b.Property<int>("AuthorizationCodeLifetime");
b.Property<bool>("BackChannelLogoutSessionRequired");
b.Property<string>("BackChannelLogoutUri")
.HasMaxLength(2000);
b.Property<string>("ClientClaimsPrefix")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<string>("ClientName")
.HasMaxLength(200);
b.Property<string>("ClientUri")
.HasMaxLength(2000);
b.Property<int?>("ConsentLifetime");
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<bool>("EnableLocalLogin");
b.Property<bool>("Enabled");
b.Property<bool>("FrontChannelLogoutSessionRequired");
b.Property<string>("FrontChannelLogoutUri")
.HasMaxLength(2000);
b.Property<int>("IdentityTokenLifetime");
b.Property<bool>("IncludeJwtId");
b.Property<DateTime?>("LastAccessed");
b.Property<string>("LogoUri")
.HasMaxLength(2000);
b.Property<string>("PairWiseSubjectSalt")
.HasMaxLength(200);
b.Property<string>("ProtocolType")
.IsRequired()
.HasMaxLength(200);
b.Property<int>("RefreshTokenExpiration");
b.Property<int>("RefreshTokenUsage");
b.Property<bool>("RequireClientSecret");
b.Property<bool>("RequireConsent");
b.Property<bool>("RequirePkce");
b.Property<int>("SlidingRefreshTokenLifetime");
b.Property<bool>("UpdateAccessTokenClaimsOnRefresh");
b.Property<DateTime?>("Updated");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("Clients");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(250);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Origin")
.IsRequired()
.HasMaxLength(150);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientCorsOrigins");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("GrantType")
.IsRequired()
.HasMaxLength(250);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientGrantTypes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientIdPRestrictions");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("PostLogoutRedirectUri")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientPostLogoutRedirectUris");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientProperties");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("RedirectUri")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientRedirectUris");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<string>("Scope")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientScopes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(2000);
b.Property<DateTime?>("Expiration");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(4000);
b.HasKey("Id");
b.HasIndex("ClientId");
b.ToTable("ClientSecrets");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("IdentityResourceId");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("IdentityResourceId");
b.ToTable("IdentityClaims");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("Created");
b.Property<string>("Description")
.HasMaxLength(1000);
b.Property<string>("DisplayName")
.HasMaxLength(200);
b.Property<bool>("Emphasize");
b.Property<bool>("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.Property<bool>("Required");
b.Property<bool>("ShowInDiscoveryDocument");
b.Property<DateTime?>("Updated");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("IdentityResources");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("IdentityResourceId");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(250);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2000);
b.HasKey("Id");
b.HasIndex("IdentityResourceId");
b.ToTable("IdentityProperties");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("UserClaims")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("Properties")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("Scopes")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope")
.WithMany("UserClaims")
.HasForeignKey("ApiScopeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
.WithMany("Secrets")
.HasForeignKey("ApiResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("Claims")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("AllowedCorsOrigins")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("AllowedGrantTypes")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("IdentityProviderRestrictions")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("PostLogoutRedirectUris")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("Properties")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("RedirectUris")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("AllowedScopes")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
.WithMany("ClientSecrets")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
.WithMany("UserClaims")
.HasForeignKey("IdentityResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
{
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
.WithMany("Properties")
.HasForeignKey("IdentityResourceId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,57 @@
// <auto-generated />
using System;
using IdentityServer4.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.PersistedGrantDb
{
[DbContext(typeof(PersistedGrantDbContext))]
[Migration("20180930040529_InitialIdentityServerPersistedGrantDbMigration")]
partial class InitialIdentityServerPersistedGrantDbMigration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.PersistedGrantDb
{
public partial class InitialIdentityServerPersistedGrantDbMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(maxLength: 200, nullable: false),
Type = table.Column<string>(maxLength: 50, nullable: false),
SubjectId = table.Column<string>(maxLength: 200, nullable: true),
ClientId = table.Column<string>(maxLength: 200, nullable: false),
CreationTime = table.Column<DateTime>(nullable: false),
Expiration = table.Column<DateTime>(nullable: true),
Data = table.Column<string>(maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PersistedGrants");
}
}
}

View File

@ -0,0 +1,55 @@
// <auto-generated />
using System;
using IdentityServer4.EntityFramework.DbContexts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Teknik.IdentityServer.Data.Migrations.IdentityServer.PersistedGrantDb
{
[DbContext(typeof(PersistedGrantDbContext))]
partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200);
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200);
b.Property<DateTime>("CreationTime");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000);
b.Property<DateTime?>("Expiration");
b.Property<string>("SubjectId")
.HasMaxLength(200);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50);
b.HasKey("Key");
b.HasIndex("SubjectId", "ClientId", "Type");
b.ToTable("PersistedGrants");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<AssemblyName>Teknik.IdentityServer</AssemblyName>
<RootNamespace>Teknik.IdentityServer</RootNamespace>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers>
<Configurations>Debug;Release;Test</Configurations>
</PropertyGroup>
<ItemGroup>
<Folder Include="Middleware\" />
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="2.3.0-preview1-update2" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.7.0-preview1" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="2.3.0-preview1-update2" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="2.3.0-preview1-update1" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.5" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Configuration\Configuration.csproj" />
<ProjectReference Include="..\Logging\Logging.csproj" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="263.292px" height="72.13px" viewBox="0 0 263.292 72.13" enable-background="new 0 0 263.292 72.13" xml:space="preserve">
<path d="M6.386,37.689c0-3.666-2.722-6.7-6.386-7.538V22.3h6.386V10.784h11.62V22.3h18.217v10.363h-22.09v3.561
c2.513,0.732,3.873,2.93,3.873,5.863v19.574h18.217V72.13H11.515c-2.616,0-5.129-2.197-5.129-5.234V37.689z"/>
<path d="M130.21,61.662l-15.599-16.121l12.04-12.877h7.118V22.3h-8.689c-2.512,0-4.083,0.942-5.129,1.989l-15.39,16.854h-2.407V5.13
c0-2.512-2.199-5.13-5.13-5.13H84.147v10.47h6.386v51.192h-5.895h-0.596H50.317V49.728h4.188c0.523,2.721,2.513,3.664,5.024,3.664
h20.31c3.036,0,5.235-2.199,5.235-5.236V27.638c0-3.035-2.199-5.338-5.235-5.338H44.036c-2.722,0-5.34,2.303-5.34,5.338v39.258
c0,3.037,2.618,5.234,5.34,5.234h40.007h1.03h11.951c2.931,0,5.13-2.197,5.13-5.234V50.984h3.035l17.798,19.264
c1.569,1.465,3.036,1.883,5.024,1.883h5.758V61.662H130.21z M50.317,32.663h23.135v10.366H50.317V32.663z"/>
<polygon points="203.416,0 196.745,0 191.794,0 191.794,12.772 203.416,12.772 "/>
<path d="M199.955,61.662v-3.453c2.617-0.629,3.873-2.723,3.873-4.816V27.638c0-3.035-2.304-5.338-5.13-5.338h-12.98v10.363h6.489
v24.184c0,3.559-2.512,3.635-6.489,4.682V72.13h24.602H223.3c2.932,0,5.13-2.197,5.13-5.234V50.984h3.035l17.798,19.264
c1.57,1.465,3.036,1.883,5.024,1.883h9.004V61.662h-6.805l-15.6-16.121l12.04-12.877h7.119V22.3h-8.69
c-2.512,0-4.082,0.942-5.129,1.989l-15.39,16.854h-2.407V5.13c0-2.512-2.198-5.13-5.13-5.13h-12.877v10.47h6.386v51.192"/>
<path d="M183.249,61.662V27.638c0-3.035-2.721-5.338-5.548-5.338h-25.754l-4.71,7.747h-0.209v-2.514
c0-2.093-1.676-5.233-5.444-5.233l-5.34-0.135v10.498v21.59v2.594V72.13h18.217V61.662h-10.469v-3.453
c2.617-0.629,3.873-2.723,3.873-4.816V40.201c0-4.396,3.455-7.538,7.852-7.538h15.912v34.233c0,1.289,0.435,2.428,1.151,3.311
c0.97,1.197,2.458,1.924,4.084,1.924h6.381L183.249,61.662L183.249,61.662z"/>
<rect x="171.628" y="54.419" width="11.62" height="6.25"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="263.292px" height="72.13px" viewBox="0 0 263.292 72.13" enable-background="new 0 0 263.292 72.13" xml:space="preserve">
<path fill="#418BCA" d="M6.386,37.689c0-3.666-2.722-6.7-6.386-7.538V22.3h6.386V10.784h11.62V22.3h18.217v10.363h-22.09v3.561
c2.513,0.732,3.873,2.93,3.873,5.863v19.574h18.217V72.13H11.515c-2.616,0-5.129-2.197-5.129-5.234V37.689z"/>
<path fill="#418BCA" d="M130.21,61.662l-15.599-16.121l12.04-12.877h7.118V22.3h-8.689c-2.512,0-4.083,0.942-5.129,1.989
l-15.39,16.854h-2.407V5.13c0-2.512-2.199-5.13-5.13-5.13H84.147v10.47h6.386v51.192h-5.895h-0.596H50.317V49.728h4.188
c0.523,2.721,2.513,3.664,5.024,3.664h20.31c3.036,0,5.235-2.199,5.235-5.236V27.638c0-3.035-2.199-5.338-5.235-5.338H44.036
c-2.722,0-5.34,2.303-5.34,5.338v39.258c0,3.037,2.618,5.234,5.34,5.234h40.007h1.03h11.951c2.931,0,5.13-2.197,5.13-5.234V50.984
h3.035l17.798,19.264c1.569,1.465,3.036,1.883,5.024,1.883h5.758V61.662H130.21z M50.317,32.663h23.135v10.366H50.317V32.663z"/>
<polygon fill="#418BCA" points="203.416,0 196.745,0 191.794,0 191.794,12.772 203.416,12.772 "/>
<path fill="#418BCA" d="M199.955,61.662v-3.453c2.617-0.629,3.873-2.723,3.873-4.816V27.638c0-3.035-2.304-5.338-5.13-5.338h-12.98
v10.363h6.489v24.184c0,3.559-2.512,3.635-6.489,4.682V72.13h24.602H223.3c2.932,0,5.13-2.197,5.13-5.234V50.984h3.035
l17.798,19.264c1.57,1.465,3.036,1.883,5.024,1.883h9.004V61.662h-6.805l-15.6-16.121l12.04-12.877h7.119V22.3h-8.69
c-2.512,0-4.082,0.942-5.129,1.989l-15.39,16.854h-2.407V5.13c0-2.512-2.198-5.13-5.13-5.13h-12.877v10.47h6.386v51.192"/>
<path fill="#418BCA" d="M183.249,61.662V27.638c0-3.035-2.721-5.338-5.548-5.338h-25.754l-4.71,7.747h-0.209v-2.514
c0-2.093-1.676-5.233-5.444-5.233l-5.34-0.135v10.498v21.59v2.594V72.13h18.217V61.662h-10.469v-3.453
c2.617-0.629,3.873-2.723,3.873-4.816V40.201c0-4.396,3.455-7.538,7.852-7.538h15.912v34.233c0,1.289,0.435,2.428,1.151,3.311
c0.97,1.197,2.458,1.924,4.084,1.924h6.381L183.249,61.662L183.249,61.662z"/>
<rect x="171.628" y="54.419" fill="#418BCA" width="11.62" height="6.25"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,118 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.IdentityServer.Middleware
{
public class BlacklistMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
public BlacklistMiddleware(RequestDelegate next, IMemoryCache cache)
{
_next = next;
_cache = cache;
}
public async Task Invoke(HttpContext context, Config config)
{
// Beggining of Request
bool blocked = false;
string blockReason = string.Empty;
#region Detect Blacklisted IPs
if (!blocked)
{
string IPAddr = context.Request.HttpContext.Connection.RemoteIpAddress.ToString();
if (!string.IsNullOrEmpty(IPAddr))
{
StringDictionary badIPs = GetFileData(context, "BlockedIPs", config.IPBlacklistFile);
blocked |= (badIPs != null && badIPs.ContainsKey(IPAddr));
blockReason = $"This IP address ({IPAddr}) has been blacklisted. If you feel this is in error, please contact support@teknik.io for assistance.";
}
}
#endregion
#region Detect Blacklisted Referrers
if (!blocked)
{
string referrer = context.Request.Headers["Referer"].ToString();
if (!string.IsNullOrEmpty(referrer))
{
StringDictionary badReferrers = GetFileData(context, "BlockedReferrers", config.ReferrerBlacklistFile);
blocked |= (badReferrers != null && badReferrers.ContainsKey(referrer));
blockReason = $"This referrer ({referrer}) has been blacklisted. If you feel this is in error, please contact support@teknik.io for assistance.";
}
}
#endregion
if (blocked)
{
// Clear the response
context.Response.Clear();
string jsonResult = JsonConvert.SerializeObject(new { error = new { type = "Blacklist", message = blockReason } });
await context.Response.WriteAsync(jsonResult);
return;
}
await _next.Invoke(context);
// End of request
}
public StringDictionary GetFileData(HttpContext context, string key, string filePath)
{
StringDictionary data;
if (!_cache.TryGetValue(key, out data))
{
data = GetFileLines(filePath);
_cache.Set(key, data);
}
return data;
}
public StringDictionary GetFileLines(string configPath)
{
StringDictionary retval = new StringDictionary();
if (File.Exists(configPath))
{
using (StreamReader sr = new StreamReader(configPath))
{
String line;
while ((line = sr.ReadLine()) != null)
{
line = line.Trim();
if (line.Length != 0)
{
retval.Add(line, null);
}
}
}
}
return retval;
}
}
public static class BlacklistMiddlewareExtensions
{
public static IApplicationBuilder UseBlacklist(this IApplicationBuilder builder)
{
return builder.UseMiddleware<BlacklistMiddleware>();
}
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Teknik.Configuration;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Middleware
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class CORSMiddleware
{
private readonly RequestDelegate _next;
public CORSMiddleware(RequestDelegate next)
{
_next = next;
}
public Task InvokeAsync(HttpContext httpContext, Config config)
{
// Allow this domain, or everything if local
string origin = (httpContext.Request.IsLocal()) ? "*" : httpContext.Request.Headers["Origin"].ToString();
// Is the referrer set to the CDN and we are using a CDN?
if (config.UseCdn && !string.IsNullOrEmpty(config.CdnHost))
{
try
{
string host = httpContext.Request.Headers["Host"];
Uri uri = new Uri(config.CdnHost);
if (host == uri.Host)
origin = host;
}
catch { }
}
string domain = (string.IsNullOrEmpty(origin)) ? string.Empty : origin.GetDomain();
if (string.IsNullOrEmpty(origin))
{
string host = httpContext.Request.Headers["Host"];
string sub = host.GetSubdomain();
origin = (string.IsNullOrEmpty(sub)) ? config.Host : sub + "." + config.Host;
}
else
{
if (domain != config.Host)
{
string sub = origin.GetSubdomain();
origin = (string.IsNullOrEmpty(sub)) ? config.Host : sub + "." + config.Host;
}
}
httpContext.Response.Headers.Append("Access-Control-Allow-Origin", origin);
httpContext.Response.Headers.Append("Vary", "Origin");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CORSMiddlewareExtensions
{
public static IApplicationBuilder UseCORS(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CORSMiddleware>();
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Teknik.Configuration;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Middleware
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class CSPMiddleware
{
private readonly RequestDelegate _next;
public CSPMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext, Config config)
{
if (!httpContext.Request.IsLocal())
{
// Default to nothing allowed
string allowedDomain = "'none'";
// Allow this domain
string host = httpContext.Request.Headers["Host"];
if (!string.IsNullOrEmpty(host))
{
allowedDomain = host;
}
var csp = "default-src 'self';" +
"img-src * 'self' data: https:;" +
$"style-src 'self' {allowedDomain};" +
$"font-src 'self' {allowedDomain};" +
$"script-src 'self' 'unsafe-inline' {allowedDomain};";
if (!httpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
{
httpContext.Response.Headers.Add("Content-Security-Policy", csp);
}
// and once again for IE
if (!httpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
{
httpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
}
}
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CSPMiddlewareExtensions
{
public static IApplicationBuilder UseCSP(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CSPMiddleware>();
}
}
}

View File

@ -0,0 +1,105 @@
using IdentityServer4.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.IdentityServer.Controllers;
using Teknik.Logging;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Middleware
{
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly IRouter _router;
public ErrorHandlerMiddleware(RequestDelegate next, IRouter router)
{
_next = next;
_router = router;
}
public async Task Invoke(HttpContext httpContext, ILogger<Logger> logger, Config config, IIdentityServerInteractionService interaction)
{
var statusCodeFeature = new StatusCodePagesFeature();
httpContext.Features.Set<IStatusCodePagesFeature>(statusCodeFeature);
Exception exception = null;
try
{
await _next(httpContext);
}
catch (Exception ex)
{
httpContext.Response.StatusCode = 500;
exception = ex;
}
if (!statusCodeFeature.Enabled)
{
// Check if the feature is still available because other middleware (such as a web API written in MVC) could
// have disabled the feature to prevent HTML status code responses from showing up to an API client.
return;
}
// Do nothing if a response body has already been provided or not 404 response
if (httpContext.Response.HasStarted)
{
return;
}
// Detect if there is a response code or exception occured
if ((httpContext.Response.StatusCode >= 400 && httpContext.Response.StatusCode <= 600) || exception != null)
{
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Routers.Add(_router);
var context = new ControllerContext();
context.HttpContext = httpContext;
context.RouteData = routeData;
context.ActionDescriptor = new Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor();
ErrorController errorController = new ErrorController(logger, config, interaction);
errorController.ControllerContext = context;
if (httpContext.Response.StatusCode == 500)
{
await errorController.Http500(exception).ExecuteResultAsync(context);
}
else
{
await errorController.HttpError(httpContext.Response.StatusCode).ExecuteResultAsync(context);
}
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SetupErrorHandlerMiddlewareExtensions
{
public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder builder, Config config)
{
var routes = new RouteBuilder(builder)
{
DefaultHandler = builder.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
return builder.UseMiddleware<ErrorHandlerMiddleware>(routes.Build());
}
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Teknik.Configuration;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Middleware
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class PerformanceMonitorMiddleware
{
private readonly RequestDelegate _next;
public PerformanceMonitorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, Config config)
{
Stopwatch timer = new Stopwatch();
timer.Start();
httpContext.Response.OnStarting(state =>
{
var context = (HttpContext)state;
timer.Stop();
double ms = (double)timer.ElapsedMilliseconds;
string result = string.Format("{0:F0}", ms);
if (!httpContext.Response.Headers.IsReadOnly)
httpContext.Response.Headers.Add("GenerationTime", result);
return Task.CompletedTask;
}, httpContext);
await _next(httpContext);
// Don't interfere with non-HTML responses
if (httpContext.Response.ContentType != null && httpContext.Response.ContentType.StartsWith("text/html") && httpContext.Response.StatusCode == 200 && !httpContext.Request.IsAjaxRequest())
{
double ms = (double)timer.ElapsedMilliseconds;
string result = string.Format("{0:F0}", ms);
await httpContext.Response.WriteAsync(
"<script nonce=\"" + httpContext.Items[Constants.NONCE_KEY] + "\">" +
"var pageGenerationTime = '" + result + "';" +
"pageloadStopTimer();" +
"</script >");
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class PerformanceMonitorMiddlewareExtensions
{
public static IApplicationBuilder UsePerformanceMonitor(this IApplicationBuilder builder)
{
return builder.UseMiddleware<PerformanceMonitorMiddleware>();
}
}
}

View File

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.IdentityServer.Middleware
{
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext, Config config)
{
IHeaderDictionary headers = httpContext.Response.Headers;
// Access Control
headers.Append("Access-Control-Allow-Credentials", "true");
headers.Append("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
headers.Append("Access-Control-Allow-Headers", "Authorization, Accept, Origin, Content-Type, X-Requested-With, Connection, Transfer-Encoding");
// HSTS
headers.Append("strict-transport-security", "max-age=31536000; includeSubdomains; preload");
// XSS Protection
headers.Append("X-XSS-Protection", "1; mode=block");
// Content Type Options
headers.Append("X-Content-Type-Options", "nosniff");
// Public Key Pinning
string keys = string.Empty;
foreach (string key in config.PublicKeys)
{
keys += $"pin-sha256=\"{key}\";";
}
headers.Append("Public-Key-Pins", $"max-age=300; includeSubDomains; {keys}");
// Referrer Policy
headers.Append("Referrer-Policy", "no-referrer, strict-origin-when-cross-origin");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SecurityHeadersMiddlewareExtensions
{
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SecurityHeadersMiddleware>();
}
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Middleware
{
public class SetupHttpContextMiddleware
{
private readonly RequestDelegate _next;
public SetupHttpContextMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
// Generate the NONCE used for this request
string nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringHelper.RandomString(24)));
httpContext.Items[Constants.NONCE_KEY] = nonce;
await _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SetupHttpContextMiddlewareExtensions
{
public static IApplicationBuilder UseHttpContextSetup(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SetupHttpContextMiddleware>();
}
}
}

View File

@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Models
{
public class ApplicationUser : IdentityUser
{
public DateTime CreationDate { get; set; }
public DateTime LastSeen { get; set; }
public AccountType AccountType { get; set; }
public AccountStatus AccountStatus { get; set; }
public string PGPPublicKey { get; set; }
public ApplicationUser() : base()
{
Init();
}
public ApplicationUser(string userName) : base(userName)
{
Init();
}
private void Init()
{
CreationDate = DateTime.Now;
LastSeen = DateTime.Now;
AccountType = AccountType.Basic;
AccountStatus = AccountStatus.Active;
PGPPublicKey = null;
}
public List<Claim> ToClaims()
{
var claims = new List<Claim>();
claims.Add(new Claim("username", UserName));
claims.Add(new Claim("creation-date", CreationDate.ToString("o")));
claims.Add(new Claim("last-seen", LastSeen.ToString("o")));
claims.Add(new Claim("account-type", AccountType.ToString()));
claims.Add(new Claim("account-status", AccountStatus.ToString()));
claims.Add(new Claim("recovery-email", Email ?? string.Empty));
claims.Add(new Claim("recovery-verified", EmailConfirmed.ToString()));
claims.Add(new Claim("2fa-enabled", TwoFactorEnabled.ToString()));
claims.Add(new Claim("pgp-public-key", PGPPublicKey ?? string.Empty));
return claims;
}
public JObject ToJson()
{
return new JObject()
{
new JProperty("username", UserName),
new JProperty("creation-date", CreationDate),
new JProperty("last-seen", LastSeen),
new JProperty("account-type", AccountType),
new JProperty("account-status", AccountStatus),
new JProperty("recovery-email", Email),
new JProperty("recovery-verified", EmailConfirmed),
new JProperty("2fa-enabled", TwoFactorEnabled),
new JProperty("pgp-public-key", PGPPublicKey)
};
}
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Teknik.IdentityServer.Models
{
public class ConsentInputModel
{
public string Button { get; set; }
public IEnumerable<string> ScopesConsented { get; set; }
public bool RememberConsent { get; set; }
public string ReturnUrl { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace Teknik.IdentityServer.Models
{
public class LoginInputModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace Teknik.IdentityServer.Models
{
public class LogoutInputModel
{
public string LogoutId { get; set; }
public string ReturnURL { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class CheckPasswordModel
{
public string Username { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class CreateClientModel
{
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class DeleteUserModel
{
public string Username { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class Disable2FAModel
{
public string Username { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class Enable2FAModel
{
public string Username { get; set; }
public string Code { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class GeneratePasswordResetTokenModel
{
public string Username { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class GenerateRecoveryCodesModel
{
public string Username { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Models.Manage
{
public class NewUserModel
{
public string Username { get; set; }
public string Password { get; set; }
public AccountType AccountType { get; set; }
public AccountStatus AccountStatus { get; set; }
public string RecoveryEmail { get; set; }
public bool RecoveryVerified { get; set; }
public string PGPPublicKey { get; set; }
public NewUserModel()
{
AccountType = AccountType.Basic;
AccountStatus = AccountStatus.Active;
}
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class Reset2FAKeyModel
{
public string Username { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class ResetPasswordModel
{
public string Username { get; set; }
public string Token { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Models.Manage
{
public class UpdateAccountStatusModel
{
public string Username { get; set; }
public AccountStatus AccountStatus { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Models.Manage
{
public class UpdateAccountTypeModel
{
public string Username { get; set; }
public AccountType AccountType { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class UpdateEmailModel
{
public string Username { get; set; }
public string Email { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class UpdateEmailVerifiedModel
{
public string Username { get; set; }
public bool Verified { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Models.Manage
{
public class UpdatePGPPublicKeyModel
{
public string Username { get; set; }
public string PGPPublicKey { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class UpdatePasswordModel
{
public string Username { get; set; }
public string CurrentPassword { get; set; }
public string NewPassword { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class VerifyEmailModel
{
public string Username { get; set; }
public string Token { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using Teknik.IdentityServer.ViewModels;
namespace Teknik.IdentityServer.Models
{
public class ProcessConsentResult
{
public bool IsRedirect => RedirectUri != null;
public string RedirectUri { get; set; }
public bool ShowView => ViewModel != null;
public ConsentViewModel ViewModel { get; set; }
public bool HasValidationError => ValidationError != null;
public string ValidationError { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace Teknik.IdentityServer.Options
{
public class AccountOptions
{
public static bool AllowLocalLogin = true;
public static bool AllowRememberLogin = true;
public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30);
public static bool ShowLogoutPrompt = true;
public static bool AutomaticRedirectAfterSignOut = true;
public static string InvalidCredentialsErrorMessage = "Invalid username or password";
}
}

View File

@ -0,0 +1,12 @@
namespace Teknik.IdentityServer.Options
{
public class ConsentOptions
{
public static bool EnableOfflineAccess = true;
public static string OfflineAccessDisplayName = "Offline Access";
public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline";
public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission";
public static readonly string InvalidSelectionErrorMessage = "Invalid selection";
}
}

27
IdentityServer/Program.cs Normal file
View File

@ -0,0 +1,27 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace Teknik.IdentityServer
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.AddCommandLine(args)
.Build();
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
}
}
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish>https://authdev.teknik.io</SiteUrlToLaunchAfterPublish>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<TargetFramework>netcoreapp2.1</TargetFramework>
<ProjectGuid>05842e03-223a-4f43-9e81-d968a9475a97</ProjectGuid>
<SelfContained>false</SelfContained>
<_IsPortable>true</_IsPortable>
<MSDeployServiceURL>ams1.teknik.io</MSDeployServiceURL>
<DeployIisAppPath>TeknikIdentityDev</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
<EnableMSDeployBackup>True</EnableMSDeployBackup>
<UserName>Administrator</UserName>
<_SavePWD>True</_SavePWD>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,35 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:44350/",
"sslPort": 44350
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IdentityServer": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:6002/"
},
"IdentityServer - Prod": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
},
"applicationUrl": "https://localhost:6002/"
}
}
}

View File

@ -0,0 +1,49 @@
$(document).ready(function () {
$('#submitErrorReport').click(function () {
bootbox.prompt({
title: "Please enter any additional information that could help us",
inputType: 'textarea',
callback: function (result) {
if (result) {
errorMsg = $("#errorMsg").html();
$.ajax({
type: "POST",
url: submitErrorReportURL,
data: AddAntiForgeryToken({
Message: result,
Exception: errorMsg,
CurrentUrl: window.location.href
}),
success: function (response) {
if (response.result) {
$("#top_msg").css('display', 'inline', 'important');
$("#top_msg").html(
'<div class="alert alert-info alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>Thank you for your help! Feedback has been submitted.</div>');
} else {
$("#top_msg").css('display', 'inline', 'important');
$("#top_msg")
.html(
'<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' +
parseErrorMessage(response) +
'</div>');
}
}
});
}
}
});
});
$(".view-details-button").on("click",
function () {
var link = $(this);
var linkText = link.text().toUpperCase();
if (linkText === "SHOW DETAILS") {
link.text("Hide Details");
} else {
link.text("Show Details");
};
}
);
});

View File

@ -0,0 +1,6 @@
window.addEventListener("load", function () {
var a = document.querySelector("a.PostLogoutRedirectUri");
if (a) {
window.location = a.href;
}
});

View File

@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
using Microsoft.Extensions.Identity.Core;
using Microsoft.Extensions.Options;
using Teknik.Utilities.Cryptography;
using Teknik.Utilities;
using System.Text;
using Teknik.IdentityServer.Models;
namespace Teknik.IdentityServer.Security
{
public class TeknikPasswordHasher : PasswordHasher<ApplicationUser>
{
private readonly Config _config;
public TeknikPasswordHasher(Config config)
{
_config = config;
}
public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPassword, string providedPassword)
{
if (hashedPassword == null)
{
throw new ArgumentNullException(nameof(hashedPassword));
}
if (providedPassword == null)
{
throw new ArgumentNullException(nameof(providedPassword));
}
// Test legacy password hashes
#region Legacy Checks
byte[] hashBytes = SHA384.Hash(user.UserName, providedPassword);
string hash = hashBytes.ToHex();
if (hashedPassword == hash)
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
hash = Encoding.ASCII.GetString(hashBytes);
if (hashedPassword == hash)
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
hash = SHA256.Hash(providedPassword, _config.Salt1, _config.Salt2);
if (hashedPassword == hash)
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
#endregion
// Test Latest
return base.VerifyHashedPassword(user, hashedPassword, providedPassword);
}
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Teknik.IdentityServer.Security
{
public class SecurityHeadersAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result;
if (result is ViewResult)
{
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
{
context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
}
if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
{
context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
}
var csp = "default-src 'self';";
// an example if you need client images to be displayed from twitter
//var csp = "default-src 'self'; img-src 'self' https://pbs.twimg.com";
// once for standards compliant browsers
if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
{
context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
}
// and once again for IE
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
{
context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
}
}
}
}
}

View File

@ -0,0 +1,60 @@
using IdentityServer4.Models;
using IdentityServer4.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.IdentityServer.Security
{
public class TeknikRedirectUriValidator : IRedirectUriValidator
{
private readonly Config _config;
public TeknikRedirectUriValidator(Config config)
{
_config = config;
}
public async Task<bool> IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client)
{
if (client.PostLogoutRedirectUris != null && client.PostLogoutRedirectUris.Any())
{
if (client.PostLogoutRedirectUris.Contains(requestedUri))
return true;
}
// Add special case for pre-configured redirect URIs
if (client.ClientId == _config.UserConfig.IdentityServerConfig.ClientId &&
_config.UserConfig.IdentityServerConfig.PostLogoutRedirectUris != null &&
_config.UserConfig.IdentityServerConfig.PostLogoutRedirectUris.Any())
{
if (_config.UserConfig.IdentityServerConfig.PostLogoutRedirectUris.Contains(requestedUri))
return true;
}
return false;
}
public async Task<bool> IsRedirectUriValidAsync(string requestedUri, Client client)
{
if (client.RedirectUris != null && client.RedirectUris.Any())
{
if (client.RedirectUris.Contains(requestedUri))
return true;
}
// Add special case for pre-configured redirect URIs
if (client.ClientId == _config.UserConfig.IdentityServerConfig.ClientId &&
_config.UserConfig.IdentityServerConfig.RedirectUris != null &&
_config.UserConfig.IdentityServerConfig.RedirectUris.Any())
{
if (_config.UserConfig.IdentityServerConfig.RedirectUris.Contains(requestedUri))
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,107 @@
using IdentityModel;
using IdentityServer4.Extensions;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Threading.Tasks;
using Teknik.IdentityServer.Models;
using Teknik.IdentityServer.Options;
using Teknik.IdentityServer.ViewModels;
namespace Teknik.IdentityServer.Services
{
public class AccountService
{
private readonly IClientStore _clientStore;
private readonly IIdentityServerInteractionService _interaction;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthenticationSchemeProvider _schemeProvider;
public AccountService(
IIdentityServerInteractionService interaction,
IHttpContextAccessor httpContextAccessor,
IAuthenticationSchemeProvider schemeProvider,
IClientStore clientStore)
{
_interaction = interaction;
_httpContextAccessor = httpContextAccessor;
_schemeProvider = schemeProvider;
_clientStore = clientStore;
}
public async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
{
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
var allowLocal = true;
if (context?.ClientId != null)
{
var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId);
if (client != null)
{
allowLocal = client.EnableLocalLogin;
}
}
return new LoginViewModel
{
AllowRememberLogin = AccountOptions.AllowRememberLogin,
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
ReturnUrl = returnUrl,
Username = context?.LoginHint
};
}
public async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model)
{
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
vm.Username = model.Username;
vm.RememberMe = model.RememberMe;
return vm;
}
public async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
{
var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };
var user = _httpContextAccessor.HttpContext.User;
if (user?.Identity.IsAuthenticated != true)
{
// if the user is not authenticated, then just show logged out page
vm.ShowLogoutPrompt = false;
return vm;
}
var context = await _interaction.GetLogoutContextAsync(logoutId);
if (context?.ShowSignoutPrompt == false)
{
// it's safe to automatically sign-out
vm.ShowLogoutPrompt = false;
return vm;
}
// show the logout prompt. this prevents attacks where the user
// is automatically signed out by another malicious web page.
return vm;
}
public async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interaction.GetLogoutContextAsync(logoutId);
var vm = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl,
LogoutId = logoutId
};
return vm;
}
}
}

View File

@ -0,0 +1,190 @@
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
using Teknik.IdentityServer.Models;
using Teknik.IdentityServer.Options;
using Teknik.IdentityServer.ViewModels;
namespace Teknik.IdentityServer.Services
{
public class ConsentService
{
private readonly IClientStore _clientStore;
private readonly IResourceStore _resourceStore;
private readonly IIdentityServerInteractionService _interaction;
private readonly ILogger _logger;
public ConsentService(
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IResourceStore resourceStore,
ILogger logger)
{
_interaction = interaction;
_clientStore = clientStore;
_resourceStore = resourceStore;
_logger = logger;
}
public async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model)
{
var result = new ProcessConsentResult();
ConsentResponse grantedConsent = null;
// user clicked 'no' - send back the standard 'access_denied' response
if (model.Button == "no")
{
grantedConsent = ConsentResponse.Denied;
}
// user clicked 'yes' - validate the data
else if (model.Button == "yes" && model != null)
{
// if the user consented to some scope, build the response model
if (model.ScopesConsented != null && model.ScopesConsented.Any())
{
var scopes = model.ScopesConsented;
if (ConsentOptions.EnableOfflineAccess == false)
{
scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
}
grantedConsent = new ConsentResponse
{
RememberConsent = model.RememberConsent,
ScopesConsented = scopes.ToArray()
};
}
else
{
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
}
}
else
{
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
}
if (grantedConsent != null)
{
// validate return url is still valid
var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (request == null) return result;
// communicate outcome of consent back to identityserver
await _interaction.GrantConsentAsync(request, grantedConsent);
// indicate that's it ok to redirect back to authorization endpoint
result.RedirectUri = model.ReturnUrl;
}
else
{
// we need to redisplay the consent UI
result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
}
return result;
}
public async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, ConsentInputModel model = null)
{
var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (request != null)
{
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
if (client != null)
{
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
{
return CreateConsentViewModel(model, returnUrl, request, client, resources);
}
else
{
_logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y));
}
}
else
{
_logger.LogError("Invalid client id: {0}", request.ClientId);
}
}
else
{
_logger.LogError("No consent request matching request: {0}", returnUrl);
}
return null;
}
private ConsentViewModel CreateConsentViewModel(
ConsentInputModel model, string returnUrl,
AuthorizationRequest request,
Client client, Resources resources)
{
var vm = new ConsentViewModel();
vm.RememberConsent = model?.RememberConsent ?? true;
vm.ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
vm.ReturnUrl = returnUrl;
vm.ClientName = client.ClientName ?? client.ClientId;
vm.ClientUrl = client.ClientUri;
vm.ClientLogoUrl = client.LogoUri;
vm.AllowRememberConsent = client.AllowRememberConsent;
vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess)
{
vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] {
GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)
});
}
return vm;
}
public ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
{
return new ScopeViewModel
{
Name = identity.Name,
DisplayName = identity.DisplayName,
Description = identity.Description,
Emphasize = identity.Emphasize,
Required = identity.Required,
Checked = check || identity.Required
};
}
public ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
{
return new ScopeViewModel
{
Name = scope.Name,
DisplayName = scope.DisplayName,
Description = scope.Description,
Emphasize = scope.Emphasize,
Required = scope.Required,
Checked = check || scope.Required
};
}
private ScopeViewModel GetOfflineAccessScope(bool check)
{
return new ScopeViewModel
{
Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess,
DisplayName = ConsentOptions.OfflineAccessDisplayName,
Description = ConsentOptions.OfflineAccessDescription,
Emphasize = true,
Checked = check
};
}
}
}

245
IdentityServer/Startup.cs Normal file
View File

@ -0,0 +1,245 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Mappers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Teknik.Configuration;
using Teknik.IdentityServer.Configuration;
using Teknik.IdentityServer.Security;
using Teknik.IdentityServer.Middleware;
using Teknik.Logging;
using Microsoft.AspNetCore.Authorization;
using Teknik.IdentityServer.Models;
using IdentityServer4.Services;
namespace Teknik.IdentityServer
{
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
Environment = env;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
string dataDir = Configuration["ConfigDirectory"];
AppDomain.CurrentDomain.SetData("DataDirectory", dataDir);
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// Create Configuration Singleton
services.AddScoped<Config, Config>(opt => Config.Load(dataDir));
// Build an intermediate service provider
var sp = services.BuildServiceProvider();
// Resolve the services from the service provider
var config = sp.GetService<Config>();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "TeknikAuth";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
options.Cookie.Expiration = TimeSpan.FromDays(30);
options.ExpireTimeSpan = TimeSpan.FromDays(30);
});
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
});
// Sessions
services.AddResponseCaching();
services.AddMemoryCache();
services.AddSession();
// Set the anti-forgery cookie name
services.AddAntiforgery(options =>
{
options.Cookie.Name = "TeknikAuthAntiForgery";
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<ApplicationDbContext>(builder =>
builder.UseSqlServer(Configuration.GetConnectionString("TeknikAuthEntities"), sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password = new PasswordOptions()
{
RequireDigit = false,
RequiredLength = 4,
RequiredUniqueChars = 1,
RequireLowercase = false,
RequireNonAlphanumeric = false,
RequireUppercase = false
};
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.ErrorUrl = "/Error/IdentityError";
options.UserInteraction.ErrorIdParameter = "errorId";
})
.AddOperationalStore(options =>
options.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("TeknikAuthEntities"), sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)))
.AddConfigurationStore(options =>
options.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("TeknikAuthEntities"), sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)))
.AddAspNetIdentity<ApplicationUser>()
.AddRedirectUriValidator<TeknikRedirectUriValidator>()
.AddDeveloperSigningCredential();
services.AddAuthorization(options =>
{
foreach (var policy in Policies.Get())
{
options.AddPolicy(policy.Name, p =>
{
foreach (var scope in policy.Scopes)
{
p.RequireScope(scope);
}
});
}
});
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = config.UserConfig.IdentityServerConfig.Authority;
options.RequireHttpsMetadata = true;
options.ApiName = "auth-api";
});
services.AddTransient<IPasswordHasher<ApplicationUser>, TeknikPasswordHasher>();
services.AddTransient<IProfileService, TeknikProfileService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, Config config)
{
// Initiate Logging
loggerFactory.AddLogger(config);
// Setup the HttpContext
app.UseHttpContextSetup();
// HttpContext Session
app.UseSession(new SessionOptions()
{
IdleTimeout = TimeSpan.FromMinutes(30),
Cookie = new CookieBuilder()
{
Domain = null,
Name = "TeknikAuthSession",
SecurePolicy = CookieSecurePolicy.Always,
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict
}
});
// Use Exception Handling
app.UseErrorHandler(config);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Custom Middleware
app.UseBlacklist();
app.UseCORS();
app.UseCSP();
app.UseSecurityHeaders();
// Cache Responses
app.UseResponseCaching();
// Force a HTTPS redirection (301)
app.UseHttpsRedirection();
// Setup static files anc cache them client side
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + 31536000;
}
});
InitializeDbTestDataAsync(app, config).Wait();
app.UseIdentityServer();
app.UseMvcWithDefaultRoute();
}
private static async System.Threading.Tasks.Task InitializeDbTestDataAsync(IApplicationBuilder app, Config config)
{
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
scope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>().Database.Migrate();
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
var context = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
if (!context.Clients.Any())
{
foreach (var client in Clients.Get(config))
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
}
if (!context.IdentityResources.Any())
{
foreach (var resource in Resources.GetIdentityResources())
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
if (!context.ApiResources.Any())
{
foreach (var resource in Resources.GetApiResources(config))
{
context.ApiResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
}
}
}
}

View File

@ -0,0 +1,49 @@
using IdentityModel;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Teknik.IdentityServer.Models;
using Teknik.Utilities;
namespace Teknik.IdentityServer
{
public class TeknikProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
public TeknikProfileService(UserManager<ApplicationUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory)
{
_userManager = userManager;
_claimsFactory = claimsFactory;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims.AddRange(user.ToClaims());
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null && user.AccountStatus == Utilities.AccountStatus.Active;
}
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
using Teknik.IdentityServer.Models;
namespace Teknik.IdentityServer.ViewModels
{
public class ConsentViewModel : ConsentInputModel
{
public string ClientName { get; set; }
public string ClientUrl { get; set; }
public string ClientLogoUrl { get; set; }
public bool AllowRememberConsent { get; set; }
public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Teknik.IdentityServer.ViewModels
{
public class ErrorViewModel
{
public string Title { get; set; }
public string Description { get; set; }
public int StatusCode { get; set; }
public Exception Exception { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace Teknik.IdentityServer.ViewModels
{
public class GrantsViewModel
{
public IEnumerable<GrantViewModel> Grants { get; set; }
}
public class GrantViewModel
{
public string ClientId { get; set; }
public string ClientName { get; set; }
public string ClientUrl { get; set; }
public string ClientLogoUrl { get; set; }
public DateTime Created { get; set; }
public DateTime? Expires { get; set; }
public IEnumerable<string> IdentityGrantNames { get; set; }
public IEnumerable<string> ApiGrantNames { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Teknik.IdentityServer.ViewModels
{
public class IdentityErrorViewModel
{
public string Title { get; set; }
public string Description { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Teknik.IdentityServer.ViewModels
{
public class LoggedOutViewModel
{
public string PostLogoutRedirectUri { get; set; }
public string ClientName { get; set; }
public string SignOutIframeUrl { get; set; }
public bool AutomaticRedirectAfterSignOut { get; set; }
public string LogoutId { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Teknik.IdentityServer.Models;
namespace Teknik.IdentityServer.ViewModels
{
public class LoginViewModel : LoginInputModel
{
public bool AllowRememberLogin { get; set; }
public bool EnableLocalLogin { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Teknik.IdentityServer.ViewModels
{
public class LoginWith2faViewModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Authenticator code")]
public string TwoFactorCode { get; set; }
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
public bool RememberMe { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Teknik.IdentityServer.ViewModels
{
public class LoginWithRecoveryCodeViewModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using Teknik.IdentityServer.Models;
namespace Teknik.IdentityServer.ViewModels
{
public class LogoutViewModel : LogoutInputModel
{
public bool ShowLogoutPrompt { get; set; }
}
}

View File

@ -0,0 +1,12 @@
namespace Teknik.IdentityServer.ViewModels
{
public class ScopeViewModel
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public bool Emphasize { get; set; }
public bool Required { get; set; }
public bool Checked { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace Teknik.IdentityServer.ViewModels
{
public class SubmitReportViewModel
{
public string Message { get; set; }
public string Exception { get; set; }
public string CurrentUrl { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.ViewModels
{
public class ViewModelBase
{
public bool Error { get; set; }
public string ErrorMessage { get; set; }
public ViewModelBase()
{
Error = false;
ErrorMessage = string.Empty;
}
}
}

View File

@ -0,0 +1,14 @@
@{
ViewData["Title"] = "Access denied";
}
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<header>
<h2 class="text-danger">@ViewData["Title"]</h2>
<p class="text-danger">You do not have access to this resource.</p>
</header>
</div>
</div>
</div>

View File

@ -0,0 +1,14 @@
@{
ViewData["Title"] = "Account Banned";
}
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<header>
<h2 class="text-danger">@ViewData["Title"]</h2>
<p class="text-danger">This account has been banned.</p>
</header>
</div>
</div>
</div>

View File

@ -0,0 +1,8 @@
@{
ViewData["Title"] = "Locked out";
}
<header>
<h2 class="text-danger">@ViewData["Title"]</h2>
<p class="text-danger">This account has been locked out, please try again later.</p>
</header>

View File

@ -0,0 +1,35 @@
@model LoggedOutViewModel
@{
// set this so the layout rendering sees an anonymous user
ViewData["signed-out"] = true;
}
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<h1>
Logout
<small>You are now logged out</small>
</h1>
@if (Model.PostLogoutRedirectUri != null)
{
<div>
Click <a class="PostLogoutRedirectUri" href="@Model.PostLogoutRedirectUri">here</a> to return to the
<span>@Model.ClientName</span> application.
</div>
}
@if (Model.SignOutIframeUrl != null)
{
<iframe width="0" height="0" class="signout" src="@Model.SignOutIframeUrl"></iframe>
}
</div>
</div>
</div>
@if (Model.AutomaticRedirectAfterSignOut)
{
<bundle src="js/signout-redirect.min.js" append-version="true"></bundle>
}

View File

@ -0,0 +1,61 @@
@model LoginViewModel
@{
string logoPath = "/images/logo-blue.svg";
// If we are using a CDN, let's replace it
if (Config.UseCdn)
{
if (!string.IsNullOrEmpty(Config.CdnHost))
{
logoPath = Config.CdnHost.TrimEnd('/') + logoPath;
}
}
}
@if (Config.UserConfig.LoginEnabled)
{
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<div class="row">
<img src="@logoPath" class="img-responsive center-block" alt="Teknik">
</div>
<br />
<div class="col-md-4 col-md-offset-4">
@await Html.PartialAsync("_ValidationSummary")
@if (Model.EnableLocalLogin)
{
<form class="form-horizontal" asp-route="Login">
<input type="hidden" asp-for="ReturnUrl" />
<div class="form-group">
<input class="form-control" placeholder="Username" asp-for="Username" autofocus>
</div>
<div class="form-group">
<input type="password" class="form-control" placeholder="Password" asp-for="Password" autocomplete="off">
</div>
@if (Model.AllowRememberLogin)
{
<div class="form-group abc-checkbox">
<input asp-for="RememberMe">
<label asp-for="RememberMe">Remember My Login</label>
</div>
}
<div class="form-group">
<button class="btn btn-primary" name="button" value="login">Log In</button>
<button class="btn btn-default" name="button" value="cancel">Cancel</button>
</div>
</form>
}
</div>
</div>
</div>
</div>
}
else
{
<h3>Authentication is currently disabled.</h3>
}

View File

@ -0,0 +1,46 @@
@model LoginWith2faViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8 col-md-offset-2 text-center">
<h1>Two-factor authentication</h1>
<hr />
<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<form method="post" asp-route-returnUrl="@ViewData["ReturnUrl"]">
<input asp-for="RememberMe" type="hidden" />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="TwoFactorCode"></label>
<input asp-for="TwoFactorCode" class="form-control" autocomplete="off" />
<span asp-validation-for="TwoFactorCode" class="text-danger"></span>
</div>
<div class="form-group">
<div class="abc-checkbox">
<input asp-for="RememberMachine" />
<label asp-for="RememberMachine">@Html.DisplayNameFor(m => m.RememberMachine)</label>
</div>
</div>
<div class="form-group pull-right">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</form>
</div>
</div>
<br />
<div class="row">
<div class="col-md-8 col-md-offset-2 text-center">
<p>
Don't have access to your authenticator device? You can
<a asp-action="LoginWithRecoveryCode" asp-route-returnUrl="@ViewData["ReturnUrl"]">log in with a recovery code</a>.
</p>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,31 @@
@model LoginWithRecoveryCodeViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8 col-md-offset-2 text-center">
<h1>Recovery code verification</h1>
<hr />
<p>
You have requested to login with a recovery code. This login will not be remembered until you provide
an authenticator app code at login or disable 2FA and login again.
</p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="RecoveryCode"></label>
<input asp-for="RecoveryCode" class="form-control" autocomplete="off" />
<span asp-validation-for="RecoveryCode" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default pull-right">Log in</button>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
@model LogoutViewModel
<div class="logout-page">
<div class="page-header">
<h1>Logout</h1>
</div>
<div class="row">
<div class="col-sm-6">
<p>Would you like to logout of IdentityServer?</p>
<form asp-action="Logout">
<input type="hidden" name="logoutId" value="@Model.LogoutId" />
<fieldset>
<div class="form-group">
<button class="btn btn-primary">Yes</button>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<bundle src="js/signout-redirect.min.js" append-version="true"></bundle>

View File

@ -0,0 +1,82 @@
@model ConsentViewModel
<div class="page-consent">
<div class="row page-header">
<div class="col-sm-10">
@if (Model.ClientLogoUrl != null)
{
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
}
<h1>
@Model.ClientName
<small>is requesting your permission</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-sm-8">
@Html.Partial("_ValidationSummary")
<form asp-action="Index" class="consent-form">
<input type="hidden" asp-for="ReturnUrl" />
<div>Uncheck the permissions you do not wish to grant.</div>
@if (Model.IdentityScopes.Any())
{
<div class="panel panel-default consent-buttons">
<div class="panel-heading">
<span class="glyphicon glyphicon-user"></span>
Personal Information
</div>
<ul class="list-group">
@foreach (var scope in Model.IdentityScopes)
{
@Html.Partial("_ScopeListItem", scope)
}
</ul>
</div>
}
@if (Model.ResourceScopes.Any())
{
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-tasks"></span>
Application Access
</div>
<ul class="list-group">
@foreach (var scope in Model.ResourceScopes)
{
@Html.Partial("_ScopeListItem", scope)
}
</ul>
</div>
}
@if (Model.AllowRememberConsent)
{
<div class="consent-remember">
<label>
<input class="consent-scopecheck" asp-for="RememberConsent" />
<strong>Remember My Decision</strong>
</label>
</div>
}
<div class="consent-buttons">
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
<button name="button" value="no" class="btn">No, Do Not Allow</button>
@if (Model.ClientUrl != null)
{
<a class="pull-right btn btn-default" target="_blank" href="@Model.ClientUrl">
<span class="glyphicon glyphicon-info-sign"></span>
<strong>@Model.ClientName</strong>
</a>
}
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
@model ScopeViewModel
<li class="list-group-item">
<label>
<input class="consent-scopecheck"
type="checkbox"
name="ScopesConsented"
id="scopes_@Model.Name"
value="@Model.Name"
checked="@Model.Checked"
disabled="@Model.Required" />
@if (Model.Required)
{
<input type="hidden"
name="ScopesConsented"
value="@Model.Name" />
}
<strong>@Model.DisplayName</strong>
@if (Model.Emphasize)
{
<span class="glyphicon glyphicon-exclamation-sign"></span>
}
</label>
@if (Model.Required)
{
<span><em>(required)</em></span>
}
@if (Model.Description != null)
{
<div class="consent-description">
<label for="scopes_@Model.Name">@Model.Description</label>
</div>
}
</li>

View File

@ -0,0 +1,47 @@
@model Teknik.IdentityServer.ViewModels.ErrorViewModel
<script>
var submitErrorReportURL = '@Url.RouteUrl("Error.Action", new { action = "SubmitErrorReport" })';
</script>
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template text-center">
<h1>Unexpected Error</h1>
<div class="error-details">
<p>Uh oh! You aren't supposed to be seeing this. Something went horribly wrong.</p>
<div class="error-actions">
@if (Model != null && Model.Exception != null)
{
<div class="btn btn-primary btn-lg" id="submitErrorReport">
<span class="fa fa-bug"></span>
Submit Bug Report
</div>
}
</div>
@if (Model != null && Model.Exception != null)
{
<br />
<div class="show-more" id="view-details">
<button role="button" class="btn btn-default btn-sm view-details-button" data-toggle="collapse" data-target="#errorMsg">Show Details</button>
</div>
<div class="text-left collapse" id="errorMsg">
<br />
<p>
<b>Exception:</b> @Model.Exception.GetFullMessage(true)
<br/>
<b>Source:</b> @Model.Exception.Source
</p>
<div style="overflow: scroll">
<pre>@Model.Exception.StackTrace</pre>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
<bundle src="js/error.min.js" append-version="true"></bundle>

View File

@ -0,0 +1,15 @@
@model Teknik.IdentityServer.ViewModels.ErrorViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template text-center">
<h2>401 Unauthorized</h2>
<div class="error-details">
The request has not been applied because it lacks valid authentication credentials for the target resource.
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
@model Teknik.IdentityServer.ViewModels.ErrorViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template text-center">
<h1>Sorry Bud!</h1>
<h2>403 Access Denied</h2>
<div class="error-details">
You do not have access to this resource. Please contact an Administrator if you think this is an error.
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
@model Teknik.IdentityServer.ViewModels.ErrorViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template text-center">
<h1>Uh Oh!</h1>
<h2>404 Not Found</h2>
<div class="error-details">
Unable to find the resource you are looking for. Please contact an Administrator if you think this is in error.
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
@model Teknik.IdentityServer.ViewModels.ErrorViewModel
<script>
var submitErrorReportURL = '@Url.RouteUrl("Error.Action", new { action = "SubmitErrorReport" })';
</script>
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template text-center">
<h1>Whoops!</h1>
<h2>500 Server Error</h2>
<div class="error-details">
<p>Sorry, a server error occured. Please contact an Administrator about this error.</p>
<div class="error-actions">
@if (Model != null && Model.Exception != null)
{
<div class="btn btn-primary btn-lg" id="submitErrorReport">
<span class="fa fa-bug"></span>
Submit Bug Report
</div>
}
</div>
@if (Model != null && Model.Exception != null)
{
<br />
<div class="show-more" id="view-details">
<button role="button" class="btn btn-default btn-sm view-details-button" data-toggle="collapse" data-target="#errorMsg">Show Details</button>
</div>
<div class="text-left collapse" id="errorMsg">
<br />
<p>
<b>Exception:</b> @Model.Exception.GetFullMessage(true)
<br/>
<b>Source:</b> @Model.Exception.Source
</p>
<div style="overflow: scroll">
<pre>@Model.Exception.StackTrace</pre>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
<bundle src="error.min.js" append-version="true"></bundle>

View File

@ -0,0 +1,16 @@
@model Teknik.IdentityServer.ViewModels.ErrorViewModel
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template text-center">
<h1>Http Error</h1>
<h2>Status Code: @Model.StatusCode</h2>
<div class="error-details">
Sorry, an error has occured: @Model.Description
</div>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More