@ -1,31 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Certificates | |||
{ | |||
static class Certificate | |||
{ | |||
public static X509Certificate2 Get() | |||
{ | |||
var assembly = typeof(Certificate).GetTypeInfo().Assembly; | |||
var names = assembly.GetManifestResourceNames(); | |||
/*********************************************************************************************** | |||
* Please note that here we are using a local certificate only for testing purposes. In a | |||
* real environment the certificate should be created and stored in a secure way, which is out | |||
* of the scope of this project. | |||
**********************************************************************************************/ | |||
using var stream = assembly.GetManifestResourceStream("Identity.API.Certificate.idsrv3test.pfx"); | |||
return new X509Certificate2(ReadStream(stream), "idsrv3test"); | |||
} | |||
private static byte[] ReadStream(Stream input) | |||
{ | |||
byte[] buffer = new byte[16 * 1024]; | |||
using MemoryStream ms = new MemoryStream(); | |||
int read; | |||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0) | |||
{ | |||
ms.Write(buffer, 0, read); | |||
} | |||
return ms.ToArray(); | |||
} | |||
} | |||
} |
@ -1,299 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers | |||
{ | |||
/// <summary> | |||
/// This sample controller implements a typical login/logout/provision workflow for local accounts. | |||
/// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! | |||
/// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval | |||
/// </summary> | |||
public class AccountController : Controller | |||
{ | |||
//private readonly InMemoryUserLoginService _loginService; | |||
private readonly ILoginService<ApplicationUser> _loginService; | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IClientStore _clientStore; | |||
private readonly ILogger<AccountController> _logger; | |||
private readonly UserManager<ApplicationUser> _userManager; | |||
private readonly IConfiguration _configuration; | |||
public AccountController( | |||
//InMemoryUserLoginService loginService, | |||
ILoginService<ApplicationUser> loginService, | |||
IIdentityServerInteractionService interaction, | |||
IClientStore clientStore, | |||
ILogger<AccountController> logger, | |||
UserManager<ApplicationUser> userManager, | |||
IConfiguration configuration) | |||
{ | |||
_loginService = loginService; | |||
_interaction = interaction; | |||
_clientStore = clientStore; | |||
_logger = logger; | |||
_userManager = userManager; | |||
_configuration = configuration; | |||
} | |||
/// <summary> | |||
/// Show login page | |||
/// </summary> | |||
[HttpGet] | |||
public async Task<IActionResult> Login(string returnUrl) | |||
{ | |||
var context = await _interaction.GetAuthorizationContextAsync(returnUrl); | |||
if (context?.IdP != null) | |||
{ | |||
throw new NotImplementedException("External login is not implemented!"); | |||
} | |||
var vm = await BuildLoginViewModelAsync(returnUrl, context); | |||
ViewData["ReturnUrl"] = returnUrl; | |||
return View(vm); | |||
} | |||
/// <summary> | |||
/// Handle postback from username/password login | |||
/// </summary> | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> Login(LoginViewModel model) | |||
{ | |||
if (ModelState.IsValid) | |||
{ | |||
var user = await _loginService.FindByUsername(model.Email); | |||
if (await _loginService.ValidateCredentials(user, model.Password)) | |||
{ | |||
var tokenLifetime = _configuration.GetValue("TokenLifetimeMinutes", 120); | |||
var props = new AuthenticationProperties | |||
{ | |||
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(tokenLifetime), | |||
AllowRefresh = true, | |||
RedirectUri = model.ReturnUrl | |||
}; | |||
if (model.RememberMe) | |||
{ | |||
var permanentTokenLifetime = _configuration.GetValue("PermanentTokenLifetimeDays", 365); | |||
props.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(permanentTokenLifetime); | |||
props.IsPersistent = true; | |||
}; | |||
await _loginService.SignInAsync(user, props); | |||
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint | |||
if (_interaction.IsValidReturnUrl(model.ReturnUrl)) | |||
{ | |||
return Redirect(model.ReturnUrl); | |||
} | |||
return Redirect("~/"); | |||
} | |||
ModelState.AddModelError("", "Invalid username or password."); | |||
} | |||
// something went wrong, show form with error | |||
var vm = await BuildLoginViewModelAsync(model); | |||
ViewData["ReturnUrl"] = model.ReturnUrl; | |||
return View(vm); | |||
} | |||
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context) | |||
{ | |||
var allowLocal = true; | |||
if (context?.Client.ClientId != null) | |||
{ | |||
var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); | |||
if (client != null) | |||
{ | |||
allowLocal = client.EnableLocalLogin; | |||
} | |||
} | |||
return new LoginViewModel | |||
{ | |||
ReturnUrl = returnUrl, | |||
Email = context?.LoginHint, | |||
}; | |||
} | |||
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginViewModel model) | |||
{ | |||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); | |||
var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context); | |||
vm.Email = model.Email; | |||
vm.RememberMe = model.RememberMe; | |||
return vm; | |||
} | |||
/// <summary> | |||
/// Show logout page | |||
/// </summary> | |||
[HttpGet] | |||
public async Task<IActionResult> Logout(string logoutId) | |||
{ | |||
if (User.Identity.IsAuthenticated == false) | |||
{ | |||
// if the user is not authenticated, then just show logged out page | |||
return await Logout(new LogoutViewModel { LogoutId = logoutId }); | |||
} | |||
//Test for Xamarin. | |||
var context = await _interaction.GetLogoutContextAsync(logoutId); | |||
if (context?.ShowSignoutPrompt == false) | |||
{ | |||
//it's safe to automatically sign-out | |||
return await Logout(new LogoutViewModel { LogoutId = logoutId }); | |||
} | |||
// show the logout prompt. this prevents attacks where the user | |||
// is automatically signed out by another malicious web page. | |||
var vm = new LogoutViewModel | |||
{ | |||
LogoutId = logoutId | |||
}; | |||
return View(vm); | |||
} | |||
/// <summary> | |||
/// Handle logout page postback | |||
/// </summary> | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> Logout(LogoutViewModel model) | |||
{ | |||
var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; | |||
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) | |||
{ | |||
if (model.LogoutId == null) | |||
{ | |||
// if there's no current logout context, we need to create one | |||
// this captures necessary info from the current logged in user | |||
// before we signout and redirect away to the external IdP for signout | |||
model.LogoutId = await _interaction.CreateLogoutContextAsync(); | |||
} | |||
string url = "/Account/Logout?logoutId=" + model.LogoutId; | |||
try | |||
{ | |||
// hack: try/catch to handle social providers that throw | |||
await HttpContext.SignOutAsync(idp, new AuthenticationProperties | |||
{ | |||
RedirectUri = url | |||
}); | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError(ex, "LOGOUT ERROR: {ExceptionMessage}", ex.Message); | |||
} | |||
} | |||
// delete authentication cookie | |||
await HttpContext.SignOutAsync(); | |||
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); | |||
// set this so UI rendering sees an anonymous user | |||
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); | |||
// get context information (client name, post logout redirect URI and iframe for federated signout) | |||
var logout = await _interaction.GetLogoutContextAsync(model.LogoutId); | |||
return Redirect(logout?.PostLogoutRedirectUri); | |||
} | |||
public async Task<IActionResult> DeviceLogOut(string redirectUrl) | |||
{ | |||
// delete authentication cookie | |||
await HttpContext.SignOutAsync(); | |||
// set this so UI rendering sees an anonymous user | |||
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity()); | |||
return Redirect(redirectUrl); | |||
} | |||
// GET: /Account/Register | |||
[HttpGet] | |||
[AllowAnonymous] | |||
public IActionResult Register(string returnUrl = null) | |||
{ | |||
ViewData["ReturnUrl"] = returnUrl; | |||
return View(); | |||
} | |||
// | |||
// POST: /Account/Register | |||
[HttpPost] | |||
[AllowAnonymous] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null) | |||
{ | |||
ViewData["ReturnUrl"] = returnUrl; | |||
if (ModelState.IsValid) | |||
{ | |||
var user = new ApplicationUser | |||
{ | |||
UserName = model.Email, | |||
Email = model.Email, | |||
CardHolderName = model.User.CardHolderName, | |||
CardNumber = model.User.CardNumber, | |||
CardType = model.User.CardType, | |||
City = model.User.City, | |||
Country = model.User.Country, | |||
Expiration = model.User.Expiration, | |||
LastName = model.User.LastName, | |||
Name = model.User.Name, | |||
Street = model.User.Street, | |||
State = model.User.State, | |||
ZipCode = model.User.ZipCode, | |||
PhoneNumber = model.User.PhoneNumber, | |||
SecurityNumber = model.User.SecurityNumber | |||
}; | |||
var result = await _userManager.CreateAsync(user, model.Password); | |||
if (result.Errors.Count() > 0) | |||
{ | |||
AddErrors(result); | |||
// If we got this far, something failed, redisplay form | |||
return View(model); | |||
} | |||
} | |||
if (returnUrl != null) | |||
{ | |||
if (HttpContext.User.Identity.IsAuthenticated) | |||
return Redirect(returnUrl); | |||
else | |||
if (ModelState.IsValid) | |||
return RedirectToAction("login", "account", new { returnUrl = returnUrl }); | |||
else | |||
return View(model); | |||
} | |||
return RedirectToAction("index", "home"); | |||
} | |||
[HttpGet] | |||
public IActionResult Redirecting() | |||
{ | |||
return View(); | |||
} | |||
private void AddErrors(IdentityResult result) | |||
{ | |||
foreach (var error in result.Errors) | |||
{ | |||
ModelState.AddModelError(string.Empty, error.Description); | |||
} | |||
} | |||
} | |||
} |
@ -1,249 +0,0 @@ | |||
using Duende.IdentityServer.Events; | |||
using Duende.IdentityServer.Extensions; | |||
using Identity.API.Extensions; | |||
using Microsoft.eShopOnContainers.Services.Identity.API.Models.ConsentViewModels; | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers | |||
{ | |||
/// <summary> | |||
/// This controller implements the consent logic | |||
/// </summary> | |||
public class ConsentController : Controller | |||
{ | |||
private readonly ILogger<ConsentController> _logger; | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IEventService _events; | |||
public ConsentController( | |||
ILogger<ConsentController> logger, | |||
IIdentityServerInteractionService interaction, | |||
IEventService events) | |||
{ | |||
_interaction = interaction; | |||
_events = events; | |||
_logger = logger; | |||
} | |||
/// <summary> | |||
/// Shows the consent screen | |||
/// </summary> | |||
/// <param name="returnUrl"></param> | |||
/// <returns></returns> | |||
[HttpGet] | |||
public async Task<IActionResult> Index(string returnUrl) | |||
{ | |||
var vm = await 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 ProcessConsent(model); | |||
if (result.IsRedirect) | |||
{ | |||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); | |||
if (context?.IsNativeClient() == true) | |||
{ | |||
// The client is native, so this change in how to | |||
// return the response is for better UX for the end user. | |||
return this.LoadingPage("Redirect", result.RedirectUri); | |||
} | |||
return Redirect(result.RedirectUri); | |||
} | |||
if (result.HasValidationError) | |||
{ | |||
ModelState.AddModelError(string.Empty, result.ValidationError); | |||
} | |||
if (result.ShowView) | |||
{ | |||
return View("Index", result.ViewModel); | |||
} | |||
return View("Error"); | |||
} | |||
/*****************************************/ | |||
/* helper APIs for the ConsentController */ | |||
/*****************************************/ | |||
private async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model) | |||
{ | |||
var result = new ProcessConsentResult(); | |||
// validate return url is still valid | |||
var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); | |||
if (request == null) return result; | |||
ConsentResponse grantedConsent = null; | |||
// user clicked 'no' - send back the standard 'access_denied' response | |||
if (model?.Button == "no") | |||
{ | |||
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; | |||
// emit event | |||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); | |||
} | |||
// user clicked 'yes' - validate the data | |||
else if (model?.Button == "yes") | |||
{ | |||
// 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 != Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess); | |||
} | |||
grantedConsent = new ConsentResponse | |||
{ | |||
RememberConsent = model.RememberConsent, | |||
ScopesValuesConsented = scopes.ToArray(), | |||
Description = model.Description | |||
}; | |||
// emit event | |||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); | |||
} | |||
else | |||
{ | |||
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; | |||
} | |||
} | |||
else | |||
{ | |||
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; | |||
} | |||
if (grantedConsent != null) | |||
{ | |||
// 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; | |||
result.Client = request.Client; | |||
} | |||
else | |||
{ | |||
// we need to redisplay the consent UI | |||
result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); | |||
} | |||
return result; | |||
} | |||
private async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) | |||
{ | |||
var request = await _interaction.GetAuthorizationContextAsync(returnUrl); | |||
if (request != null) | |||
{ | |||
return CreateConsentViewModel(model, returnUrl, request); | |||
} | |||
else | |||
{ | |||
_logger.LogError("No consent request matching request: {0}", returnUrl); | |||
} | |||
return null; | |||
} | |||
private ConsentViewModel CreateConsentViewModel( | |||
ConsentInputModel model, string returnUrl, | |||
AuthorizationRequest request) | |||
{ | |||
var vm = new ConsentViewModel | |||
{ | |||
RememberConsent = model?.RememberConsent ?? true, | |||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(), | |||
Description = model?.Description, | |||
ReturnUrl = returnUrl, | |||
ClientName = request.Client.ClientName ?? request.Client.ClientId, | |||
ClientUrl = request.Client.ClientUri, | |||
ClientLogoUrl = request.Client.LogoUri, | |||
AllowRememberConsent = request.Client.AllowRememberConsent | |||
}; | |||
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); | |||
var apiScopes = new List<ScopeViewModel>(); | |||
foreach (var parsedScope in request.ValidatedResources.ParsedScopes) | |||
{ | |||
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); | |||
if (apiScope != null) | |||
{ | |||
var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); | |||
apiScopes.Add(scopeVm); | |||
} | |||
} | |||
if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) | |||
{ | |||
apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); | |||
} | |||
vm.ApiScopes = apiScopes; | |||
return vm; | |||
} | |||
private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = identity.Name, | |||
DisplayName = identity.DisplayName ?? identity.Name, | |||
Description = identity.Description, | |||
Emphasize = identity.Emphasize, | |||
Required = identity.Required, | |||
Checked = check || identity.Required | |||
}; | |||
} | |||
public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) | |||
{ | |||
var displayName = apiScope.DisplayName ?? apiScope.Name; | |||
if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) | |||
{ | |||
displayName += ":" + parsedScopeValue.ParsedParameter; | |||
} | |||
return new ScopeViewModel | |||
{ | |||
Value = parsedScopeValue.RawValue, | |||
DisplayName = displayName, | |||
Description = apiScope.Description, | |||
Emphasize = apiScope.Emphasize, | |||
Required = apiScope.Required, | |||
Checked = check || apiScope.Required | |||
}; | |||
} | |||
private ScopeViewModel GetOfflineAccessScope(bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = Duende.IdentityServer.IdentityServerConstants.StandardScopes.OfflineAccess, | |||
DisplayName = ConsentOptions.OfflineAccessDisplayName, | |||
Description = ConsentOptions.OfflineAccessDescription, | |||
Emphasize = true, | |||
Checked = check | |||
}; | |||
} | |||
} | |||
} |
@ -1,46 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers | |||
{ | |||
public class HomeController : Controller | |||
{ | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IOptionsSnapshot<AppSettings> _settings; | |||
private readonly IRedirectService _redirectSvc; | |||
public HomeController(IIdentityServerInteractionService interaction, IOptionsSnapshot<AppSettings> settings, IRedirectService redirectSvc) | |||
{ | |||
_interaction = interaction; | |||
_settings = settings; | |||
_redirectSvc = redirectSvc; | |||
} | |||
public IActionResult Index(string returnUrl) | |||
{ | |||
return View(); | |||
} | |||
public IActionResult ReturnToOriginalApplication(string returnUrl) | |||
{ | |||
if (returnUrl != null) | |||
return Redirect(_redirectSvc.ExtractRedirectUriFromReturnUrl(returnUrl)); | |||
else | |||
return RedirectToAction("Index", "Home"); | |||
} | |||
/// <summary> | |||
/// Shows the error page | |||
/// </summary> | |||
public async Task<IActionResult> Error(string errorId) | |||
{ | |||
var vm = new ErrorViewModel(); | |||
// retrieve error details from identityserver | |||
var message = await _interaction.GetErrorContextAsync(errorId); | |||
if (message != null) | |||
{ | |||
vm.Error = message; | |||
} | |||
return View("Error", vm); | |||
} | |||
} | |||
} |
@ -1,18 +1,17 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Data | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Data; | |||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> | |||
{ | |||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> | |||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) | |||
: base(options) | |||
{ | |||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) | |||
: base(options) | |||
{ | |||
} | |||
} | |||
protected override void OnModelCreating(ModelBuilder builder) | |||
{ | |||
base.OnModelCreating(builder); | |||
// Customize the ASP.NET Identity model and override the defaults if needed. | |||
// For example, you can rename the ASP.NET Identity table names and more. | |||
// Add your customizations after calling base.OnModelCreating(builder); | |||
} | |||
protected override void OnModelCreating(ModelBuilder builder) | |||
{ | |||
base.OnModelCreating(builder); | |||
// Customize the ASP.NET Identity model and override the defaults if needed. | |||
// For example, you can rename the ASP.NET Identity table names and more. | |||
// Add your customizations after calling base.OnModelCreating(builder); | |||
} | |||
} |
@ -1,218 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Data | |||
{ | |||
using Microsoft.Extensions.Logging; | |||
public class ApplicationDbContextSeed | |||
{ | |||
private readonly IPasswordHasher<ApplicationUser> _passwordHasher = new PasswordHasher<ApplicationUser>(); | |||
public async Task SeedAsync(ApplicationDbContext context, IWebHostEnvironment env, | |||
ILogger<ApplicationDbContextSeed> logger, IOptions<AppSettings> settings, int? retry = 0) | |||
{ | |||
int retryForAvaiability = retry.Value; | |||
try | |||
{ | |||
var useCustomizationData = settings.Value.UseCustomizationData; | |||
var contentRootPath = env.ContentRootPath; | |||
var webroot = env.WebRootPath; | |||
if (!context.Users.Any()) | |||
{ | |||
context.Users.AddRange(useCustomizationData | |||
? GetUsersFromFile(contentRootPath, logger) | |||
: GetDefaultUser()); | |||
await context.SaveChangesAsync(); | |||
} | |||
if (useCustomizationData) | |||
{ | |||
GetPreconfiguredImages(contentRootPath, webroot, logger); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
if (retryForAvaiability < 10) | |||
{ | |||
retryForAvaiability++; | |||
logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext)); | |||
await SeedAsync(context, env, logger, settings, retryForAvaiability); | |||
} | |||
} | |||
} | |||
private IEnumerable<ApplicationUser> GetUsersFromFile(string contentRootPath, ILogger logger) | |||
{ | |||
string csvFileUsers = Path.Combine(contentRootPath, "Setup", "Users.csv"); | |||
if (!File.Exists(csvFileUsers)) | |||
{ | |||
return GetDefaultUser(); | |||
} | |||
string[] csvheaders; | |||
try | |||
{ | |||
string[] requiredHeaders = { | |||
"cardholdername", "cardnumber", "cardtype", "city", "country", | |||
"email", "expiration", "lastname", "name", "phonenumber", | |||
"username", "zipcode", "state", "street", "securitynumber", | |||
"normalizedemail", "normalizedusername", "password" | |||
}; | |||
csvheaders = GetHeaders(requiredHeaders, csvFileUsers); | |||
} | |||
catch (Exception ex) | |||
{ | |||
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); | |||
return GetDefaultUser(); | |||
} | |||
List<ApplicationUser> users = File.ReadAllLines(csvFileUsers) | |||
.Skip(1) // skip header column | |||
.Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)")) | |||
.SelectTry(column => CreateApplicationUser(column, csvheaders)) | |||
.OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) | |||
.Where(x => x != null) | |||
.ToList(); | |||
return users; | |||
} | |||
private ApplicationUser CreateApplicationUser(string[] column, string[] headers) | |||
{ | |||
if (column.Count() != headers.Count()) | |||
{ | |||
throw new Exception($"column count '{column.Count()}' not the same as headers count'{headers.Count()}'"); | |||
} | |||
string cardtypeString = column[Array.IndexOf(headers, "cardtype")].Trim('"').Trim(); | |||
if (!int.TryParse(cardtypeString, out int cardtype)) | |||
{ | |||
throw new Exception($"cardtype='{cardtypeString}' is not a number"); | |||
} | |||
var user = new ApplicationUser | |||
{ | |||
CardHolderName = column[Array.IndexOf(headers, "cardholdername")].Trim('"').Trim(), | |||
CardNumber = column[Array.IndexOf(headers, "cardnumber")].Trim('"').Trim(), | |||
CardType = cardtype, | |||
City = column[Array.IndexOf(headers, "city")].Trim('"').Trim(), | |||
Country = column[Array.IndexOf(headers, "country")].Trim('"').Trim(), | |||
Email = column[Array.IndexOf(headers, "email")].Trim('"').Trim(), | |||
Expiration = column[Array.IndexOf(headers, "expiration")].Trim('"').Trim(), | |||
Id = Guid.NewGuid().ToString(), | |||
LastName = column[Array.IndexOf(headers, "lastname")].Trim('"').Trim(), | |||
Name = column[Array.IndexOf(headers, "name")].Trim('"').Trim(), | |||
PhoneNumber = column[Array.IndexOf(headers, "phonenumber")].Trim('"').Trim(), | |||
UserName = column[Array.IndexOf(headers, "username")].Trim('"').Trim(), | |||
ZipCode = column[Array.IndexOf(headers, "zipcode")].Trim('"').Trim(), | |||
State = column[Array.IndexOf(headers, "state")].Trim('"').Trim(), | |||
Street = column[Array.IndexOf(headers, "street")].Trim('"').Trim(), | |||
SecurityNumber = column[Array.IndexOf(headers, "securitynumber")].Trim('"').Trim(), | |||
NormalizedEmail = column[Array.IndexOf(headers, "normalizedemail")].Trim('"').Trim(), | |||
NormalizedUserName = column[Array.IndexOf(headers, "normalizedusername")].Trim('"').Trim(), | |||
SecurityStamp = Guid.NewGuid().ToString("D"), | |||
PasswordHash = column[Array.IndexOf(headers, "password")].Trim('"').Trim(), // Note: This is the password | |||
}; | |||
user.PasswordHash = _passwordHasher.HashPassword(user, user.PasswordHash); | |||
return user; | |||
} | |||
private IEnumerable<ApplicationUser> GetDefaultUser() | |||
{ | |||
var user = | |||
new ApplicationUser() | |||
{ | |||
CardHolderName = "DemoUser", | |||
CardNumber = "4012888888881881", | |||
CardType = 1, | |||
City = "Redmond", | |||
Country = "U.S.", | |||
Email = "demouser@microsoft.com", | |||
Expiration = "12/25", | |||
Id = Guid.NewGuid().ToString(), | |||
LastName = "DemoLastName", | |||
Name = "DemoUser", | |||
PhoneNumber = "1234567890", | |||
UserName = "demouser@microsoft.com", | |||
ZipCode = "98052", | |||
State = "WA", | |||
Street = "15703 NE 61st Ct", | |||
SecurityNumber = "535", | |||
NormalizedEmail = "DEMOUSER@MICROSOFT.COM", | |||
NormalizedUserName = "DEMOUSER@MICROSOFT.COM", | |||
SecurityStamp = Guid.NewGuid().ToString("D"), | |||
}; | |||
user.PasswordHash = _passwordHasher.HashPassword(user, "Pass@word1"); | |||
return new List<ApplicationUser>() | |||
{ | |||
user | |||
}; | |||
} | |||
static string[] GetHeaders(string[] requiredHeaders, string csvfile) | |||
{ | |||
string[] csvheaders = File.ReadLines(csvfile).First().ToLowerInvariant().Split(','); | |||
if (csvheaders.Count() != requiredHeaders.Count()) | |||
{ | |||
throw new Exception($"requiredHeader count '{ requiredHeaders.Count()}' is different then read header '{csvheaders.Count()}'"); | |||
} | |||
foreach (var requiredHeader in requiredHeaders) | |||
{ | |||
if (!csvheaders.Contains(requiredHeader)) | |||
{ | |||
throw new Exception($"does not contain required header '{requiredHeader}'"); | |||
} | |||
} | |||
return csvheaders; | |||
} | |||
static void GetPreconfiguredImages(string contentRootPath, string webroot, ILogger logger) | |||
{ | |||
try | |||
{ | |||
string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); | |||
if (!File.Exists(imagesZipFile)) | |||
{ | |||
logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); | |||
return; | |||
} | |||
string imagePath = Path.Combine(webroot, "images"); | |||
string[] imageFiles = Directory.GetFiles(imagePath).Select(file => Path.GetFileName(file)).ToArray(); | |||
using ZipArchive zip = ZipFile.Open(imagesZipFile, ZipArchiveMode.Read); | |||
foreach (ZipArchiveEntry entry in zip.Entries) | |||
{ | |||
if (imageFiles.Contains(entry.Name)) | |||
{ | |||
string destinationFilename = Path.Combine(imagePath, entry.Name); | |||
if (File.Exists(destinationFilename)) | |||
{ | |||
File.Delete(destinationFilename); | |||
} | |||
entry.ExtractToFile(destinationFilename); | |||
} | |||
else | |||
{ | |||
logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ; | |||
} | |||
} | |||
} | |||
} |
@ -1,83 +0,0 @@ | |||
using Duende.IdentityServer.EntityFramework.Entities; | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Data | |||
{ | |||
public class ConfigurationDbContextSeed | |||
{ | |||
public async Task SeedAsync(ConfigurationDbContext context, IConfiguration configuration) | |||
{ | |||
//callbacks urls from config: | |||
var clientUrls = new Dictionary<string, string>(); | |||
clientUrls.Add("Mvc", configuration.GetValue<string>("MvcClient")); | |||
clientUrls.Add("Spa", configuration.GetValue<string>("SpaClient")); | |||
clientUrls.Add("Xamarin", configuration.GetValue<string>("XamarinCallback")); | |||
clientUrls.Add("BasketApi", configuration.GetValue<string>("BasketApiClient")); | |||
clientUrls.Add("OrderingApi", configuration.GetValue<string>("OrderingApiClient")); | |||
clientUrls.Add("MobileShoppingAgg", configuration.GetValue<string>("MobileShoppingAggClient")); | |||
clientUrls.Add("WebShoppingAgg", configuration.GetValue<string>("WebShoppingAggClient")); | |||
clientUrls.Add("WebhooksApi", configuration.GetValue<string>("WebhooksApiClient")); | |||
clientUrls.Add("WebhooksWeb", configuration.GetValue<string>("WebhooksWebClient")); | |||
if (!context.Clients.Any()) | |||
{ | |||
foreach (var client in Config.GetClients(clientUrls)) | |||
{ | |||
context.Clients.Add(client.ToEntity()); | |||
} | |||
await context.SaveChangesAsync(); | |||
} | |||
// Checking always for old redirects to fix existing deployments | |||
// to use new swagger-ui redirect uri as of v3.0.0 | |||
// There should be no problem for new ones | |||
// ref: https://github.com/dotnet-architecture/eShopOnContainers/issues/586 | |||
else | |||
{ | |||
List<ClientRedirectUri> oldRedirects = (await context.Clients.Include(c => c.RedirectUris).ToListAsync()) | |||
.SelectMany(c => c.RedirectUris) | |||
.Where(ru => ru.RedirectUri.EndsWith("/o2c.html")) | |||
.ToList(); | |||
if (oldRedirects.Any()) | |||
{ | |||
foreach (var ru in oldRedirects) | |||
{ | |||
ru.RedirectUri = ru.RedirectUri.Replace("/o2c.html", "/oauth2-redirect.html"); | |||
context.Update(ru.Client); | |||
} | |||
await context.SaveChangesAsync(); | |||
} | |||
} | |||
if (!context.IdentityResources.Any()) | |||
{ | |||
foreach (var resource in Config.GetResources()) | |||
{ | |||
context.IdentityResources.Add(resource.ToEntity()); | |||
} | |||
await context.SaveChangesAsync(); | |||
} | |||
if (!context.ApiResources.Any()) | |||
{ | |||
foreach (var api in Config.GetApis()) | |||
{ | |||
context.ApiResources.Add(api.ToEntity()); | |||
} | |||
await context.SaveChangesAsync(); | |||
} | |||
if (!context.ApiScopes.Any()) | |||
{ | |||
foreach (var apiScope in Config.GetApiScopes()) | |||
{ | |||
context.ApiScopes.Add(apiScope.ToEntity()); | |||
} | |||
await context.SaveChangesAsync(); | |||
} | |||
} | |||
} | |||
} |
@ -1,7 +1,7 @@ | |||
using System; | |||
using Microsoft.EntityFrameworkCore.Migrations; | |||
namespace Identity.API.Migrations | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Data.Migrations | |||
{ | |||
public partial class InitialMigration : Migration | |||
{ |
@ -1,23 +0,0 @@ | |||
namespace Identity.API.Extensions | |||
{ | |||
public static class Extensions | |||
{ | |||
/// <summary> | |||
/// Checks if the redirect URI is for a native client. | |||
/// </summary> | |||
/// <returns></returns> | |||
public static bool IsNativeClient(this AuthorizationRequest context) | |||
{ | |||
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) | |||
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); | |||
} | |||
public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) | |||
{ | |||
controller.HttpContext.Response.StatusCode = 200; | |||
controller.HttpContext.Response.Headers["Location"] = ""; | |||
return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); | |||
} | |||
} | |||
} |
@ -1,46 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Extensions | |||
{ | |||
public static class LinqSelectExtensions | |||
{ | |||
public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector) | |||
{ | |||
foreach (TSource element in enumerable) | |||
{ | |||
SelectTryResult<TSource, TResult> returnedValue; | |||
try | |||
{ | |||
returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null); | |||
} | |||
catch (Exception ex) | |||
{ | |||
returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex); | |||
} | |||
yield return returnedValue; | |||
} | |||
} | |||
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler) | |||
{ | |||
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException)); | |||
} | |||
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler) | |||
{ | |||
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException)); | |||
} | |||
public class SelectTryResult<TSource, TResult> | |||
{ | |||
internal SelectTryResult(TSource source, TResult result, Exception exception) | |||
{ | |||
Source = source; | |||
Result = result; | |||
CaughtException = exception; | |||
} | |||
public TSource Source { get; private set; } | |||
public TResult Result { get; private set; } | |||
public Exception CaughtException { get; private set; } | |||
} | |||
} | |||
} |
@ -1,20 +0,0 @@ | |||
namespace Identity.API.Factories | |||
{ | |||
public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> | |||
{ | |||
public ApplicationDbContext CreateDbContext(string[] args) | |||
{ | |||
var config = new ConfigurationBuilder() | |||
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) | |||
.AddJsonFile("appsettings.json") | |||
.AddEnvironmentVariables() | |||
.Build(); | |||
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>(); | |||
optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); | |||
return new ApplicationDbContext(optionsBuilder.Options); | |||
} | |||
} | |||
} |
@ -1,21 +0,0 @@ | |||
namespace Identity.API.Factories | |||
{ | |||
public class ConfigurationDbContextFactory : IDesignTimeDbContextFactory<ConfigurationDbContext> | |||
{ | |||
public ConfigurationDbContext CreateDbContext(string[] args) | |||
{ | |||
var config = new ConfigurationBuilder() | |||
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) | |||
.AddJsonFile("appsettings.json") | |||
.AddEnvironmentVariables() | |||
.Build(); | |||
var optionsBuilder = new DbContextOptionsBuilder<ConfigurationDbContext>(); | |||
var storeOptions = new ConfigurationStoreOptions(); | |||
optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); | |||
return new ConfigurationDbContext(optionsBuilder.Options); | |||
} | |||
} | |||
} |
@ -1,21 +0,0 @@ | |||
namespace Identity.API.Factories | |||
{ | |||
public class PersistedGrantDbContextFactory : IDesignTimeDbContextFactory<PersistedGrantDbContext> | |||
{ | |||
public PersistedGrantDbContext CreateDbContext(string[] args) | |||
{ | |||
var config = new ConfigurationBuilder() | |||
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory())) | |||
.AddJsonFile("appsettings.json") | |||
.AddEnvironmentVariables() | |||
.Build(); | |||
var optionsBuilder = new DbContextOptionsBuilder<PersistedGrantDbContext>(); | |||
var operationOptions = new OperationalStoreOptions(); | |||
optionsBuilder.UseSqlServer(config["ConnectionString"], sqlServerOptionsAction: o => o.MigrationsAssembly("Identity.API")); | |||
return new PersistedGrantDbContext(optionsBuilder.Options); | |||
} | |||
} | |||
} |
@ -1,712 +0,0 @@ | |||
using System; | |||
using Microsoft.EntityFrameworkCore.Migrations; | |||
#nullable disable | |||
namespace Identity.API.Migrations.ConfigurationDb | |||
{ | |||
public partial class Configuration : Migration | |||
{ | |||
protected override void Up(MigrationBuilder migrationBuilder) | |||
{ | |||
migrationBuilder.CreateTable( | |||
name: "ApiResources", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Enabled = table.Column<bool>(type: "bit", nullable: false), | |||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
DisplayName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true), | |||
AllowedAccessTokenSigningAlgorithms = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
ShowInDiscoveryDocument = table.Column<bool>(type: "bit", nullable: false), | |||
RequireResourceIndicator = table.Column<bool>(type: "bit", nullable: false), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Updated = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
LastAccessed = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
NonEditable = table.Column<bool>(type: "bit", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiResources", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiScopes", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Enabled = table.Column<bool>(type: "bit", nullable: false), | |||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
DisplayName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true), | |||
Required = table.Column<bool>(type: "bit", nullable: false), | |||
Emphasize = table.Column<bool>(type: "bit", nullable: false), | |||
ShowInDiscoveryDocument = table.Column<bool>(type: "bit", nullable: false), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Updated = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
LastAccessed = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
NonEditable = table.Column<bool>(type: "bit", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiScopes", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "Clients", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Enabled = table.Column<bool>(type: "bit", nullable: false), | |||
ClientId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
ProtocolType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
RequireClientSecret = table.Column<bool>(type: "bit", nullable: false), | |||
ClientName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true), | |||
ClientUri = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true), | |||
LogoUri = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true), | |||
RequireConsent = table.Column<bool>(type: "bit", nullable: false), | |||
AllowRememberConsent = table.Column<bool>(type: "bit", nullable: false), | |||
AlwaysIncludeUserClaimsInIdToken = table.Column<bool>(type: "bit", nullable: false), | |||
RequirePkce = table.Column<bool>(type: "bit", nullable: false), | |||
AllowPlainTextPkce = table.Column<bool>(type: "bit", nullable: false), | |||
RequireRequestObject = table.Column<bool>(type: "bit", nullable: false), | |||
AllowAccessTokensViaBrowser = table.Column<bool>(type: "bit", nullable: false), | |||
FrontChannelLogoutUri = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true), | |||
FrontChannelLogoutSessionRequired = table.Column<bool>(type: "bit", nullable: false), | |||
BackChannelLogoutUri = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true), | |||
BackChannelLogoutSessionRequired = table.Column<bool>(type: "bit", nullable: false), | |||
AllowOfflineAccess = table.Column<bool>(type: "bit", nullable: false), | |||
IdentityTokenLifetime = table.Column<int>(type: "int", nullable: false), | |||
AllowedIdentityTokenSigningAlgorithms = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
AccessTokenLifetime = table.Column<int>(type: "int", nullable: false), | |||
AuthorizationCodeLifetime = table.Column<int>(type: "int", nullable: false), | |||
ConsentLifetime = table.Column<int>(type: "int", nullable: true), | |||
AbsoluteRefreshTokenLifetime = table.Column<int>(type: "int", nullable: false), | |||
SlidingRefreshTokenLifetime = table.Column<int>(type: "int", nullable: false), | |||
RefreshTokenUsage = table.Column<int>(type: "int", nullable: false), | |||
UpdateAccessTokenClaimsOnRefresh = table.Column<bool>(type: "bit", nullable: false), | |||
RefreshTokenExpiration = table.Column<int>(type: "int", nullable: false), | |||
AccessTokenType = table.Column<int>(type: "int", nullable: false), | |||
EnableLocalLogin = table.Column<bool>(type: "bit", nullable: false), | |||
IncludeJwtId = table.Column<bool>(type: "bit", nullable: false), | |||
AlwaysSendClientClaims = table.Column<bool>(type: "bit", nullable: false), | |||
ClientClaimsPrefix = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
PairWiseSubjectSalt = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
UserSsoLifetime = table.Column<int>(type: "int", nullable: true), | |||
UserCodeType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
DeviceCodeLifetime = table.Column<int>(type: "int", nullable: false), | |||
CibaLifetime = table.Column<int>(type: "int", nullable: true), | |||
PollingInterval = table.Column<int>(type: "int", nullable: true), | |||
CoordinateLifetimeWithUserSession = table.Column<bool>(type: "bit", nullable: true), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Updated = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
LastAccessed = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
NonEditable = table.Column<bool>(type: "bit", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_Clients", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "IdentityProviders", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Scheme = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
DisplayName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
Enabled = table.Column<bool>(type: "bit", nullable: false), | |||
Type = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false), | |||
Properties = table.Column<string>(type: "nvarchar(max)", nullable: true), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Updated = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
LastAccessed = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
NonEditable = table.Column<bool>(type: "bit", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_IdentityProviders", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "IdentityResources", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Enabled = table.Column<bool>(type: "bit", nullable: false), | |||
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
DisplayName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true), | |||
Required = table.Column<bool>(type: "bit", nullable: false), | |||
Emphasize = table.Column<bool>(type: "bit", nullable: false), | |||
ShowInDiscoveryDocument = table.Column<bool>(type: "bit", nullable: false), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Updated = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
NonEditable = table.Column<bool>(type: "bit", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_IdentityResources", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiResourceClaims", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ApiResourceId = table.Column<int>(type: "int", nullable: false), | |||
Type = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiResourceClaims", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_ApiResourceClaims_ApiResources_ApiResourceId", | |||
column: x => x.ApiResourceId, | |||
principalTable: "ApiResources", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiResourceProperties", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ApiResourceId = table.Column<int>(type: "int", nullable: false), | |||
Key = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Value = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiResourceProperties", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_ApiResourceProperties_ApiResources_ApiResourceId", | |||
column: x => x.ApiResourceId, | |||
principalTable: "ApiResources", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiResourceScopes", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Scope = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
ApiResourceId = table.Column<int>(type: "int", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiResourceScopes", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_ApiResourceScopes_ApiResources_ApiResourceId", | |||
column: x => x.ApiResourceId, | |||
principalTable: "ApiResources", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiResourceSecrets", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ApiResourceId = table.Column<int>(type: "int", nullable: false), | |||
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true), | |||
Value = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: false), | |||
Expiration = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
Type = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId", | |||
column: x => x.ApiResourceId, | |||
principalTable: "ApiResources", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiScopeClaims", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ScopeId = table.Column<int>(type: "int", nullable: false), | |||
Type = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_ApiScopeClaims_ApiScopes_ScopeId", | |||
column: x => x.ScopeId, | |||
principalTable: "ApiScopes", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ApiScopeProperties", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ScopeId = table.Column<int>(type: "int", nullable: false), | |||
Key = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Value = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ApiScopeProperties", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_ApiScopeProperties_ApiScopes_ScopeId", | |||
column: x => x.ScopeId, | |||
principalTable: "ApiScopes", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ClientClaims", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Type = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Value = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Origin = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
GrantType = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Provider = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
PostLogoutRedirectUri = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ClientId = table.Column<int>(type: "int", nullable: false), | |||
Key = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Value = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, 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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
RedirectUri = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Scope = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
ClientId = table.Column<int>(type: "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>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
ClientId = table.Column<int>(type: "int", nullable: false), | |||
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true), | |||
Value = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: false), | |||
Expiration = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
Type = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Created = table.Column<DateTime>(type: "datetime2", 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: "IdentityResourceClaims", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
IdentityResourceId = table.Column<int>(type: "int", nullable: false), | |||
Type = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId", | |||
column: x => x.IdentityResourceId, | |||
principalTable: "IdentityResources", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "IdentityResourceProperties", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
IdentityResourceId = table.Column<int>(type: "int", nullable: false), | |||
Key = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false), | |||
Value = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id); | |||
table.ForeignKey( | |||
name: "FK_IdentityResourceProperties_IdentityResources_IdentityResourceId", | |||
column: x => x.IdentityResourceId, | |||
principalTable: "IdentityResources", | |||
principalColumn: "Id", | |||
onDelete: ReferentialAction.Cascade); | |||
}); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiResourceClaims_ApiResourceId_Type", | |||
table: "ApiResourceClaims", | |||
columns: new[] { "ApiResourceId", "Type" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiResourceProperties_ApiResourceId_Key", | |||
table: "ApiResourceProperties", | |||
columns: new[] { "ApiResourceId", "Key" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiResources_Name", | |||
table: "ApiResources", | |||
column: "Name", | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiResourceScopes_ApiResourceId_Scope", | |||
table: "ApiResourceScopes", | |||
columns: new[] { "ApiResourceId", "Scope" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiResourceSecrets_ApiResourceId", | |||
table: "ApiResourceSecrets", | |||
column: "ApiResourceId"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiScopeClaims_ScopeId_Type", | |||
table: "ApiScopeClaims", | |||
columns: new[] { "ScopeId", "Type" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiScopeProperties_ScopeId_Key", | |||
table: "ApiScopeProperties", | |||
columns: new[] { "ScopeId", "Key" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ApiScopes_Name", | |||
table: "ApiScopes", | |||
column: "Name", | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientClaims_ClientId_Type_Value", | |||
table: "ClientClaims", | |||
columns: new[] { "ClientId", "Type", "Value" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientCorsOrigins_ClientId_Origin", | |||
table: "ClientCorsOrigins", | |||
columns: new[] { "ClientId", "Origin" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientGrantTypes_ClientId_GrantType", | |||
table: "ClientGrantTypes", | |||
columns: new[] { "ClientId", "GrantType" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientIdPRestrictions_ClientId_Provider", | |||
table: "ClientIdPRestrictions", | |||
columns: new[] { "ClientId", "Provider" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientPostLogoutRedirectUris_ClientId_PostLogoutRedirectUri", | |||
table: "ClientPostLogoutRedirectUris", | |||
columns: new[] { "ClientId", "PostLogoutRedirectUri" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientProperties_ClientId_Key", | |||
table: "ClientProperties", | |||
columns: new[] { "ClientId", "Key" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientRedirectUris_ClientId_RedirectUri", | |||
table: "ClientRedirectUris", | |||
columns: new[] { "ClientId", "RedirectUri" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_Clients_ClientId", | |||
table: "Clients", | |||
column: "ClientId", | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientScopes_ClientId_Scope", | |||
table: "ClientScopes", | |||
columns: new[] { "ClientId", "Scope" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ClientSecrets_ClientId", | |||
table: "ClientSecrets", | |||
column: "ClientId"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_IdentityProviders_Scheme", | |||
table: "IdentityProviders", | |||
column: "Scheme", | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_IdentityResourceClaims_IdentityResourceId_Type", | |||
table: "IdentityResourceClaims", | |||
columns: new[] { "IdentityResourceId", "Type" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_IdentityResourceProperties_IdentityResourceId_Key", | |||
table: "IdentityResourceProperties", | |||
columns: new[] { "IdentityResourceId", "Key" }, | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_IdentityResources_Name", | |||
table: "IdentityResources", | |||
column: "Name", | |||
unique: true); | |||
} | |||
protected override void Down(MigrationBuilder migrationBuilder) | |||
{ | |||
migrationBuilder.DropTable( | |||
name: "ApiResourceClaims"); | |||
migrationBuilder.DropTable( | |||
name: "ApiResourceProperties"); | |||
migrationBuilder.DropTable( | |||
name: "ApiResourceScopes"); | |||
migrationBuilder.DropTable( | |||
name: "ApiResourceSecrets"); | |||
migrationBuilder.DropTable( | |||
name: "ApiScopeClaims"); | |||
migrationBuilder.DropTable( | |||
name: "ApiScopeProperties"); | |||
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: "IdentityProviders"); | |||
migrationBuilder.DropTable( | |||
name: "IdentityResourceClaims"); | |||
migrationBuilder.DropTable( | |||
name: "IdentityResourceProperties"); | |||
migrationBuilder.DropTable( | |||
name: "ApiResources"); | |||
migrationBuilder.DropTable( | |||
name: "ApiScopes"); | |||
migrationBuilder.DropTable( | |||
name: "Clients"); | |||
migrationBuilder.DropTable( | |||
name: "IdentityResources"); | |||
} | |||
} | |||
} |
@ -1,240 +0,0 @@ | |||
// <auto-generated /> | |||
using System; | |||
using Duende.IdentityServer.EntityFramework.DbContexts; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||
using Microsoft.EntityFrameworkCore.Metadata; | |||
using Microsoft.EntityFrameworkCore.Migrations; | |||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | |||
#nullable disable | |||
namespace Identity.API.Migrations.PersistedGrantDb | |||
{ | |||
[DbContext(typeof(PersistedGrantDbContext))] | |||
[Migration("20220324152905_Grants")] | |||
partial class Grants | |||
{ | |||
protected override void BuildTargetModel(ModelBuilder modelBuilder) | |||
{ | |||
#pragma warning disable 612, 618 | |||
modelBuilder | |||
.HasAnnotation("ProductVersion", "6.0.0") | |||
.HasAnnotation("Relational:MaxIdentifierLength", 128); | |||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => | |||
{ | |||
b.Property<string>("UserCode") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("ClientId") | |||
.IsRequired() | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime>("CreationTime") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasMaxLength(50000) | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<string>("Description") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("DeviceCode") | |||
.IsRequired() | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime?>("Expiration") | |||
.IsRequired() | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("SessionId") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SubjectId") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.HasKey("UserCode"); | |||
b.HasIndex("DeviceCode") | |||
.IsUnique(); | |||
b.HasIndex("Expiration"); | |||
b.ToTable("DeviceCodes", (string)null); | |||
}); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.Key", b => | |||
{ | |||
b.Property<string>("Id") | |||
.HasColumnType("nvarchar(450)"); | |||
b.Property<string>("Algorithm") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<DateTime>("Created") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<bool>("DataProtected") | |||
.HasColumnType("bit"); | |||
b.Property<bool>("IsX509Certificate") | |||
.HasColumnType("bit"); | |||
b.Property<string>("Use") | |||
.HasColumnType("nvarchar(450)"); | |||
b.Property<int>("Version") | |||
.HasColumnType("int"); | |||
b.HasKey("Id"); | |||
b.HasIndex("Use"); | |||
b.ToTable("Keys", (string)null); | |||
}); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => | |||
{ | |||
b.Property<long>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasColumnType("bigint"); | |||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||
b.Property<string>("ClientId") | |||
.IsRequired() | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime?>("ConsumedTime") | |||
.HasColumnType("datetime2"); | |||
b.Property<DateTime>("CreationTime") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasMaxLength(50000) | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<string>("Description") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime?>("Expiration") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Key") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("SessionId") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SubjectId") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("Type") | |||
.IsRequired() | |||
.HasMaxLength(50) | |||
.HasColumnType("nvarchar(50)"); | |||
b.HasKey("Id"); | |||
b.HasIndex("ConsumedTime"); | |||
b.HasIndex("Expiration"); | |||
b.HasIndex("Key") | |||
.IsUnique() | |||
.HasFilter("[Key] IS NOT NULL"); | |||
b.HasIndex("SubjectId", "ClientId", "Type"); | |||
b.HasIndex("SubjectId", "SessionId", "Type"); | |||
b.ToTable("PersistedGrants", (string)null); | |||
}); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.ServerSideSession", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasColumnType("int"); | |||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||
b.Property<DateTime>("Created") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<string>("DisplayName") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<DateTime?>("Expires") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Key") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<DateTime>("Renewed") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Scheme") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SessionId") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SubjectId") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.HasKey("Id"); | |||
b.HasIndex("DisplayName"); | |||
b.HasIndex("Expires"); | |||
b.HasIndex("Key") | |||
.IsUnique(); | |||
b.HasIndex("SessionId"); | |||
b.HasIndex("SubjectId"); | |||
b.ToTable("ServerSideSessions", (string)null); | |||
}); | |||
#pragma warning restore 612, 618 | |||
} | |||
} | |||
} |
@ -1,177 +0,0 @@ | |||
using System; | |||
using Microsoft.EntityFrameworkCore.Migrations; | |||
#nullable disable | |||
namespace Identity.API.Migrations.PersistedGrantDb | |||
{ | |||
public partial class Grants : Migration | |||
{ | |||
protected override void Up(MigrationBuilder migrationBuilder) | |||
{ | |||
migrationBuilder.CreateTable( | |||
name: "DeviceCodes", | |||
columns: table => new | |||
{ | |||
UserCode = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
DeviceCode = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
SubjectId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
SessionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
ClientId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
Description = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Expiration = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Data = table.Column<string>(type: "nvarchar(max)", maxLength: 50000, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "Keys", | |||
columns: table => new | |||
{ | |||
Id = table.Column<string>(type: "nvarchar(450)", nullable: false), | |||
Version = table.Column<int>(type: "int", nullable: false), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Use = table.Column<string>(type: "nvarchar(450)", nullable: true), | |||
Algorithm = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), | |||
IsX509Certificate = table.Column<bool>(type: "bit", nullable: false), | |||
DataProtected = table.Column<bool>(type: "bit", nullable: false), | |||
Data = table.Column<string>(type: "nvarchar(max)", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_Keys", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "PersistedGrants", | |||
columns: table => new | |||
{ | |||
Id = table.Column<long>(type: "bigint", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Key = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
Type = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false), | |||
SubjectId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
SessionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
ClientId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false), | |||
Description = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true), | |||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Expiration = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
ConsumedTime = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
Data = table.Column<string>(type: "nvarchar(max)", maxLength: 50000, nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_PersistedGrants", x => x.Id); | |||
}); | |||
migrationBuilder.CreateTable( | |||
name: "ServerSideSessions", | |||
columns: table => new | |||
{ | |||
Id = table.Column<int>(type: "int", nullable: false) | |||
.Annotation("SqlServer:Identity", "1, 1"), | |||
Key = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), | |||
Scheme = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), | |||
SubjectId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), | |||
SessionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
DisplayName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), | |||
Created = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Renewed = table.Column<DateTime>(type: "datetime2", nullable: false), | |||
Expires = table.Column<DateTime>(type: "datetime2", nullable: true), | |||
Data = table.Column<string>(type: "nvarchar(max)", nullable: false) | |||
}, | |||
constraints: table => | |||
{ | |||
table.PrimaryKey("PK_ServerSideSessions", x => x.Id); | |||
}); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_DeviceCodes_DeviceCode", | |||
table: "DeviceCodes", | |||
column: "DeviceCode", | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_DeviceCodes_Expiration", | |||
table: "DeviceCodes", | |||
column: "Expiration"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_Keys_Use", | |||
table: "Keys", | |||
column: "Use"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_PersistedGrants_ConsumedTime", | |||
table: "PersistedGrants", | |||
column: "ConsumedTime"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_PersistedGrants_Expiration", | |||
table: "PersistedGrants", | |||
column: "Expiration"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_PersistedGrants_Key", | |||
table: "PersistedGrants", | |||
column: "Key", | |||
unique: true, | |||
filter: "[Key] IS NOT NULL"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_PersistedGrants_SubjectId_ClientId_Type", | |||
table: "PersistedGrants", | |||
columns: new[] { "SubjectId", "ClientId", "Type" }); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_PersistedGrants_SubjectId_SessionId_Type", | |||
table: "PersistedGrants", | |||
columns: new[] { "SubjectId", "SessionId", "Type" }); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ServerSideSessions_DisplayName", | |||
table: "ServerSideSessions", | |||
column: "DisplayName"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ServerSideSessions_Expires", | |||
table: "ServerSideSessions", | |||
column: "Expires"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ServerSideSessions_Key", | |||
table: "ServerSideSessions", | |||
column: "Key", | |||
unique: true); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ServerSideSessions_SessionId", | |||
table: "ServerSideSessions", | |||
column: "SessionId"); | |||
migrationBuilder.CreateIndex( | |||
name: "IX_ServerSideSessions_SubjectId", | |||
table: "ServerSideSessions", | |||
column: "SubjectId"); | |||
} | |||
protected override void Down(MigrationBuilder migrationBuilder) | |||
{ | |||
migrationBuilder.DropTable( | |||
name: "DeviceCodes"); | |||
migrationBuilder.DropTable( | |||
name: "Keys"); | |||
migrationBuilder.DropTable( | |||
name: "PersistedGrants"); | |||
migrationBuilder.DropTable( | |||
name: "ServerSideSessions"); | |||
} | |||
} | |||
} |
@ -1,238 +0,0 @@ | |||
// <auto-generated /> | |||
using System; | |||
using Duende.IdentityServer.EntityFramework.DbContexts; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||
using Microsoft.EntityFrameworkCore.Metadata; | |||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | |||
#nullable disable | |||
namespace Identity.API.Migrations.PersistedGrantDb | |||
{ | |||
[DbContext(typeof(PersistedGrantDbContext))] | |||
partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot | |||
{ | |||
protected override void BuildModel(ModelBuilder modelBuilder) | |||
{ | |||
#pragma warning disable 612, 618 | |||
modelBuilder | |||
.HasAnnotation("ProductVersion", "6.2.0") | |||
.HasAnnotation("Relational:MaxIdentifierLength", 128); | |||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => | |||
{ | |||
b.Property<string>("UserCode") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("ClientId") | |||
.IsRequired() | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime>("CreationTime") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasMaxLength(50000) | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<string>("Description") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("DeviceCode") | |||
.IsRequired() | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime?>("Expiration") | |||
.IsRequired() | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("SessionId") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SubjectId") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.HasKey("UserCode"); | |||
b.HasIndex("DeviceCode") | |||
.IsUnique(); | |||
b.HasIndex("Expiration"); | |||
b.ToTable("DeviceCodes", (string)null); | |||
}); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.Key", b => | |||
{ | |||
b.Property<string>("Id") | |||
.HasColumnType("nvarchar(450)"); | |||
b.Property<string>("Algorithm") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<DateTime>("Created") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<bool>("DataProtected") | |||
.HasColumnType("bit"); | |||
b.Property<bool>("IsX509Certificate") | |||
.HasColumnType("bit"); | |||
b.Property<string>("Use") | |||
.HasColumnType("nvarchar(450)"); | |||
b.Property<int>("Version") | |||
.HasColumnType("int"); | |||
b.HasKey("Id"); | |||
b.HasIndex("Use"); | |||
b.ToTable("Keys", (string)null); | |||
}); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => | |||
{ | |||
b.Property<long>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasColumnType("bigint"); | |||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1); | |||
b.Property<string>("ClientId") | |||
.IsRequired() | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime?>("ConsumedTime") | |||
.HasColumnType("datetime2"); | |||
b.Property<DateTime>("CreationTime") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasMaxLength(50000) | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<string>("Description") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<DateTime?>("Expiration") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Key") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("SessionId") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SubjectId") | |||
.HasMaxLength(200) | |||
.HasColumnType("nvarchar(200)"); | |||
b.Property<string>("Type") | |||
.IsRequired() | |||
.HasMaxLength(50) | |||
.HasColumnType("nvarchar(50)"); | |||
b.HasKey("Id"); | |||
b.HasIndex("ConsumedTime"); | |||
b.HasIndex("Expiration"); | |||
b.HasIndex("Key") | |||
.IsUnique() | |||
.HasFilter("[Key] IS NOT NULL"); | |||
b.HasIndex("SubjectId", "ClientId", "Type"); | |||
b.HasIndex("SubjectId", "SessionId", "Type"); | |||
b.ToTable("PersistedGrants", (string)null); | |||
}); | |||
modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.ServerSideSession", b => | |||
{ | |||
b.Property<int>("Id") | |||
.ValueGeneratedOnAdd() | |||
.HasColumnType("int"); | |||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"), 1L, 1); | |||
b.Property<DateTime>("Created") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Data") | |||
.IsRequired() | |||
.HasColumnType("nvarchar(max)"); | |||
b.Property<string>("DisplayName") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<DateTime?>("Expires") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Key") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<DateTime>("Renewed") | |||
.HasColumnType("datetime2"); | |||
b.Property<string>("Scheme") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SessionId") | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.Property<string>("SubjectId") | |||
.IsRequired() | |||
.HasMaxLength(100) | |||
.HasColumnType("nvarchar(100)"); | |||
b.HasKey("Id"); | |||
b.HasIndex("DisplayName"); | |||
b.HasIndex("Expires"); | |||
b.HasIndex("Key") | |||
.IsUnique(); | |||
b.HasIndex("SessionId"); | |||
b.HasIndex("SubjectId"); | |||
b.ToTable("ServerSideSessions", (string)null); | |||
}); | |||
#pragma warning restore 612, 618 | |||
} | |||
} | |||
} |
@ -0,0 +1,117 @@ | |||
using Serilog; | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API; | |||
public static class ProgramExtensions | |||
{ | |||
private const string AppName = "Identity API"; | |||
public static void AddCustomConfiguration(this WebApplicationBuilder builder) | |||
{ | |||
builder.Configuration.AddConfiguration(GetConfiguration()).Build(); | |||
} | |||
public static void AddCustomSerilog(this WebApplicationBuilder builder) | |||
{ | |||
var seqServerUrl = builder.Configuration["SeqServerUrl"]; | |||
var logstashUrl = builder.Configuration["LogstashgUrl"]; | |||
Log.Logger = new LoggerConfiguration() | |||
.MinimumLevel.Verbose() | |||
.Enrich.WithProperty("ApplicationContext", AppName) | |||
.Enrich.FromLogContext() | |||
.WriteTo.Console() | |||
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) | |||
.WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://localhost:8080" : logstashUrl, null) | |||
.ReadFrom.Configuration(builder.Configuration) | |||
.CreateLogger(); | |||
builder.Host.UseSerilog(); | |||
} | |||
public static void AddCustomMvc(this WebApplicationBuilder builder) | |||
{ | |||
builder.Services.AddControllersWithViews(); | |||
builder.Services.AddControllers(); | |||
builder.Services.AddRazorPages(); | |||
} | |||
public static void AddCustomDatabase(this WebApplicationBuilder builder) => | |||
builder.Services.AddDbContext<ApplicationDbContext>( | |||
options => options.UseSqlServer(builder.Configuration["ConnectionString"])); | |||
public static void AddCustomIdentity(this WebApplicationBuilder builder) | |||
{ | |||
builder.Services.AddIdentity<ApplicationUser, IdentityRole>() | |||
.AddEntityFrameworkStores<ApplicationDbContext>() | |||
.AddDefaultTokenProviders(); | |||
} | |||
public static void AddCustomIdentityServer(this WebApplicationBuilder builder) | |||
{ | |||
var identityServerBuilder = builder.Services.AddIdentityServer(options => | |||
{ | |||
options.IssuerUri = builder.Configuration["IssuerUrl"]; | |||
options.Authentication.CookieLifetime = TimeSpan.FromHours(2); | |||
options.Events.RaiseErrorEvents = true; | |||
options.Events.RaiseInformationEvents = true; | |||
options.Events.RaiseFailureEvents = true; | |||
options.Events.RaiseSuccessEvents = true; | |||
}) | |||
.AddInMemoryIdentityResources(Config.GetResources()) | |||
.AddInMemoryApiScopes(Config.GetApiScopes()) | |||
.AddInMemoryApiResources(Config.GetApis()) | |||
.AddInMemoryClients(Config.GetClients(builder.Configuration)) | |||
.AddAspNetIdentity<ApplicationUser>(); | |||
// not recommended for production - you need to store your key material somewhere secure | |||
identityServerBuilder.AddDeveloperSigningCredential(); | |||
} | |||
public static void AddCustomAuthentication(this WebApplicationBuilder builder) | |||
{ | |||
builder.Services.AddAuthentication(); | |||
} | |||
public static void AddCustomHealthChecks(this WebApplicationBuilder builder) | |||
{ | |||
builder.Services.AddHealthChecks() | |||
.AddCheck("self", () => HealthCheckResult.Healthy()) | |||
.AddSqlServer(builder.Configuration["ConnectionString"], | |||
name: "IdentityDB-check", | |||
tags: new string[] { "IdentityDB" }); | |||
} | |||
public static void AddCustomApplicationServices(this WebApplicationBuilder builder) | |||
{ | |||
builder.Services.AddTransient<IProfileService, ProfileService>(); | |||
builder.Services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>(); | |||
builder.Services.AddTransient<IRedirectService, RedirectService>(); | |||
} | |||
static IConfiguration GetConfiguration() | |||
{ | |||
var builder = new ConfigurationBuilder() | |||
.SetBasePath(Directory.GetCurrentDirectory()) | |||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) | |||
.AddEnvironmentVariables(); | |||
var config = builder.Build(); | |||
if (config.GetValue<bool>("UseVault", false)) | |||
{ | |||
TokenCredential credential = new ClientSecretCredential( | |||
config["Vault:TenantId"], | |||
config["Vault:ClientId"], | |||
config["Vault:ClientSecret"]); | |||
builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); | |||
} | |||
return builder.Build(); | |||
} | |||
} |
@ -0,0 +1,341 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
using Duende.IdentityServer.Events; | |||
using Duende.IdentityServer.Extensions; | |||
using Duende.IdentityServer.Stores; | |||
using Microsoft.AspNetCore.Authentication; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace IdentityServerHost.Quickstart.UI | |||
{ | |||
[SecurityHeaders] | |||
[AllowAnonymous] | |||
public class AccountController : Controller | |||
{ | |||
private readonly UserManager<ApplicationUser> _userManager; | |||
private readonly SignInManager<ApplicationUser> _signInManager; | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IClientStore _clientStore; | |||
private readonly IAuthenticationSchemeProvider _schemeProvider; | |||
private readonly IAuthenticationHandlerProvider _handlerProvider; | |||
private readonly IEventService _events; | |||
public AccountController( | |||
UserManager<ApplicationUser> userManager, | |||
SignInManager<ApplicationUser> signInManager, | |||
IIdentityServerInteractionService interaction, | |||
IClientStore clientStore, | |||
IAuthenticationSchemeProvider schemeProvider, | |||
IAuthenticationHandlerProvider handlerProvider, | |||
IEventService events) | |||
{ | |||
_userManager = userManager; | |||
_signInManager = signInManager; | |||
_interaction = interaction; | |||
_clientStore = clientStore; | |||
_schemeProvider = schemeProvider; | |||
_handlerProvider = handlerProvider; | |||
_events = events; | |||
} | |||
/// <summary> | |||
/// Entry point into the login workflow | |||
/// </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 BuildLoginViewModelAsync(returnUrl); | |||
ViewData["ReturnUrl"] = returnUrl; | |||
if (vm.IsExternalLoginOnly) | |||
{ | |||
// we only have one option for logging in and it's an external provider | |||
return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); | |||
} | |||
return View(vm); | |||
} | |||
/// <summary> | |||
/// Handle postback from username/password login | |||
/// </summary> | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> Login(LoginInputModel model, string button) | |||
{ | |||
// check if we are in the context of an authorization request | |||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); | |||
// the user clicked the "cancel" button | |||
if (button != "login") | |||
{ | |||
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.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); | |||
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null | |||
if (context.IsNativeClient()) | |||
{ | |||
// The client is native, so this change in how to | |||
// return the response is for better UX for the end user. | |||
return this.LoadingPage("Redirect", model.ReturnUrl); | |||
} | |||
return Redirect(model.ReturnUrl); | |||
} | |||
else | |||
{ | |||
// since we don't have a valid context, then we just go back to the home page | |||
return Redirect("~/"); | |||
} | |||
} | |||
if (ModelState.IsValid) | |||
{ | |||
var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true); | |||
if (result.Succeeded) | |||
{ | |||
var user = await _userManager.FindByNameAsync(model.Username); | |||
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); | |||
if (context != null) | |||
{ | |||
if (context.IsNativeClient()) | |||
{ | |||
// The client is native, so this change in how to | |||
// return the response is for better UX for the end user. | |||
return this.LoadingPage("Redirect", model.ReturnUrl); | |||
} | |||
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null | |||
return Redirect(model.ReturnUrl); | |||
} | |||
// request for a local page | |||
if (Url.IsLocalUrl(model.ReturnUrl)) | |||
{ | |||
return Redirect(model.ReturnUrl); | |||
} | |||
else if (string.IsNullOrEmpty(model.ReturnUrl)) | |||
{ | |||
return Redirect("~/"); | |||
} | |||
else | |||
{ | |||
// user might have clicked on a malicious link - should be logged | |||
throw new Exception("invalid return URL"); | |||
} | |||
} | |||
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)); | |||
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); | |||
} | |||
// something went wrong, show form with error | |||
var vm = await BuildLoginViewModelAsync(model); | |||
ViewData["ReturnUrl"] = model.ReturnUrl; | |||
return View(vm); | |||
} | |||
/// <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 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) | |||
{ | |||
// build a model so the logged out page knows what to display | |||
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); | |||
if (User?.Identity.IsAuthenticated == true) | |||
{ | |||
// delete local authentication cookie | |||
await _signInManager.SignOutAsync(); | |||
// raise the logout event | |||
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); | |||
} | |||
// check if we need to trigger sign-out at an upstream identity provider | |||
if (vm.TriggerExternalSignout) | |||
{ | |||
// build a return URL so the upstream provider will redirect back | |||
// to us after the user has logged out. this allows us to then | |||
// complete our single sign-out processing. | |||
string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); | |||
// this triggers a redirect to the external provider for sign-out | |||
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); | |||
} | |||
return View("LoggedOut", vm); | |||
} | |||
[HttpGet] | |||
public IActionResult AccessDenied() | |||
{ | |||
return View(); | |||
} | |||
/*****************************************/ | |||
/* helper APIs for the AccountController */ | |||
/*****************************************/ | |||
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl) | |||
{ | |||
var context = await _interaction.GetAuthorizationContextAsync(returnUrl); | |||
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) | |||
{ | |||
var local = context.IdP == IdentityServerConstants.LocalIdentityProvider; | |||
// this is meant to short circuit the UI and only trigger the one external IdP | |||
var vm = new LoginViewModel | |||
{ | |||
EnableLocalLogin = local, | |||
ReturnUrl = returnUrl, | |||
Username = context?.LoginHint, | |||
}; | |||
if (!local) | |||
{ | |||
vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; | |||
} | |||
return vm; | |||
} | |||
var schemes = await _schemeProvider.GetAllSchemesAsync(); | |||
var providers = schemes | |||
.Where(x => x.DisplayName != null) | |||
.Select(x => new ExternalProvider | |||
{ | |||
DisplayName = x.DisplayName ?? x.Name, | |||
AuthenticationScheme = x.Name | |||
}).ToList(); | |||
var allowLocal = true; | |||
if (context?.Client.ClientId != null) | |||
{ | |||
var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); | |||
if (client != null) | |||
{ | |||
allowLocal = client.EnableLocalLogin; | |||
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) | |||
{ | |||
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); | |||
} | |||
} | |||
} | |||
return new LoginViewModel | |||
{ | |||
AllowRememberLogin = AccountOptions.AllowRememberLogin, | |||
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, | |||
ReturnUrl = returnUrl, | |||
Username = context?.LoginHint, | |||
ExternalProviders = providers.ToArray() | |||
}; | |||
} | |||
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginInputModel model) | |||
{ | |||
var vm = await BuildLoginViewModelAsync(model.ReturnUrl); | |||
vm.Username = model.Username; | |||
vm.RememberLogin = model.RememberLogin; | |||
return vm; | |||
} | |||
private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId) | |||
{ | |||
var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; | |||
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; | |||
} | |||
private 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 = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, | |||
SignOutIframeUrl = logout?.SignOutIFrameUrl, | |||
LogoutId = logoutId | |||
}; | |||
if (User?.Identity.IsAuthenticated == true) | |||
{ | |||
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; | |||
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) | |||
{ | |||
var handler = await _handlerProvider.GetHandlerAsync(HttpContext, idp); | |||
if (handler is IAuthenticationSignOutHandler) | |||
{ | |||
if (vm.LogoutId == null) | |||
{ | |||
// if there's no current logout context, we need to create one | |||
// this captures necessary info from the current logged in user | |||
// before we signout and redirect away to the external IdP for signout | |||
vm.LogoutId = await _interaction.CreateLogoutContextAsync(); | |||
} | |||
vm.ExternalAuthenticationScheme = idp; | |||
} | |||
} | |||
} | |||
return vm; | |||
} | |||
} | |||
} |
@ -0,0 +1,16 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class AccountOptions | |||
{ | |||
public static bool AllowLocalLogin = true; | |||
public static bool AllowRememberLogin = true; | |||
public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); | |||
public static bool ShowLogoutPrompt = false; | |||
public static bool AutomaticRedirectAfterSignOut = true; | |||
public static string InvalidCredentialsErrorMessage = "Invalid username or password"; | |||
} |
@ -0,0 +1,238 @@ | |||
namespace IdentityServerHost.Quickstart.UI; | |||
[SecurityHeaders] | |||
[AllowAnonymous] | |||
public class ExternalController : Controller | |||
{ | |||
private readonly UserManager<ApplicationUser> _userManager; | |||
private readonly SignInManager<ApplicationUser> _signInManager; | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IClientStore _clientStore; | |||
private readonly IEventService _events; | |||
private readonly ILogger<ExternalController> _logger; | |||
public ExternalController( | |||
UserManager<ApplicationUser> userManager, | |||
SignInManager<ApplicationUser> signInManager, | |||
IIdentityServerInteractionService interaction, | |||
IClientStore clientStore, | |||
IEventService events, | |||
ILogger<ExternalController> logger) | |||
{ | |||
_userManager = userManager; | |||
_signInManager = signInManager; | |||
_interaction = interaction; | |||
_clientStore = clientStore; | |||
_events = events; | |||
_logger = logger; | |||
} | |||
/// <summary> | |||
/// initiate roundtrip to external authentication provider | |||
/// </summary> | |||
[HttpGet] | |||
public IActionResult Challenge(string scheme, string returnUrl) | |||
{ | |||
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; | |||
// validate returnUrl - either it is a valid OIDC URL or back to a local page | |||
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) | |||
{ | |||
// user might have clicked on a malicious link - should be logged | |||
throw new Exception("invalid return URL"); | |||
} | |||
// start challenge and roundtrip the return URL and scheme | |||
var props = new AuthenticationProperties | |||
{ | |||
RedirectUri = Url.Action(nameof(Callback)), | |||
Items = | |||
{ | |||
{ "returnUrl", returnUrl }, | |||
{ "scheme", scheme }, | |||
} | |||
}; | |||
return Challenge(props, scheme); | |||
} | |||
/// <summary> | |||
/// Post processing of external authentication | |||
/// </summary> | |||
[HttpGet] | |||
public async Task<IActionResult> Callback() | |||
{ | |||
// read external identity from the temporary cookie | |||
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); | |||
if (result?.Succeeded != true) | |||
{ | |||
throw new Exception("External authentication error"); | |||
} | |||
if (_logger.IsEnabled(LogLevel.Debug)) | |||
{ | |||
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); | |||
_logger.LogDebug("External claims: {@claims}", externalClaims); | |||
} | |||
// lookup our user and external provider info | |||
var (user, provider, providerUserId, claims) = await FindUserFromExternalProviderAsync(result); | |||
if (user == null) | |||
{ | |||
// this might be where you might initiate a custom workflow for user registration | |||
// in this sample we don't show how that would be done, as our sample implementation | |||
// simply auto-provisions new external user | |||
user = await AutoProvisionUserAsync(provider, providerUserId, claims); | |||
} | |||
// this allows us to collect any additional claims or properties | |||
// for the specific protocols used and store them in the local auth cookie. | |||
// this is typically used to store data needed for signout from those protocols. | |||
var additionalLocalClaims = new List<Claim>(); | |||
var localSignInProps = new AuthenticationProperties(); | |||
ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); | |||
// issue authentication cookie for user | |||
// we must issue the cookie maually, and can't use the SignInManager because | |||
// it doesn't expose an API to issue additional claims from the login workflow | |||
var principal = await _signInManager.CreateUserPrincipalAsync(user); | |||
additionalLocalClaims.AddRange(principal.Claims); | |||
var name = principal.FindFirst(JwtClaimTypes.Name)?.Value ?? user.Id; | |||
var isuser = new IdentityServerUser(user.Id) | |||
{ | |||
DisplayName = name, | |||
IdentityProvider = provider, | |||
AdditionalClaims = additionalLocalClaims | |||
}; | |||
await HttpContext.SignInAsync(isuser, localSignInProps); | |||
// delete temporary cookie used during external authentication | |||
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); | |||
// retrieve return URL | |||
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; | |||
// check if external login is in the context of an OIDC request | |||
var context = await _interaction.GetAuthorizationContextAsync(returnUrl); | |||
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, name, true, context?.Client.ClientId)); | |||
if (context != null) | |||
{ | |||
if (context.IsNativeClient()) | |||
{ | |||
// The client is native, so this change in how to | |||
// return the response is for better UX for the end user. | |||
return this.LoadingPage("Redirect", returnUrl); | |||
} | |||
} | |||
return Redirect(returnUrl); | |||
} | |||
private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable<Claim> claims)> | |||
FindUserFromExternalProviderAsync(AuthenticateResult result) | |||
{ | |||
var externalUser = result.Principal; | |||
// try to determine the unique id of the external user (issued by the provider) | |||
// the most common claim type for that are the sub claim and the NameIdentifier | |||
// depending on the external provider, some other claim type might be used | |||
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? | |||
externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? | |||
throw new Exception("Unknown userid"); | |||
// remove the user id claim so we don't include it as an extra claim if/when we provision the user | |||
var claims = externalUser.Claims.ToList(); | |||
claims.Remove(userIdClaim); | |||
var provider = result.Properties.Items["scheme"]; | |||
var providerUserId = userIdClaim.Value; | |||
// find external user | |||
var user = await _userManager.FindByLoginAsync(provider, providerUserId); | |||
return (user, provider, providerUserId, claims); | |||
} | |||
private async Task<ApplicationUser> AutoProvisionUserAsync(string provider, string providerUserId, IEnumerable<Claim> claims) | |||
{ | |||
// create a list of claims that we want to transfer into our store | |||
var filtered = new List<Claim>(); | |||
// user's display name | |||
var name = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Name)?.Value ?? | |||
claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value; | |||
if (name != null) | |||
{ | |||
filtered.Add(new Claim(JwtClaimTypes.Name, name)); | |||
} | |||
else | |||
{ | |||
var first = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value ?? | |||
claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value; | |||
var last = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value ?? | |||
claims.FirstOrDefault(x => x.Type == ClaimTypes.Surname)?.Value; | |||
if (first != null && last != null) | |||
{ | |||
filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last)); | |||
} | |||
else if (first != null) | |||
{ | |||
filtered.Add(new Claim(JwtClaimTypes.Name, first)); | |||
} | |||
else if (last != null) | |||
{ | |||
filtered.Add(new Claim(JwtClaimTypes.Name, last)); | |||
} | |||
} | |||
var email = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Email)?.Value ?? | |||
claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value; | |||
if (email != null) | |||
{ | |||
filtered.Add(new Claim(JwtClaimTypes.Email, email)); | |||
} | |||
var user = new ApplicationUser | |||
{ | |||
UserName = Guid.NewGuid().ToString(), | |||
}; | |||
var identityResult = await _userManager.CreateAsync(user); | |||
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); | |||
if (filtered.Any()) | |||
{ | |||
identityResult = await _userManager.AddClaimsAsync(user, filtered); | |||
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); | |||
} | |||
identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(provider, providerUserId, provider)); | |||
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description); | |||
return user; | |||
} | |||
// if the external login is OIDC-based, there are certain things we need to preserve to make logout work | |||
// this will be different for WS-Fed, SAML2p or other protocols | |||
private void ProcessLoginCallback(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps) | |||
{ | |||
// if the external system sent a session id claim, copy it over | |||
// so we can use it for single sign-out | |||
var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); | |||
if (sid != null) | |||
{ | |||
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); | |||
} | |||
// if the external provider issued an id_token, we'll keep it for signout | |||
var idToken = externalResult.Properties.GetTokenValue("id_token"); | |||
if (idToken != null) | |||
{ | |||
localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); | |||
} | |||
} | |||
} |
@ -0,0 +1,10 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class ExternalProvider | |||
{ | |||
public string DisplayName { get; set; } | |||
public string AuthenticationScheme { get; set; } | |||
} |
@ -0,0 +1,18 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
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; } | |||
public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; | |||
public string ExternalAuthenticationScheme { get; set; } | |||
} |
@ -0,0 +1,15 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class LoginInputModel | |||
{ | |||
[Required] | |||
public string Username { get; set; } | |||
[Required] | |||
public string Password { get; set; } | |||
public bool RememberLogin { get; set; } | |||
public string ReturnUrl { get; set; } | |||
} |
@ -0,0 +1,17 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class LoginViewModel : LoginInputModel | |||
{ | |||
public bool AllowRememberLogin { get; set; } = true; | |||
public bool EnableLocalLogin { get; set; } = true; | |||
public IEnumerable<ExternalProvider> ExternalProviders { get; set; } = Enumerable.Empty<ExternalProvider>(); | |||
public IEnumerable<ExternalProvider> VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); | |||
public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; | |||
public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; | |||
} |
@ -0,0 +1,10 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class LogoutInputModel | |||
{ | |||
public string LogoutId { get; set; } | |||
} |
@ -0,0 +1,10 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class LogoutViewModel : LogoutInputModel | |||
{ | |||
public bool ShowLogoutPrompt { get; set; } = true; | |||
} |
@ -0,0 +1,11 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class RedirectViewModel | |||
{ | |||
public string RedirectUrl { get; set; } | |||
} |
@ -0,0 +1,247 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
/// <summary> | |||
/// This controller processes the consent UI | |||
/// </summary> | |||
[SecurityHeaders] | |||
[Authorize] | |||
public class ConsentController : Controller | |||
{ | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IEventService _events; | |||
private readonly ILogger<ConsentController> _logger; | |||
public ConsentController( | |||
IIdentityServerInteractionService interaction, | |||
IEventService events, | |||
ILogger<ConsentController> logger) | |||
{ | |||
_interaction = interaction; | |||
_events = events; | |||
_logger = logger; | |||
} | |||
/// <summary> | |||
/// Shows the consent screen | |||
/// </summary> | |||
/// <param name="returnUrl"></param> | |||
/// <returns></returns> | |||
[HttpGet] | |||
public async Task<IActionResult> Index(string returnUrl) | |||
{ | |||
var vm = await 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 ProcessConsent(model); | |||
if (result.IsRedirect) | |||
{ | |||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); | |||
if (context?.IsNativeClient() == true) | |||
{ | |||
// The client is native, so this change in how to | |||
// return the response is for better UX for the end user. | |||
return this.LoadingPage("Redirect", result.RedirectUri); | |||
} | |||
return Redirect(result.RedirectUri); | |||
} | |||
if (result.HasValidationError) | |||
{ | |||
ModelState.AddModelError(string.Empty, result.ValidationError); | |||
} | |||
if (result.ShowView) | |||
{ | |||
return View("Index", result.ViewModel); | |||
} | |||
return View("Error"); | |||
} | |||
/*****************************************/ | |||
/* helper APIs for the ConsentController */ | |||
/*****************************************/ | |||
private async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model) | |||
{ | |||
var result = new ProcessConsentResult(); | |||
// validate return url is still valid | |||
var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); | |||
if (request == null) return result; | |||
ConsentResponse grantedConsent = null; | |||
// user clicked 'no' - send back the standard 'access_denied' response | |||
if (model?.Button == "no") | |||
{ | |||
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; | |||
// emit event | |||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); | |||
} | |||
// user clicked 'yes' - validate the data | |||
else if (model?.Button == "yes") | |||
{ | |||
// 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 != IdentityServerConstants.StandardScopes.OfflineAccess); | |||
} | |||
grantedConsent = new ConsentResponse | |||
{ | |||
RememberConsent = model.RememberConsent, | |||
ScopesValuesConsented = scopes.ToArray(), | |||
Description = model.Description | |||
}; | |||
// emit event | |||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); | |||
} | |||
else | |||
{ | |||
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; | |||
} | |||
} | |||
else | |||
{ | |||
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; | |||
} | |||
if (grantedConsent != null) | |||
{ | |||
// 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; | |||
result.Client = request.Client; | |||
} | |||
else | |||
{ | |||
// we need to redisplay the consent UI | |||
result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); | |||
} | |||
return result; | |||
} | |||
private async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) | |||
{ | |||
var request = await _interaction.GetAuthorizationContextAsync(returnUrl); | |||
if (request != null) | |||
{ | |||
return CreateConsentViewModel(model, returnUrl, request); | |||
} | |||
else | |||
{ | |||
_logger.LogError("No consent request matching request: {0}", returnUrl); | |||
} | |||
return null; | |||
} | |||
private ConsentViewModel CreateConsentViewModel( | |||
ConsentInputModel model, string returnUrl, | |||
AuthorizationRequest request) | |||
{ | |||
var vm = new ConsentViewModel | |||
{ | |||
RememberConsent = model?.RememberConsent ?? true, | |||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(), | |||
Description = model?.Description, | |||
ReturnUrl = returnUrl, | |||
ClientName = request.Client.ClientName ?? request.Client.ClientId, | |||
ClientUrl = request.Client.ClientUri, | |||
ClientLogoUrl = request.Client.LogoUri, | |||
AllowRememberConsent = request.Client.AllowRememberConsent | |||
}; | |||
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); | |||
var apiScopes = new List<ScopeViewModel>(); | |||
foreach (var parsedScope in request.ValidatedResources.ParsedScopes) | |||
{ | |||
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); | |||
if (apiScope != null) | |||
{ | |||
var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); | |||
apiScopes.Add(scopeVm); | |||
} | |||
} | |||
if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) | |||
{ | |||
apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); | |||
} | |||
vm.ApiScopes = apiScopes; | |||
return vm; | |||
} | |||
private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = identity.Name, | |||
DisplayName = identity.DisplayName ?? identity.Name, | |||
Description = identity.Description, | |||
Emphasize = identity.Emphasize, | |||
Required = identity.Required, | |||
Checked = check || identity.Required | |||
}; | |||
} | |||
public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) | |||
{ | |||
var displayName = apiScope.DisplayName ?? apiScope.Name; | |||
if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) | |||
{ | |||
displayName += ":" + parsedScopeValue.ParsedParameter; | |||
} | |||
return new ScopeViewModel | |||
{ | |||
Value = parsedScopeValue.RawValue, | |||
DisplayName = displayName, | |||
Description = apiScope.Description, | |||
Emphasize = apiScope.Emphasize, | |||
Required = apiScope.Required, | |||
Checked = check || apiScope.Required | |||
}; | |||
} | |||
private ScopeViewModel GetOfflineAccessScope(bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = IdentityServerConstants.StandardScopes.OfflineAccess, | |||
DisplayName = ConsentOptions.OfflineAccessDisplayName, | |||
Description = ConsentOptions.OfflineAccessDescription, | |||
Emphasize = true, | |||
Checked = check | |||
}; | |||
} | |||
} |
@ -0,0 +1,14 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class ConsentInputModel | |||
{ | |||
public string Button { get; set; } | |||
public IEnumerable<string> ScopesConsented { get; set; } | |||
public bool RememberConsent { get; set; } | |||
public string ReturnUrl { get; set; } | |||
public string Description { get; set; } | |||
} |
@ -0,0 +1,15 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
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"; | |||
} |
@ -0,0 +1,16 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
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> ApiScopes { get; set; } | |||
} |
@ -0,0 +1,17 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class ProcessConsentResult | |||
{ | |||
public bool IsRedirect => RedirectUri != null; | |||
public string RedirectUri { get; set; } | |||
public Client Client { get; set; } | |||
public bool ShowView => ViewModel != null; | |||
public ConsentViewModel ViewModel { get; set; } | |||
public bool HasValidationError => ValidationError != null; | |||
public string ValidationError { get; set; } | |||
} |
@ -0,0 +1,15 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class ScopeViewModel | |||
{ | |||
public string Value { 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; } | |||
} |
@ -0,0 +1,10 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class DeviceAuthorizationInputModel : ConsentInputModel | |||
{ | |||
public string UserCode { get; set; } | |||
} |
@ -0,0 +1,11 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class DeviceAuthorizationViewModel : ConsentViewModel | |||
{ | |||
public string UserCode { get; set; } | |||
public bool ConfirmUserCode { get; set; } | |||
} |
@ -0,0 +1,214 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
[Authorize] | |||
[SecurityHeaders] | |||
public class DeviceController : Controller | |||
{ | |||
private readonly IDeviceFlowInteractionService _interaction; | |||
private readonly IEventService _events; | |||
private readonly IOptions<IdentityServerOptions> _options; | |||
private readonly ILogger<DeviceController> _logger; | |||
public DeviceController( | |||
IDeviceFlowInteractionService interaction, | |||
IEventService eventService, | |||
IOptions<IdentityServerOptions> options, | |||
ILogger<DeviceController> logger) | |||
{ | |||
_interaction = interaction; | |||
_events = eventService; | |||
_options = options; | |||
_logger = logger; | |||
} | |||
[HttpGet] | |||
public async Task<IActionResult> Index() | |||
{ | |||
string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; | |||
string userCode = Request.Query[userCodeParamName]; | |||
if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); | |||
var vm = await BuildViewModelAsync(userCode); | |||
if (vm == null) return View("Error"); | |||
vm.ConfirmUserCode = true; | |||
return View("UserCodeConfirmation", vm); | |||
} | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> UserCodeCapture(string userCode) | |||
{ | |||
var vm = await BuildViewModelAsync(userCode); | |||
if (vm == null) return View("Error"); | |||
return View("UserCodeConfirmation", vm); | |||
} | |||
[HttpPost] | |||
[ValidateAntiForgeryToken] | |||
public async Task<IActionResult> Callback(DeviceAuthorizationInputModel model) | |||
{ | |||
if (model == null) throw new ArgumentNullException(nameof(model)); | |||
var result = await ProcessConsent(model); | |||
if (result.HasValidationError) return View("Error"); | |||
return View("Success"); | |||
} | |||
private async Task<ProcessConsentResult> ProcessConsent(DeviceAuthorizationInputModel model) | |||
{ | |||
var result = new ProcessConsentResult(); | |||
var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); | |||
if (request == null) return result; | |||
ConsentResponse grantedConsent = null; | |||
// user clicked 'no' - send back the standard 'access_denied' response | |||
if (model.Button == "no") | |||
{ | |||
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; | |||
// emit event | |||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); | |||
} | |||
// user clicked 'yes' - validate the data | |||
else if (model.Button == "yes") | |||
{ | |||
// 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 != IdentityServerConstants.StandardScopes.OfflineAccess); | |||
} | |||
grantedConsent = new ConsentResponse | |||
{ | |||
RememberConsent = model.RememberConsent, | |||
ScopesValuesConsented = scopes.ToArray(), | |||
Description = model.Description | |||
}; | |||
// emit event | |||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); | |||
} | |||
else | |||
{ | |||
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; | |||
} | |||
} | |||
else | |||
{ | |||
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; | |||
} | |||
if (grantedConsent != null) | |||
{ | |||
// communicate outcome of consent back to identityserver | |||
await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); | |||
// indicate that's it ok to redirect back to authorization endpoint | |||
result.RedirectUri = model.ReturnUrl; | |||
result.Client = request.Client; | |||
} | |||
else | |||
{ | |||
// we need to redisplay the consent UI | |||
result.ViewModel = await BuildViewModelAsync(model.UserCode, model); | |||
} | |||
return result; | |||
} | |||
private async Task<DeviceAuthorizationViewModel> BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) | |||
{ | |||
var request = await _interaction.GetAuthorizationContextAsync(userCode); | |||
if (request != null) | |||
{ | |||
return CreateConsentViewModel(userCode, model, request); | |||
} | |||
return null; | |||
} | |||
private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) | |||
{ | |||
var vm = new DeviceAuthorizationViewModel | |||
{ | |||
UserCode = userCode, | |||
Description = model?.Description, | |||
RememberConsent = model?.RememberConsent ?? true, | |||
ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>(), | |||
ClientName = request.Client.ClientName ?? request.Client.ClientId, | |||
ClientUrl = request.Client.ClientUri, | |||
ClientLogoUrl = request.Client.LogoUri, | |||
AllowRememberConsent = request.Client.AllowRememberConsent | |||
}; | |||
vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); | |||
var apiScopes = new List<ScopeViewModel>(); | |||
foreach (var parsedScope in request.ValidatedResources.ParsedScopes) | |||
{ | |||
var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); | |||
if (apiScope != null) | |||
{ | |||
var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); | |||
apiScopes.Add(scopeVm); | |||
} | |||
} | |||
if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) | |||
{ | |||
apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); | |||
} | |||
vm.ApiScopes = apiScopes; | |||
return vm; | |||
} | |||
private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = identity.Name, | |||
DisplayName = identity.DisplayName ?? identity.Name, | |||
Description = identity.Description, | |||
Emphasize = identity.Emphasize, | |||
Required = identity.Required, | |||
Checked = check || identity.Required | |||
}; | |||
} | |||
public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = parsedScopeValue.RawValue, | |||
DisplayName = apiScope.DisplayName ?? apiScope.Name, | |||
Description = apiScope.Description, | |||
Emphasize = apiScope.Emphasize, | |||
Required = apiScope.Required, | |||
Checked = check || apiScope.Required | |||
}; | |||
} | |||
private ScopeViewModel GetOfflineAccessScope(bool check) | |||
{ | |||
return new ScopeViewModel | |||
{ | |||
Value = IdentityServerConstants.StandardScopes.OfflineAccess, | |||
DisplayName = ConsentOptions.OfflineAccessDisplayName, | |||
Description = ConsentOptions.OfflineAccessDescription, | |||
Emphasize = true, | |||
Checked = check | |||
}; | |||
} | |||
} |
@ -0,0 +1,22 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
[SecurityHeaders] | |||
[Authorize] | |||
public class DiagnosticsController : Controller | |||
{ | |||
public async Task<IActionResult> Index() | |||
{ | |||
var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; | |||
if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) | |||
{ | |||
return NotFound(); | |||
} | |||
var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); | |||
return View(model); | |||
} | |||
} |
@ -0,0 +1,28 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
using System.Text; | |||
using System.Text.Json; | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class DiagnosticsViewModel | |||
{ | |||
public DiagnosticsViewModel(AuthenticateResult result) | |||
{ | |||
AuthenticateResult = result; | |||
if (result.Properties.Items.ContainsKey("client_list")) | |||
{ | |||
var encoded = result.Properties.Items["client_list"]; | |||
var bytes = Base64Url.Decode(encoded); | |||
var value = Encoding.UTF8.GetString(bytes); | |||
Clients = JsonSerializer.Deserialize<string[]>(value); | |||
} | |||
} | |||
public AuthenticateResult AuthenticateResult { get; } | |||
public IEnumerable<string> Clients { get; } = new List<string>(); | |||
} |
@ -0,0 +1,22 @@ | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public static class Extensions | |||
{ | |||
/// <summary> | |||
/// Checks if the redirect URI is for a native client. | |||
/// </summary> | |||
/// <returns></returns> | |||
public static bool IsNativeClient(this AuthorizationRequest context) | |||
{ | |||
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) | |||
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); | |||
} | |||
public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) | |||
{ | |||
controller.HttpContext.Response.StatusCode = 200; | |||
controller.HttpContext.Response.Headers["Location"] = ""; | |||
return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); | |||
} | |||
} |
@ -0,0 +1,85 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
/// <summary> | |||
/// This sample controller allows a user to revoke grants given to clients | |||
/// </summary> | |||
[SecurityHeaders] | |||
[Authorize] | |||
public class GrantsController : Controller | |||
{ | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IClientStore _clients; | |||
private readonly IResourceStore _resources; | |||
private readonly IEventService _events; | |||
public GrantsController(IIdentityServerInteractionService interaction, | |||
IClientStore clients, | |||
IResourceStore resources, | |||
IEventService events) | |||
{ | |||
_interaction = interaction; | |||
_clients = clients; | |||
_resources = resources; | |||
_events = events; | |||
} | |||
/// <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); | |||
await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); | |||
return RedirectToAction("Index"); | |||
} | |||
private async Task<GrantsViewModel> BuildViewModelAsync() | |||
{ | |||
var grants = await _interaction.GetAllUserGrantsAsync(); | |||
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, | |||
Description = grant.Description, | |||
Created = grant.CreationTime, | |||
Expires = grant.Expiration, | |||
IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), | |||
ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() | |||
}; | |||
list.Add(item); | |||
} | |||
} | |||
return new GrantsViewModel | |||
{ | |||
Grants = list | |||
}; | |||
} | |||
} |
@ -0,0 +1,23 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
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 string Description { get; set; } | |||
public DateTime Created { get; set; } | |||
public DateTime? Expires { get; set; } | |||
public IEnumerable<string> IdentityGrantNames { get; set; } | |||
public IEnumerable<string> ApiGrantNames { get; set; } | |||
} |
@ -0,0 +1,18 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class ErrorViewModel | |||
{ | |||
public ErrorViewModel() | |||
{ | |||
} | |||
public ErrorViewModel(string error) | |||
{ | |||
Error = new ErrorMessage { Error = error }; | |||
} | |||
public ErrorMessage Error { get; set; } | |||
} |
@ -0,0 +1,63 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace IdentityServerHost.Quickstart.UI | |||
{ | |||
[SecurityHeaders] | |||
[AllowAnonymous] | |||
public class HomeController : Controller | |||
{ | |||
private readonly IIdentityServerInteractionService _interaction; | |||
private readonly IWebHostEnvironment _environment; | |||
private readonly ILogger _logger; | |||
public HomeController( | |||
IIdentityServerInteractionService interaction, | |||
IWebHostEnvironment environment, | |||
ILogger<HomeController> logger) | |||
{ | |||
_interaction = interaction; | |||
_environment = environment; | |||
_logger = logger; | |||
} | |||
public IActionResult Index() | |||
{ | |||
if (_environment.IsDevelopment()) | |||
{ | |||
// only show in development | |||
return View(); | |||
} | |||
_logger.LogInformation("Homepage is disabled in production. Returning 404."); | |||
return NotFound(); | |||
} | |||
/// <summary> | |||
/// Shows the error page | |||
/// </summary> | |||
public async Task<IActionResult> Error(string errorId) | |||
{ | |||
var vm = new ErrorViewModel(); | |||
// retrieve error details from identityserver | |||
var message = await _interaction.GetErrorContextAsync(errorId); | |||
if (message != null) | |||
{ | |||
vm.Error = message; | |||
if (!_environment.IsDevelopment()) | |||
{ | |||
// only show in development | |||
message.ErrorDescription = null; | |||
} | |||
} | |||
return View("Error", vm); | |||
} | |||
} | |||
} |
@ -0,0 +1,52 @@ | |||
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. | |||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | |||
namespace IdentityServerHost.Quickstart.UI; | |||
public class SecurityHeadersAttribute : ActionFilterAttribute | |||
{ | |||
public override void OnResultExecuting(ResultExecutingContext context) | |||
{ | |||
var result = context.Result; | |||
if (result is ViewResult) | |||
{ | |||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options | |||
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) | |||
{ | |||
context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); | |||
} | |||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options | |||
if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) | |||
{ | |||
context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); | |||
} | |||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy | |||
var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; | |||
// also consider adding upgrade-insecure-requests once you have HTTPS in place for production | |||
//csp += "upgrade-insecure-requests;"; | |||
// also an example if you need client images to be displayed from twitter | |||
// csp += "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); | |||
} | |||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy | |||
var referrer_policy = "no-referrer"; | |||
if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) | |||
{ | |||
context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,124 @@ | |||
using System.Threading.Tasks; | |||
using System; | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API; | |||
public class SeedData | |||
{ | |||
public static async Task EnsureSeedData(IServiceScope scope, IConfiguration configuration, Microsoft.Extensions.Logging.ILogger logger) | |||
{ | |||
var retryPolicy = CreateRetryPolicy(configuration, logger); | |||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); | |||
await retryPolicy.ExecuteAsync(async () => | |||
{ | |||
await context.Database.MigrateAsync(); | |||
var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>(); | |||
var alice = await userMgr.FindByNameAsync("alice"); | |||
if (alice == null) | |||
{ | |||
alice = new ApplicationUser | |||
{ | |||
UserName = "alice", | |||
Email = "AliceSmith@email.com", | |||
EmailConfirmed = true, | |||
CardHolderName = "Alice Smith", | |||
CardNumber = "4012888888881881", | |||
CardType = 1, | |||
City = "Redmond", | |||
Country = "U.S.", | |||
Expiration = "12/20", | |||
Id = Guid.NewGuid().ToString(), | |||
LastName = "Smith", | |||
Name = "Alice", | |||
PhoneNumber = "1234567890", | |||
ZipCode = "98052", | |||
State = "WA", | |||
Street = "15703 NE 61st Ct", | |||
SecurityNumber = "123" | |||
}; | |||
var result = userMgr.CreateAsync(alice, "Pass123$").Result; | |||
if (!result.Succeeded) | |||
{ | |||
throw new Exception(result.Errors.First().Description); | |||
} | |||
logger.LogDebug("alice created"); | |||
} | |||
else | |||
{ | |||
logger.LogDebug("alice already exists"); | |||
} | |||
var bob = await userMgr.FindByNameAsync("bob"); | |||
if (bob == null) | |||
{ | |||
bob = new ApplicationUser | |||
{ | |||
UserName = "bob", | |||
Email = "BobSmith@email.com", | |||
EmailConfirmed = true, | |||
CardHolderName = "Bob Smith", | |||
CardNumber = "4012888888881881", | |||
CardType = 1, | |||
City = "Redmond", | |||
Country = "U.S.", | |||
Expiration = "12/20", | |||
Id = Guid.NewGuid().ToString(), | |||
LastName = "Smith", | |||
Name = "Bob", | |||
PhoneNumber = "1234567890", | |||
ZipCode = "98052", | |||
State = "WA", | |||
Street = "15703 NE 61st Ct", | |||
SecurityNumber = "456" | |||
}; | |||
var result = await userMgr.CreateAsync(bob, "Pass123$"); | |||
if (!result.Succeeded) | |||
{ | |||
throw new Exception(result.Errors.First().Description); | |||
} | |||
logger.LogDebug("bob created"); | |||
} | |||
else | |||
{ | |||
logger.LogDebug("bob already exists"); | |||
} | |||
}); | |||
} | |||
private static AsyncPolicy CreateRetryPolicy(IConfiguration configuration, Microsoft.Extensions.Logging.ILogger logger) | |||
{ | |||
var retryMigrations = false; | |||
bool.TryParse(configuration["RetryMigrations"], out retryMigrations); | |||
// Only use a retry policy if configured to do so. | |||
// When running in an orchestrator/K8s, it will take care of restarting failed services. | |||
if (retryMigrations) | |||
{ | |||
return Policy.Handle<Exception>(). | |||
WaitAndRetryForeverAsync( | |||
sleepDurationProvider: retry => TimeSpan.FromSeconds(5), | |||
onRetry: (exception, retry, timeSpan) => | |||
{ | |||
logger.LogWarning( | |||
exception, | |||
"Exception {ExceptionType} with message {Message} detected during database migration (retry attempt {retry})", | |||
exception.GetType().Name, | |||
exception.Message, | |||
retry); | |||
} | |||
); | |||
} | |||
return Policy.NoOpAsync(); | |||
} | |||
} |
@ -1,2 +0,0 @@ | |||
CardHolderName,CardNumber,CardType,City,Country,Email,Expiration,LastName,Name,PhoneNumber,UserName,ZipCode,State,Street,SecurityNumber,NormalizedEmail,NormalizedUserName,Password | |||
DemoUser,4012888888881881,1,Redmond,U.S.,demouser@microsoft.com,12/25,DemoLastName,DemoUser,1234567890,demouser@microsoft.com,98052,WA,15703 NE 61st Ct,535,DEMOUSER@MICROSOFT.COM,DEMOUSER@MICROSOFT.COM,Pass@word1 |
@ -1,167 +0,0 @@ | |||
using Microsoft.AspNetCore.DataProtection; | |||
namespace Microsoft.eShopOnContainers.Services.Identity.API | |||
{ | |||
public class Startup | |||
{ | |||
public Startup(IConfiguration configuration) | |||
{ | |||
Configuration = configuration; | |||
} | |||
public IConfiguration Configuration { get; } | |||
// This method gets called by the runtime. Use this method to add services to the container. | |||
public IServiceProvider ConfigureServices(IServiceCollection services) | |||
{ | |||
RegisterAppInsights(services); | |||
// Add framework services. | |||
services.AddDbContext<ApplicationDbContext>(options => | |||
options.UseSqlServer(Configuration["ConnectionString"], | |||
sqlServerOptionsAction: sqlOptions => | |||
{ | |||
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); | |||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); | |||
})); | |||
services.AddIdentity<ApplicationUser, IdentityRole>() | |||
.AddEntityFrameworkStores<ApplicationDbContext>() | |||
.AddDefaultTokenProviders(); | |||
services.Configure<AppSettings>(Configuration); | |||
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString) | |||
{ | |||
services.AddDataProtection(opts => | |||
{ | |||
opts.ApplicationDiscriminator = "eshop.identity"; | |||
}) | |||
.PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys"); | |||
} | |||
services.AddHealthChecks() | |||
.AddCheck("self", () => HealthCheckResult.Healthy()) | |||
.AddSqlServer(Configuration["ConnectionString"], | |||
name: "IdentityDB-check", | |||
tags: new string[] { "IdentityDB" }); | |||
services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>(); | |||
services.AddTransient<IRedirectService, RedirectService>(); | |||
var connectionString = Configuration["ConnectionString"]; | |||
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; | |||
// Adds IdentityServer | |||
services.AddIdentityServer(x => | |||
{ | |||
x.IssuerUri = "null"; | |||
x.Authentication.CookieLifetime = TimeSpan.FromHours(2); | |||
}) | |||
.AddServerSideSessions() | |||
.AddDevspacesIfNeeded(Configuration.GetValue("EnableDevspaces", false)) | |||
.AddSigningCredential(Certificate.Get()) | |||
.AddAspNetIdentity<ApplicationUser>() | |||
.AddConfigurationStore(options => | |||
{ | |||
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, | |||
sqlServerOptionsAction: sqlOptions => | |||
{ | |||
sqlOptions.MigrationsAssembly(migrationsAssembly); | |||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); | |||
}); | |||
}) | |||
.AddOperationalStore(options => | |||
{ | |||
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, | |||
sqlServerOptionsAction: sqlOptions => | |||
{ | |||
sqlOptions.MigrationsAssembly(migrationsAssembly); | |||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); | |||
}); | |||
}) | |||
.Services.AddTransient<IProfileService, ProfileService>(); | |||
//services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); | |||
services.AddControllers(); | |||
services.AddControllersWithViews(); | |||
services.AddRazorPages(); | |||
var container = new ContainerBuilder(); | |||
container.Populate(services); | |||
return new AutofacServiceProvider(container.Build()); | |||
} | |||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||
{ | |||
//loggerFactory.AddConsole(Configuration.GetSection("Logging")); | |||
//loggerFactory.AddDebug(); | |||
//loggerFactory.AddAzureWebAppDiagnostics(); | |||
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); | |||
if (env.IsDevelopment()) | |||
{ | |||
app.UseDeveloperExceptionPage(); | |||
} | |||
else | |||
{ | |||
app.UseExceptionHandler("/Home/Error"); | |||
} | |||
var pathBase = Configuration["PATH_BASE"]; | |||
if (!string.IsNullOrEmpty(pathBase)) | |||
{ | |||
//loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | |||
app.UsePathBase(pathBase); | |||
} | |||
app.UseStaticFiles(); | |||
// Make work identity server redirections in Edge and lastest versions of browsers. WARN: Not valid in a production environment. | |||
app.Use(async (context, next) => | |||
{ | |||
context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'"); | |||
context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); | |||
context.Response.Headers.Add("Access-Control-Allow-Headers", "*"); | |||
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS"); | |||
await next(); | |||
}); | |||
app.UseForwardedHeaders(); | |||
// Adds IdentityServer | |||
app.UseIdentityServer(); | |||
// Fix a problem with chrome. Chrome enabled a new feature "Cookies without SameSite must be secure", | |||
// the cookies should be expired from https, but in eShop, the internal communication in aks and docker compose is http. | |||
// To avoid this problem, the policy of cookies should be in Lax mode. | |||
app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = AspNetCore.Http.SameSiteMode.Lax }); | |||
app.UseRouting(); | |||
app.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapDefaultControllerRoute(); | |||
endpoints.MapControllers(); | |||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||
{ | |||
Predicate = _ => true, | |||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||
}); | |||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||
{ | |||
Predicate = r => r.Name.Contains("self") | |||
}); | |||
}); | |||
} | |||
private void RegisterAppInsights(IServiceCollection services) | |||
{ | |||
services.AddApplicationInsightsTelemetry(Configuration); | |||
services.AddApplicationInsightsKubernetesEnricher(); | |||
} | |||
} | |||
} |
@ -0,0 +1,7 @@ | |||
<div class="container"> | |||
<div class="lead"> | |||
<h1>Access Denied</h1> | |||
<p>You do not have access to that resource.</p> | |||
</div> | |||
</div> |
@ -1,28 +1,58 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.LoginViewModel | |||
@{ | |||
ViewData["Title"] = "Log in"; | |||
var requestQuery = ViewContext.HttpContext.Request.Query; | |||
requestQuery.TryGetValue("ReturnUrl", out var returnUrl); | |||
string partialView; | |||
if (returnUrl[0].Contains("client_id=js")) | |||
{ | |||
Layout = "_Layout-SPA"; | |||
partialView = "_LoginPartial-SPA.cshtml"; | |||
} | |||
else | |||
{ | |||
partialView = "_LoginPartial-MVC.cshtml"; | |||
} | |||
} | |||
<partial name=@partialView model=@Model /> | |||
@section Scripts { | |||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } | |||
} | |||
@model LoginViewModel | |||
<div class="login-page"> | |||
<partial name="_ValidationSummary" /> | |||
<div class="container"> | |||
<div class="row top-buffer"> | |||
@if (Model.EnableLocalLogin) | |||
{ | |||
<div class="col"></div> | |||
<div class="col-sm-6"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<h2>Login</h2> | |||
</div> | |||
<div class="card-body"> | |||
<form asp-route="Login"> | |||
<input type="hidden" asp-for="ReturnUrl" /> | |||
<div class="form-group"> | |||
<label asp-for="Username"></label> | |||
<input class="form-control" placeholder="Username" asp-for="Username" autofocus> | |||
</div> | |||
<div class="form-group"> | |||
<label asp-for="Password"></label> | |||
<input type="password" class="form-control" placeholder="Password" asp-for="Password" autocomplete="off"> | |||
</div> | |||
@if (Model.AllowRememberLogin) | |||
{ | |||
<div class="form-group"> | |||
<div class="form-check"> | |||
<input class="form-check-input" asp-for="RememberLogin"> | |||
<label class="form-check-label" asp-for="RememberLogin"> | |||
Remember My Login | |||
</label> | |||
</div> | |||
</div> | |||
} | |||
<div> | |||
<p>The default users are alice/bob, password: Pass123$</p> | |||
</div> | |||
<button class="btn btn-primary" name="button" value="login">Login</button> | |||
<button class="btn btn-secondary" name="button" value="cancel">Cancel</button> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col"></div> | |||
} | |||
</div> | |||
</div> | |||
</div> |
@ -1,21 +1,15 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.LogoutViewModel | |||
@model LogoutViewModel | |||
<div class="container logout-page"> | |||
<div class="page-header"> | |||
<div class="logout-page"> | |||
<div class="lead"> | |||
<h1>Logout</h1> | |||
<p>Would you like to logut of IdentityServer?</p> | |||
</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> | |||
<form asp-action="Logout"> | |||
<input type="hidden" name="logoutId" value="@Model.LogoutId" /> | |||
<div class="form-group"> | |||
<button class="btn btn-primary">Yes</button> | |||
</div> | |||
</div> | |||
</div> | |||
</form> | |||
</div> |
@ -1,3 +0,0 @@ | |||
<br /> | |||
<br /> | |||
<p style="width:80%;"><center><span>Redirecting...</span></center></p> |
@ -1,28 +0,0 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.RegisterViewModel | |||
@{ | |||
ViewData["Title"] = "Register"; | |||
var requestQuery = ViewContext.HttpContext.Request.Query; | |||
requestQuery.TryGetValue("ReturnUrl", out var returnUrl); | |||
string partialView; | |||
if (returnUrl[0].Contains("client_id=js")) | |||
{ | |||
Layout = "_Layout-SPA"; | |||
partialView = "_RegisterPartial-SPA.cshtml"; | |||
} | |||
else | |||
{ | |||
partialView = "_RegisterPartial-MVC.cshtml"; | |||
} | |||
} | |||
<partial name=@partialView model=@Model /> | |||
@section Scripts { | |||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } | |||
} |
@ -1,54 +0,0 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.LoginViewModel | |||
<div class="brand-header-block"> | |||
<ul class="container"> | |||
<li><a asp-area="" asp-controller="Account" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">REGISTER</a></li> | |||
<li class="active" style="margin-right: 65px;">LOGIN</li> | |||
</ul> | |||
</div> | |||
<div class="container account-login-container"> | |||
<div class="row"> | |||
<div class="col-md-12"> | |||
<section> | |||
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal"> | |||
<input type="hidden" asp-for="ReturnUrl" /> | |||
<h4>ARE YOU REGISTERED?</h4> | |||
<div asp-validation-summary="All" class="text-danger"></div> | |||
<div class="form-group"> | |||
<label asp-for="Email" class="control-label form-label"></label> | |||
<input asp-for="Email" class="form-control form-input form-input-center" /> | |||
<span asp-validation-for="Email" class="text-danger"></span> | |||
</div> | |||
<div class="form-group"> | |||
<label asp-for="Password" class="control-label form-label"></label> | |||
<input asp-for="Password" class="form-control form-input form-input-center" /> | |||
<span asp-validation-for="Password" class="text-danger"></span> | |||
</div> | |||
<div class="form-group"> | |||
<div class="checkbox"> | |||
<label asp-for="RememberMe"> | |||
<input asp-for="RememberMe" /> | |||
@Html.DisplayNameFor(m => m.RememberMe) | |||
</label> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<button type="submit" class="btn btn-default btn-brand btn-brand-big"> LOG IN </button> | |||
</div> | |||
<p> | |||
<a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" class="text">Register as a new user?</a> | |||
</p> | |||
<p> | |||
Note that for demo purposes you don't need to register and can login with these credentials: | |||
</p> | |||
<p> | |||
User: <b>demouser@microsoft.com</b> | |||
</p> | |||
<p> | |||
Password: <b>Pass@word1</b> | |||
</p> | |||
</form> | |||
</section> | |||
</div> | |||
</div> | |||
</div> |
@ -1,47 +0,0 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.LoginViewModel | |||
<div class="container"> | |||
<div class="row"> | |||
<div class="col-md-12"> | |||
<section> | |||
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-login"> | |||
<input type="hidden" asp-for="ReturnUrl" /> | |||
<h4 class="text-left mb-4">ARE YOU REGISTERED?</h4> | |||
<div asp-validation-summary="All" class="text-danger"></div> | |||
<div class="form-group"> | |||
<label asp-for="Email" class="control-label form-label"></label> | |||
<input asp-for="Email" class="form-control form-input w-100" /> | |||
<span asp-validation-for="Email" class="text-danger"></span> | |||
</div> | |||
<div class="form-group"> | |||
<label asp-for="Password" class="control-label form-label"></label> | |||
<input asp-for="Password" class="form-control form-input w-100" /> | |||
<span asp-validation-for="Password" class="text-danger"></span> | |||
</div> | |||
<div class="d-flex align-items-center justify-content-between"> | |||
<div class="checkbox"> | |||
<label asp-for="RememberMe"> | |||
<input asp-for="RememberMe" class="mr-1" /> | |||
@Html.DisplayNameFor(m => m.RememberMe) | |||
</label> | |||
</div> | |||
<button type="submit" class="btn btn-primary">LOG IN</button> | |||
</div> | |||
<div class="form-login-register-link text-center mt-3"> | |||
<a class="text-link" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a> | |||
</div> | |||
</form> | |||
<div class="form-login-details"> | |||
<div> | |||
Note that for demo purposes you don't need to register and can login with these credentials: | |||
</div> | |||
<div> | |||
User: <strong>demouser@microsoft.com</strong> Password: <strong>Pass@word1</strong> | |||
</div> | |||
</div> | |||
</section> | |||
</div> | |||
</div> | |||
</div> |
@ -1,100 +0,0 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.RegisterViewModel | |||
<div class="brand-header-block"> | |||
<ul class="container"> | |||
<li class="active">REGISTER</li> | |||
<li style="margin-right: 65px;"><a asp-area="" asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]">LOGIN</a></li> | |||
</ul> | |||
</div> | |||
<div class="container register-container"> | |||
<form asp-controller="Account" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal"> | |||
<h4 class="order-create-section-title">CREATE NEW ACCOUNT</h4> | |||
<div asp-validation-summary="All" class="text-danger"></div> | |||
<div class="row"> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.Name" class="control-label form-label">NAME</label> | |||
<input asp-for="User.Name" class="form-control form-input" /> | |||
<span asp-validation-for="User.Name" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.LastName" class="control-label form-label">LAST NAME</label> | |||
<input asp-for="User.LastName" class="form-control form-input" /> | |||
<span asp-validation-for="User.LastName" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.Street" class="control-label form-label">ADDRESS</label> | |||
<input asp-for="User.Street" class="form-control form-input" /> | |||
<span asp-validation-for="User.Street" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.City" class="control-label form-label"></label> | |||
<input asp-for="User.City" class="form-control form-input" /> | |||
<span asp-validation-for="User.City" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.State" class="control-label form-label"></label> | |||
<input asp-for="User.State" class="form-control form-input" /> | |||
<span asp-validation-for="User.State" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.Country" class="control-label form-label"></label> | |||
<input asp-for="User.Country" class="form-control form-input" /> | |||
<span asp-validation-for="User.Country" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.ZipCode" class="control-label form-label">POSTCODE</label> | |||
<input asp-for="User.ZipCode" class="form-control form-input" /> | |||
<span asp-validation-for="User.ZipCode" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.PhoneNumber" class="control-label form-label">PHONE NUMBER</label> | |||
<input asp-for="User.PhoneNumber" class="form-control form-input" /> | |||
<span asp-validation-for="User.PhoneNumber" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.CardNumber" class="control-label form-label">Card Number</label> | |||
<input asp-for="User.CardNumber" class="form-control form-input" /> | |||
<span asp-validation-for="User.CardNumber" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.CardHolderName" class="control-label form-label">Cardholder Name</label> | |||
<input asp-for="User.CardHolderName" class="form-control form-input" /> | |||
<span asp-validation-for="User.CardHolderName" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-3"> | |||
<label asp-for="User.Expiration" class="control-label form-label">Expiration Date</label> | |||
<input asp-for="User.Expiration" placeholder="MM/YY" class="form-control form-input form-input-small" /> | |||
<span asp-validation-for="User.Expiration" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-3"> | |||
<label asp-for="User.SecurityNumber" class="control-label form-label">Security Code</label> | |||
<input asp-for="User.SecurityNumber" class="form-control form-input form-input-small" /> | |||
<span asp-validation-for="User.SecurityNumber" class="text-danger" /> | |||
</div> | |||
</div> | |||
<br /><br /> | |||
<div class="row"> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="Email" class="control-label form-label"></label> | |||
<input asp-for="Email" class="form-control form-input" /> | |||
<span asp-validation-for="Email" class="text-danger"></span> | |||
</div> | |||
<div class="form-group col-sm-offset-6"></div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="Password" class="control-label form-label"></label> | |||
<input asp-for="Password" class="form-control form-input" /> | |||
<span asp-validation-for="Password" class="text-danger"></span> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="ConfirmPassword" class="control-label form-label"></label> | |||
<input asp-for="ConfirmPassword" class="form-control form-input" /> | |||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span> | |||
</div> | |||
</div> | |||
<br /><br /> | |||
<div class="form-group"> | |||
<button type="submit" class="btn btn-default btn-brand"> Register </button> | |||
</div> | |||
<br /><br /> | |||
</form> | |||
</div> |
@ -1,100 +0,0 @@ | |||
@model Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels.RegisterViewModel | |||
<div class="container"> | |||
<h1 class="mb-4 mt-5">[ New Account ]</h1> | |||
<form asp-controller="Account" asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-register"> | |||
<h4 class="order-create-section-title">Information</h4> | |||
<div asp-validation-summary="All" class="text-danger"></div> | |||
<div class="row"> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.Name" class="control-label form-label">Name</label> | |||
<input asp-for="User.Name" class="form-control form-input" /> | |||
<span asp-validation-for="User.Name" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.LastName" class="control-label form-label">Last Name</label> | |||
<input asp-for="User.LastName" class="form-control form-input" /> | |||
<span asp-validation-for="User.LastName" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.Street" class="control-label form-label">Address</label> | |||
<input asp-for="User.Street" class="form-control form-input" /> | |||
<span asp-validation-for="User.Street" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.City" class="control-label form-label"></label> | |||
<input asp-for="User.City" class="form-control form-input" /> | |||
<span asp-validation-for="User.City" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.State" class="control-label form-label"></label> | |||
<input asp-for="User.State" class="form-control form-input" /> | |||
<span asp-validation-for="User.State" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.Country" class="control-label form-label"></label> | |||
<input asp-for="User.Country" class="form-control form-input" /> | |||
<span asp-validation-for="User.Country" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.ZipCode" class="control-label form-label">Postcode</label> | |||
<input asp-for="User.ZipCode" class="form-control form-input" /> | |||
<span asp-validation-for="User.ZipCode" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.PhoneNumber" class="control-label form-label">Phone Number</label> | |||
<input asp-for="User.PhoneNumber" class="form-control form-input" /> | |||
<span asp-validation-for="User.PhoneNumber" class="text-danger" /> | |||
</div> | |||
</div> | |||
<div class="mt-4"> | |||
<h4 class="order-create-section-title">Credit Card</h4> | |||
</div> | |||
<div class="row"> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.CardNumber" class="control-label form-label">Card Number</label> | |||
<input asp-for="User.CardNumber" class="form-control form-input" /> | |||
<span asp-validation-for="User.CardNumber" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="User.CardHolderName" class="control-label form-label">Cardholder Name</label> | |||
<input asp-for="User.CardHolderName" class="form-control form-input" /> | |||
<span asp-validation-for="User.CardHolderName" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-3"> | |||
<label asp-for="User.Expiration" class="control-label form-label">Expiration Date</label> | |||
<input asp-for="User.Expiration" placeholder="MM/YY" class="form-control form-input form-input-small" /> | |||
<span asp-validation-for="User.Expiration" class="text-danger" /> | |||
</div> | |||
<div class="form-group col-sm-3"> | |||
<label asp-for="User.SecurityNumber" class="control-label form-label">Security Code</label> | |||
<input asp-for="User.SecurityNumber" class="form-control form-input form-input-small" /> | |||
<span asp-validation-for="User.SecurityNumber" class="text-danger" /> | |||
</div> | |||
</div> | |||
<br /><br /> | |||
<div class="row"> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="Email" class="control-label form-label"></label> | |||
<input asp-for="Email" class="form-control form-input" /> | |||
<span asp-validation-for="Email" class="text-danger"></span> | |||
</div> | |||
<div class="form-group col-sm-offset-6"></div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="Password" class="control-label form-label"></label> | |||
<input asp-for="Password" class="form-control form-input" /> | |||
<span asp-validation-for="Password" class="text-danger"></span> | |||
</div> | |||
<div class="form-group col-sm-6"> | |||
<label asp-for="ConfirmPassword" class="control-label form-label"></label> | |||
<input asp-for="ConfirmPassword" class="form-control form-input" /> | |||
<span asp-validation-for="ConfirmPassword" class="text-danger"></span> | |||
</div> | |||
</div> | |||
<br /><br /> | |||
<div class="d-flex mt-3 justify-content-end"> | |||
<button type="submit" class="btn btn-primary">Register</button> | |||
</div> | |||
<br /><br /> | |||
</form> | |||
</div> |
@ -0,0 +1,7 @@ | |||
<div class="page-device-success"> | |||
<div class="lead"> | |||
<h1>Success</h1> | |||
<p>You have successfully authorized the device</p> | |||
</div> | |||
</div> |
@ -0,0 +1,23 @@ | |||
@model string | |||
<div class="page-device-code"> | |||
<div class="lead"> | |||
<h1>User Code</h1> | |||
<p>Please enter the code displayed on your device.</p> | |||
</div> | |||
<partial name="_ValidationSummary" /> | |||
<div class="row"> | |||
<div class="col-sm-6"> | |||
<form asp-action="UserCodeCapture"> | |||
<div class="form-group"> | |||
<label for="userCode">User Code:</label> | |||
<input class="form-control" for="userCode" name="userCode" autofocus /> | |||
</div> | |||
<button class="btn btn-primary" name="button">Submit</button> | |||
</form> | |||
</div> | |||
</div> | |||
</div> |
@ -0,0 +1,108 @@ | |||
@model DeviceAuthorizationViewModel | |||
<div class="page-device-confirmation"> | |||
<div class="lead"> | |||
@if (Model.ClientLogoUrl != null) | |||
{ | |||
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div> | |||
} | |||
<h1> | |||
@Model.ClientName | |||
<small class="text-muted">is requesting your permission</small> | |||
</h1> | |||
@if (Model.ConfirmUserCode) | |||
{ | |||
<p>Please confirm that the authorization request quotes the code: <strong>@Model.UserCode</strong>.</p> | |||
} | |||
<p>Uncheck the permissions you do not wish to grant.</p> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-8"> | |||
<partial name="_ValidationSummary" /> | |||
</div> | |||
</div> | |||
<form asp-action="Callback"> | |||
<input asp-for="UserCode" type="hidden" value="@Model.UserCode" /> | |||
<div class="row"> | |||
<div class="col-sm-8"> | |||
@if (Model.IdentityScopes.Any()) | |||
{ | |||
<div class="form-group"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<span class="glyphicon glyphicon-user"></span> | |||
Personal Information | |||
</div> | |||
<ul class="list-group list-group-flush"> | |||
@foreach (var scope in Model.IdentityScopes) | |||
{ | |||
<partial name="_ScopeListItem" model="@scope" /> | |||
} | |||
</ul> | |||
</div> | |||
</div> | |||
} | |||
@if (Model.ApiScopes.Any()) | |||
{ | |||
<div class="form-group"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<span class="glyphicon glyphicon-tasks"></span> | |||
Application Access | |||
</div> | |||
<ul class="list-group list-group-flush"> | |||
@foreach (var scope in Model.ApiScopes) | |||
{ | |||
<partial name="_ScopeListItem" model="scope" /> | |||
} | |||
</ul> | |||
</div> | |||
</div> | |||
} | |||
<div class="form-group"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<span class="glyphicon glyphicon-tasks"></span> | |||
Description | |||
</div> | |||
<div class="card-body"> | |||
<input class="form-control" placeholder="Description or name of device" asp-for="Description" autofocus> | |||
</div> | |||
</div> | |||
</div> | |||
@if (Model.AllowRememberConsent) | |||
{ | |||
<div class="form-group"> | |||
<div class="form-check"> | |||
<input class="form-check-input" asp-for="RememberConsent"> | |||
<label class="form-check-label" asp-for="RememberConsent"> | |||
<strong>Remember My Decision</strong> | |||
</label> | |||
</div> | |||
</div> | |||
} | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-4"> | |||
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button> | |||
<button name="button" value="no" class="btn btn-secondary">No, Do Not Allow</button> | |||
</div> | |||
<div class="col-sm-4 col-lg-auto"> | |||
@if (Model.ClientUrl != null) | |||
{ | |||
<a class="btn btn-outline-info" href="@Model.ClientUrl"> | |||
<span class="glyphicon glyphicon-info-sign"></span> | |||
<strong>@Model.ClientName</strong> | |||
</a> | |||
} | |||
</div> | |||
</div> | |||
</form> | |||
</div> |
@ -0,0 +1,64 @@ | |||
@model DiagnosticsViewModel | |||
<div class="diagnostics-page"> | |||
<div class="lead"> | |||
<h1>Authentication Cookie</h1> | |||
</div> | |||
<div class="row"> | |||
<div class="col"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<h2>Claims</h2> | |||
</div> | |||
<div class="card-body"> | |||
<dl> | |||
@foreach (var claim in Model.AuthenticateResult.Principal.Claims) | |||
{ | |||
<dt>@claim.Type</dt> | |||
<dd>@claim.Value</dd> | |||
} | |||
</dl> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="col"> | |||
<div class="card"> | |||
<div class="card-header"> | |||
<h2>Properties</h2> | |||
</div> | |||
<div class="card-body"> | |||
<dl> | |||
@foreach (var prop in Model.AuthenticateResult.Properties.Items) | |||
{ | |||
<dt>@prop.Key</dt> | |||
<dd>@prop.Value</dd> | |||
} | |||
@if (Model.Clients.Any()) | |||
{ | |||
<dt>Clients</dt> | |||
<dd> | |||
@{ | |||
var clients = Model.Clients.ToArray(); | |||
for(var i = 0; i < clients.Length; i++) | |||
{ | |||
<text>@clients[i]</text> | |||
if (i < clients.Length - 1) | |||
{ | |||
<text>, </text> | |||
} | |||
} | |||
} | |||
</dd> | |||
} | |||
</dl> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
@ -0,0 +1,87 @@ | |||
@model GrantsViewModel | |||
<div class="grants-page"> | |||
<div class="lead"> | |||
<h1>Client Application Permissions</h1> | |||
<p>Below is the list of applications you have given permission to and the resources they have access to.</p> | |||
</div> | |||
@if (Model.Grants.Any() == false) | |||
{ | |||
<div class="row"> | |||
<div class="col-sm-8"> | |||
<div class="alert alert-info"> | |||
You have not given access to any applications | |||
</div> | |||
</div> | |||
</div> | |||
} | |||
else | |||
{ | |||
foreach (var grant in Model.Grants) | |||
{ | |||
<div class="card"> | |||
<div class="card-header"> | |||
<div class="row"> | |||
<div class="col-sm-8 card-title"> | |||
@if (grant.ClientLogoUrl != null) | |||
{ | |||
<img src="@grant.ClientLogoUrl"> | |||
} | |||
<strong>@grant.ClientName</strong> | |||
</div> | |||
<div class="col-sm-2"> | |||
<form asp-action="Revoke"> | |||
<input type="hidden" name="clientId" value="@grant.ClientId"> | |||
<button class="btn btn-danger">Revoke Access</button> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
<ul class="list-group list-group-flush"> | |||
@if (grant.Description != null) | |||
{ | |||
<li class="list-group-item"> | |||
<label>Description:</label> @grant.Description | |||
</li> | |||
} | |||
<li class="list-group-item"> | |||
<label>Created:</label> @grant.Created.ToString("yyyy-MM-dd") | |||
</li> | |||
@if (grant.Expires.HasValue) | |||
{ | |||
<li class="list-group-item"> | |||
<label>Expires:</label> @grant.Expires.Value.ToString("yyyy-MM-dd") | |||
</li> | |||
} | |||
@if (grant.IdentityGrantNames.Any()) | |||
{ | |||
<li class="list-group-item"> | |||
<label>Identity Grants</label> | |||
<ul> | |||
@foreach (var name in grant.IdentityGrantNames) | |||
{ | |||
<li>@name</li> | |||
} | |||
</ul> | |||
</li> | |||
} | |||
@if (grant.ApiGrantNames.Any()) | |||
{ | |||
<li class="list-group-item"> | |||
<label>API Grants</label> | |||
<ul> | |||
@foreach (var name in grant.ApiGrantNames) | |||
{ | |||
<li>@name</li> | |||
} | |||
</ul> | |||
</li> | |||
} | |||
</ul> | |||
</div> | |||
} | |||
} | |||
</div> |
@ -1,30 +1,32 @@ | |||
<div class="welcome-page container"> | |||
<div class="row page-header"> | |||
<div class="col-sm-10"> | |||
<h1> | |||
<img class="icon" src="~/icon.jpg"> | |||
Welcome to Duende.IdentityServer | |||
@*<small>(build {version})</small>*@ | |||
</h1> | |||
</div> | |||
</div> | |||
@using System.Diagnostics | |||
<div class="row"> | |||
<div class="col-sm-8"> | |||
<p> | |||
IdentityServer publishes a | |||
<a href="~/.well-known/openid-configuration">discovery document</a> | |||
where you can find metadata and links to all the endpoints, key material, etc. | |||
</p> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="col-sm-8"> | |||
<p> | |||
Here are links to the | |||
<a href="https://github.com/identityserver/Duende.IdentityServer">source code repository</a>, | |||
and <a href="https://github.com/identityserver/Duende.IdentityServer.Samples">ready to use samples</a>. | |||
</p> | |||
</div> | |||
</div> | |||
@{ | |||
var version = FileVersionInfo.GetVersionInfo(typeof(Duende.IdentityServer.Hosting.IdentityServerMiddleware).Assembly.Location).ProductVersion.Split('+').First(); | |||
} | |||
<div class="welcome-page"> | |||
<h1> | |||
<img src="~/icon.jpg"> | |||
Welcome to IdentityServer4 | |||
<small class="text-muted">(version @version)</small> | |||
</h1> | |||
<ul> | |||
<li> | |||
IdentityServer publishes a | |||
<a href="~/.well-known/openid-configuration">discovery document</a> | |||
where you can find metadata and links to all the endpoints, key material, etc. | |||
</li> | |||
<li> | |||
Click <a href="~/diagnostics">here</a> to see the claims for your current session. | |||
</li> | |||
<li> | |||
Click <a href="~/grants">here</a> to manage your stored grants. | |||
</li> | |||
<li> | |||
Here are links to the | |||
<a href="https://github.com/identityserver/IdentityServer4">source code repository</a>, | |||
and <a href="https://github.com/IdentityServer/IdentityServer4/tree/main/samples">ready to use samples</a>. | |||
</li> | |||
</ul> | |||
</div> |
@ -0,0 +1,11 @@ | |||
@model RedirectViewModel | |||
<div class="redirect-page"> | |||
<div class="lead"> | |||
<h1>You are now being returned to the application</h1> | |||
<p>Once complete, you may close this tab.</p> | |||
</div> | |||
</div> | |||
<meta http-equiv="refresh" content="0;url=@Model.RedirectUrl" data-url="@Model.RedirectUrl"> | |||
<script src="~/js/signin-redirect.js"></script> |
@ -1,54 +0,0 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'; script-src-elem 'unsafe-inline'"> | |||
<title>eShopOnContainers - Identity</title> | |||
<link rel="icon" type="image/x-icon" href="~/favicon.ico" /> | |||
<link rel="shortcut icon" type="image/x-icon" href="~/favicon.ico" /> | |||
<environment names="Development"> | |||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> | |||
<link rel="stylesheet" href="~/css/site-spa.css" /> | |||
</environment> | |||
<environment names="Staging,Production"> | |||
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css" | |||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" | |||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> | |||
<link rel="stylesheet" href="~/css/site-spa.min.css" asp-append-version="true" /> | |||
</environment> | |||
</head> | |||
<body> | |||
<div class="es-header"> | |||
<div class="container"> | |||
<article class="d-flex align-content-center justify-content-between"> | |||
<section> | |||
<a asp-controller="home" asp-action="ReturnToOriginalApplication" asp-route-returnUrl="@ViewData["ReturnUrl"]"> | |||
<img class="es-header-brand" src="~/images/logo_color.svg"> | |||
</a> | |||
</section> | |||
</article> | |||
</div> | |||
</div> | |||
<div class="content"> | |||
@RenderBody() | |||
</div> | |||
<footer class="footer"> | |||
<div class="container"> | |||
<article class="d-flex w-100 h-100 justify-content-between align-items-center"> | |||
<section> | |||
<img class="footer-brand" src="~/images/logo.svg"> | |||
</section> | |||
<section> © e-Shoponcontainers. All rights reserved </section> | |||
</article> | |||
</div> | |||
</footer> | |||
<script src="~/lib/jquery/jquery.js"></script> | |||
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> | |||
@RenderSection("scripts", required: false) | |||
</body> | |||
</html> |
@ -1,56 +1,48 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'; script-src-elem 'unsafe-inline'"> | |||
<title>eShopOnContainers Identity</title> | |||
<link rel="icon" type="image/x-icon" href="~/favicon.ico" /> | |||
<link rel="shortcut icon" type="image/x-icon" href="~/favicon.ico" /> | |||
<environment names="Development"> | |||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> | |||
<link rel="stylesheet" href="~/css/site.css" /> | |||
</environment> | |||
<environment names="Staging,Production"> | |||
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css" | |||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" | |||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> | |||
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" /> | |||
</environment> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" /> | |||
<title>eShopOnContainers - Identity</title> | |||
<link rel="icon" type="image/x-icon" href="favicon.png"> | |||
<link rel="shortcut icon" type="image/x-icon" href="~/favicon.png" /> | |||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> | |||
<link rel="stylesheet" href="~/css/site.css" /> | |||
</head> | |||
<body> | |||
<div class="navbar navbar-inverse fixed-top es-header"> | |||
<div class="es-header"> | |||
<div class="container"> | |||
<div class="row"> | |||
<div class="navbar-header col-sm-6 col-xs-8"> | |||
<a asp-controller="home" asp-action="ReturnToOriginalApplication" asp-route-returnUrl="@ViewData["ReturnUrl"]"> | |||
<div class="navbar-brand"></div> | |||
</a> | |||
</div> | |||
</div> | |||
<article class="d-flex align-content-center justify-content-between"> | |||
<section> | |||
<img class="es-header-brand" src="~/images/logo_color.svg"> | |||
</section> | |||
</article> | |||
</div> | |||
</div> | |||
<div> | |||
<div class="content"> | |||
@RenderBody() | |||
<br /><br /> | |||
<footer> | |||
<div class="container"> | |||
<div class="row"> | |||
<div class="col-sm-6"> | |||
<br><div class="brand"></div> | |||
</div> | |||
<div class="col-sm-6"> | |||
<img class="text hidden-xs" src="~/images/main_footer_text.PNG" width="335" height="26" alt="footer text image" /> | |||
</div> | |||
</div> | |||
</div> | |||
</footer> | |||
</div> | |||
<footer class="footer"> | |||
<div class="container"> | |||
<article class="d-flex w-100 h-100 justify-content-between align-items-center"> | |||
<section> | |||
<img class="footer-brand" src="~/images/logo.svg"> | |||
</section> | |||
<section> © eShopOnContainers. All rights reserved </section> | |||
</article> | |||
</div> | |||
</footer> | |||
<script src="~/lib/jquery/dist/jquery.slim.min.js"></script> | |||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> | |||
<script src="~/lib/jquery/jquery.js"></script> | |||
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> | |||
@RenderSection("scripts", required: false) | |||
</body> | |||
</html> |
@ -1,27 +0,0 @@ | |||
@using Microsoft.AspNetCore.Identity | |||
@using Microsoft.eShopOnContainers.Services.Identity.API.Models | |||
@inject SignInManager<ApplicationUser> SignInManager | |||
@inject UserManager<ApplicationUser> UserManager | |||
@if (SignInManager.IsSignedIn(User)) | |||
{ | |||
<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right"> | |||
<ul class="nav navbar-nav navbar-right"> | |||
<li> | |||
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a> | |||
</li> | |||
<li> | |||
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log off</button> | |||
</li> | |||
</ul> | |||
</form> | |||
} | |||
else | |||
{ | |||
<ul class="nav navbar-nav navbar-right"> | |||
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li> | |||
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li> | |||
</ul> | |||
} |
@ -1,14 +0,0 @@ | |||
<environment names="Development"> | |||
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script> | |||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> | |||
</environment> | |||
<environment names="Staging,Production"> | |||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js" | |||
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js" | |||
asp-fallback-test="window.jQuery && window.jQuery.validator"> | |||
</script> | |||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js" | |||
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" | |||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"> | |||
</script> | |||
</environment> |
@ -1 +1,2 @@ | |||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers | |||
@using IdentityServerHost.Quickstart.UI | |||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@ -1,3 +1,3 @@ | |||
@{ | |||
@{ | |||
Layout = "_Layout"; | |||
} |
@ -0,0 +1 @@ | |||
{"Version":1,"Id":"10C452043E09C5A22FC8D97669868B8F","Created":"2022-11-30T12:03:40.1630635Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8JmnWj_BpdBDujWYr1NmSawi1hoBHaFG7R1_Hu0pSObE9SiTNg1Wm83zVA3-oCwbntipelQt_gpZ3KH-NMBYdleYt4Gzhpvf4uchFNFzvdnx1X9jpWQi9WCmUm3cVNfzNG2eIKzrVLAoiaWLuDDG9XT-u2ojWIuKRKH7zEBMGrqCDQiVHZlBuqH_qzpWUQn-qKYEkFRcm05OmMYXJA0AquXimdOl1V_BcagZOpNVzG3C5t7lZTsTrm7FpD5zMdyZxL2VJJjRy5fUZbIQIjbQdTGYJaROhj-Sc4h2MgyrMNYJ-TQZKci_5ZoN-GJfjdaB-3UHDk_uFrVk9WpAYEWw0rw-I05tMu8vVxlE2jAnEGaVWEqS0f_Bz53MkDQb2YEleekTwaLJM1JZF791NTaG7oDXHSetSeo7UngHt3D9Ls2FHnZwV-B5mvzZju_-U7inwsoKXjZeqzQfQMs2IsIgiXJJkS20PObmosEbq7LkT97qRrinGicY0pA2zIcDmLiywYzWpsP6Rjw7epDaDlPu2J9BZ93Ni-slfTkNdn2SvAfE2dsJmV7Pz5tMEE-0mBlSMbcrs8TuVQayYPm2dm-sZLCv-iQu0rrvGcJ-lLPliby69du5oFU6WKpo52X4Du9tcM-5prBLWRVt_gcq16zBY_f8ieNnq6b0C_9chaztCevTQAouiSk7ni3zjJo3RuG1wPUts2tLT4sjY6IK2Zbhsvi-negegn91cFA0MzdOaD2Ca2GWN2L2kRrDWioVz_P4ztVFIU3SrgjW6stOGNreRvOB8txUCexJuUP0rb85pOa8rxPCkk1Iw8BjjRdirhvU3bvqhml6i2_iqNTAxpx89A6LRUuOYvYiwJersfhOF9F-FfOVGNxxfm1AKKnUBDfgLWDAw1r8PBeV6huAqUSIwaN5PcQJcdsfER9FeP9_CJqDgNGaL-psm2NQDaVV2Vx4m3GqWQhKMlk90zee5XNe1wobBS0XW_GQYieWWhH5t0pyzJi0QvpFyMAz1mcGXQs-TiHoU142FlFQojIu3_Ynf2RHOOByWDM9krMKMn6ZTnLWgBXnWtMefXqYNEhlACtP8lIXBxcbI2wFeBgRmaQA2DtEEjlymZ4RTaiQEuVC6B_wtTTVVFx3CL-TM62Xs-FOWTYafzKqGsHKzVWnB3fZqjDh4DNwb_A4mkfxoyb2UI9W4P7BJmbV5tOktYudVAFLt3XL7uNhBYLDSOjL_n8bixTg-sLUyO1H5-IxYQJciZdAro4yGO7r40FTYpZfTyvlr3RqFMDhBEPu_nSx9lPKrgpei0DmqYWw_cHJ3mPR1HDUE_i8Prlyzsyf9F-pYV4HMyupetXeXxMAIULnB52SE7uYfSUZHC25Ffo_tif8zMPo2ZKiMpc42BZ0cBptkPKUCvkR-Cq3pFnqXkKADWh1LLBP09hZtOjXqkFdsIFZq5KfRE6UVIyMJ91L-OSHOnggdjScFJzPlgEteUbydKSlPEBkicIs-uXXe8Tk8KZg2CfUU2WnziP0-VgymbTomKR5xz36kJqMjP2VYp6Xy-t3joQEqPE50ntT0AYUFJb6NZt5stHQz76-WecPnA6ZSA3aJec7NlLrxwM6BMGDyBZpbkgWtWFJdGWz6m9j4RKuyZBnyOXzYZU8FDNpDu6FVEymUkYxEhUnpIl7PZWuiZGADwj9-Bpzg0To8jMJ-jgx8w3RDzb5DoM6-jh0IJ5vecn8lSfRzHcOu48CLY5mTuHFr93YKiQCK06DS8VY3kI1b8irtdvmY-IvUCyCJlpzBYh8vFrS2PdJYsRVWuGJraxMiHYP_zH5r4Q4z27p6vt0Rbm0S4Fm1I9z4Hmaa4igF1mnktuTMizyxKWTsh3DjbsB_6W_lNU62RqJAt_IGr69MIphVY6ZOcbLx2I8p0z4yWpuYJey2BmRQPQeCZ-Tb9HtHESxTBFxupYul8619OJWA7RfLl-xv43X81OZnaxE9wLVFs5uEowKoEOzl_K5nxOw4igCiWYWufaFMMrZSu-0ni9RVSwytOPiEbGV0XOcVnB_lHDz1ymvDI4u1rfOVkehwB_wFBeRJRSEymzoD-bgRox-a1qLJNhOsdm9WUdZF3MHbmmA8NpyqU5qOTTqCvY-FXdJHL_OHmq1nDVHCJvcwLqUfPFMfFAi0MZQEhRndeQWYGdRGOhmvtwbRX-Hryz2HKXber1ixUvY3-XCxjzSyZdn845UzpKBdsgwAwY-LFzlgNQGEKHzsZUl8MSeqx3iO2QwDwC5eV4gNWBC5QBw0NAloBmdnQ84sSWydPViOYW1CinUQAnZsW2Z-0Xw0FP4fbS4HvulNBMIxE_mfJsHny4YZu_463zGR0SrV2hEFSiXuwSx27K6xSyPRB26__t8w37AeFYXtavqr9rB5GXnEJRQkW-i5lDnhtA93uRjfNzVhUpcTK-ROm3h7b4UrUTaK_IMLXxoYEjOFm8-cw1ZGHu3","DataProtected":true} |
@ -0,0 +1 @@ | |||
{"Version":1,"Id":"1417AE0BAF26F393609EF30FA355D06C","Created":"2022-11-30T15:12:36.3392917Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8Etya4tS5j5DiLTWt2Gh6S_0SY7Shff7x3C-zhStPviP80hTMRjXNVXCumUQnwoKrJ48OLZhHuU7wgSxqXaAT3hk35KvO_KOhz5pV_sWLPOogHC-hpQTIzSEbXzyfEIkliqVgW3AfpLbuDTZY4Hj8hu-OM0waYEjCQkIv-PrftxX7JU4KCT1rxZBUrDJN_mKCQEGWy0LGLVsLV8dKyLMSvJsIz3HJh4sngzist-XVaPxat2eMPqVOJEWkbUf6VqwJOnssR-ZOS9b63YZPhihUcz6J0T5Y2aWIyZfZwSkuh8DiL9_wzKSRTmplaY1tzeq_-FhbkgwtLX9VDbeesx_HGL_Pxj5SG5aprfZ7DN2FBr70m7olb-bEl8jOm0w7ot0QGwxHft53Nf9piLEyQp4hsReq-PNwCirzFNkQ5aM6Kwvwitkop_Ab8gpzfwiEw-uVyPN_gpkrt099MQrdtljc9gXHKuXr4321pgcbwO6Ua-amRcF_9uYSCVBdIhwmpaQAgMmI-0uI-5Ph7H23ZV2zR1epClkiR2t5RWOtXNgLqoupXyBjDWrse3al-CsdnkofmtY0bqr2ZdNd_EKFxqn7Gf5-dNuPJEboDUdwvjUpWYIzMlq6_3q9KP012SbimkqMgz3-3jDBw_xe43DvZd2CnWFTDQSuD0m7DTf2GVL0DlasQNYQgO1T7xN7hgScsXmzhJNUx7iz2ze5BMqWV3vw6P6ny4sj_QA4KAi0zxl7sPgPo2p-65MWlbZy646ZYlm-jDD9dfORdo9nmURg3ITYNVUREtVg4IcEzPVDBMnV-NEf3T3vHD1V49yVhovA9oAHj4uTye-bbLUIhoLPCZ5GfR8AEHE4IhL0JyjuaWilp1qNEXAhziwZUELenYAf74wEzMvSSobEWL5Hq-ApMdRePBjjeGrIobS6KuE7b3HVsYwJt9pWvmVCYZ-nQCDAOIFcgH5kifkHu6NEi23Jk7a5hmgq1kVJXr5xcli9B-bHu1mO1kl0gXsqJcGpfetcw8bEtwO8F_6kdkmW3RXLa3rvrnkigPJJtn4sm_3ee1dqFYarJlXHPugwuyzfEPXNiT1HCcDhQYlrYxfKSzmlhjthGn14W2ZYtb3YVCXUFReA8V20rs4ec7VTdjuKCPgA8bk3xKevr6aETF9I65hbI0pIFQ9_cJHkl9a3BZL-X5qSH2s3_rvPupVuP6mu-Y1pUEGp4p5XjZc0CqsLm-W6MsUKmBgeCiokmUGfs5P_Riv9ZuvlHyYcwj2qmgf4MDkmhThfisook1nrPomO1lByHMRHx8rwbVQQXHPKMI32WZxfpRQiuAERonMvLBd2P2UOutqe6t7qy_gbeM0WNEcKb08UowSxGUFnEH6Vk1Dx9Vzye2tw7ej-EsT39hudhe_59nd9IYvx1o547N1cI_ASDVn3ibVQU_BWLrSK-HIqGaA52F_hthPGpMjVBO8xtLXHiyxqI-gh9iLq0vpvMeFQjyLSmFjKQifMEVaATAF9h2TJTc0yqkIVN9y77LvkUDoFvi96zGSYDyIf6rtiF_E1GnAwu-6Le16bj9npI-FcNmh-DRIvA6LepbSXs27rVMjQdK6dOPDavu5Y9IM5bg0pndKek0orZzkbaFwxTxmM08AhKlyN6PPiIqcZ7i456FQDhqMTUEeBEShsOJy4573e0GA-J1sHvzVQYiKDlzvgYD-zh79OxnJXlq22BNrWsQ6pqXfXgB5UIvhUTrmcrUtv3uw6GcAyl76ySgfs3Hb9Mpk0aeUdI-xyMuxxJvz1w1NffcuXPsxf3VosZO8vkofjVSO8lGtzarScy-abFGD9UA-S7xGvz0rqRSAEnpxe4AZ5eBfs_VmTy21RYJqxPbrYWcjrRdQjxERhND70wiuLkF1pivUQ-bohP3w0EfV0fJoSoH9HthQ9_aY2ASJnv82BX_lPRdEgYVh1RYWziVGB_6WWkch8BLrUzXezJZcfQHGuEQYzVB17RQIhCqFo7GexmHkFB40punvvS1gUZOQLiqSu8UvJ27F5KNLpkgDy8q0pDWewRGmZ5J09aMlJE6e08u_gPrA9G52SWXvdkX8CRvPn-xOLsBfb1_84QBhB1PuSKeptNLjgMufTdfUkGrL_b3fTo4AgLz7VuKrAMNYjwCTLr4e6towu-JJ08Fr3c8igbWApP6yNiVbE1W1mX66jaRfyw3jiVAP2ytEH_0kLOE3BDFkepIopwGtkQ2ultqFHxr_-oSadSpP3g4dLt1GfQ7fjzYkYAkstXxgYgGGe7Q-mwQM1dR8RnE0jFvQnTKi-Oc_pj0h91HgRK_ASQrqrgOf9JN6McbVlE3VbEKvFfOKP8ukFa6i8n7tk1H0B8bX5iy4m_b3i3MbQmQ16gkZ2ECW29-bA4vWz6-R3LZ3kf2hHAJzv8U6cG71KLVgWSBxIyRJE-cSiBPxtM1hGT1qcJvpAVWvowkcO9Anng3lrfgnGaYh","DataProtected":true} |
@ -0,0 +1 @@ | |||
{"Version":1,"Id":"22178AEDB80CD0A41DF6874FE93F794D","Created":"2022-11-29T07:52:44.7571677Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8E4RSuU_s2BGnQ_I0J1Xz9YiYIkBDFNHHGo4A20gtVeWqoNIQNxtP1p0JacgjeZkGl9NlE2-7qD02Vs9NIZ_IfOHW71XkfBIuIp5JvnwMi-X42Qw1F6UTQKNlYNvk1Fh27a6tM-mQgxysayCdDLlJWoRJYKDPDkUO03rZVkWDD2r6QNcMNtYhjM7oxy2yiw479PIeIldPOk73cPqp_k4oMPTRWwS04dX8fpQNA_3d2PoV1sw8-Hbmspiv3PO-umUGU2OFcN243cP9aaGXDoR-RgEbCn225WuHAWTe3tf39zVDtbAWDnRTNEX_jCNBItSaFOdMerDKD28PXTnWXuoMKyZ1_8ETZk0CDMAMVktqOatYrx0JHisG8vJbHtzxIvQrZL1YqoAN_qraVLgTl99WIZv56rYSb3RbNSzqB7yNn1Y_YAobXVbqJCaqqVG6uEaQxQ4WRI78sit630anIlKP7vccOtJH_bspQlPisNVrpCWiGo0D0IzNsZXLNPkc9kUbJKGc3xgE5H-z9kbk3sOecVH_iQy4b6fJ0IrDGz9fIhu2UI0-_lUE6sQ0g-NAA9kW68vD6PVSytiag7tFVV0olRPhaiBkSRuNGKjqsPmlPtkpzVxuTxSj19YxoC200cFARZq68qjxZ7ugdHQGlqd6bKFjj6NWCfG3Sbub1HpTkPnEUIR6ic8EfUrbu18MlOh-7bnIJkIuz0Nr5zZ9tPgiJCdGPjNo47z4i-kPWiNhS9k1sJqg60dgCT3onC2Uft4F4dnQpIKb28Fli9qkTDOg9gObEr9W19tMJvEPAhWJSl6HSmIJ-TElrW7nNCdS-H7PIlGWIvP6f0CCzrlgtsfMh8TK0B-Vpv5ky9D67L03IA7VfrhHss5mEkoz2s5-yefn2Wo5uMTxy53O4GIS8j3lF0ufUIBjGqR5q6IyEQUyU79OA5A7A3gJx78Cb-OEK3GixhYwjXYOw1YcguMZo_rGsq-EnjPoVBuZfdwyEIZ847gVwDzFsh0FHZIi2vVxXZ3tNGjJI2TXxNx7nyXuBxERgCPI3C5OeK2Uy8e3ooZCqPICKS9xEnICZteEcSYkRFmvPX6O1pZMKWoiyzGe-bhuBiotTB3yGIVlkhMyqNni2hQHoR5b1CX4YaGBMT_fdtW4uhvYLhLnGR69aal2ffIhiFJMfB_ByI8sALzgsn5jW-zOAmtiEOaPs1O9FqPf1x3Wk2YuoyujgHjHi9SSUz1hAJRmsyCmcYsMdGjMd-wnk83dsdMMeNANTKJHlZP5-__XA8emO-sloHrCiHpALX-JB5RQ1BGVD-dPiTlM0MYeTLdt7y5HRKSAbsOCnnF1vzV8y8NkUgpKVLTPRsJaoNYqik4Wpoa2d2ez-gXrG9S-pAByXHyTHiVDVNyqUTDMI0s8Fm_6_QwJTkgGEV6dR6r89Rzq70FFjdKB5RygRLhRdF2Be8blyExHI3HQg8_S9fcZ3zCybjatlNuNCgNZTTelC1UzWSEql3HqeqiMWIi9Kr8GNl59OIGm2lm9KNoyvcFHlQVQ33lw4akd5Z7aYu_HcdpBhMcEr9CuN6yQPhbCDOVhsMkqzXGzpwC2ezFnALAkbj9P44vRU9p0FEKFDNn_pQimVTLQJoU5Taf1n70CxYcPgM9BU3JPL9VUb9rDS3bHA7XQoO6878k6PkLbJ6GcfIJitaRZL5TkAUJUywH-a5xADVo-1_k3ADJYhOTbwxsYU96Kuhw8FOIToBH8CtLnqJLoJ8iwVPjB9LSsHPhmR7Rsi-x7Oi5JeugLctOvBn1sq7x_hHQKPVHA5LvtQ6Y719bOgPKLybQSCebe0ordUIsPAZMq-OvLj1sPS8PvfvZBDkyUA04avHm5kgT6RJF7ARUI1vMNLijm-_dxxrzY2bj6FHT340iFDURFePLdSM-Ctu3X-gLXKqPzQ6MBBQS0pyC3GkuX7J_GhPOZ-yXvRJmvIEoDAVEvjUQc57Ud6nyS_C_ljLmIoHghJiTJxNOOjR5pucb9Vljxm_BGPZwMi6-bTbD454ESbZ1kyvOAG73PjnPoQSaEYyM3XLW3XgJ9R4dnYDk31zHpQebodqwcFsXzxV43yi7zPrJSMzaFnC3-uPQ7qH4xs9puX5RL4q4lSCUKAB82TMtCJ0xHwj4DYqsJQt9jUAz1IZbgbChq8d0EPe6y2yg38LZs50r6DD0f-VgF1fyup_1ORM1Ub6gRyWxIBZLyLFY6uLvzPhVQaBsDitK2ku-9ELv9frr0j-BPNNCBpNRQyJCbZwOcovJ3gn9w1TAJ4cXGxzJWRGVnJY0e_tJ5ZMwRrn4E6LwhKXK9N2lBU7-2RQxEU79sTRz12SV9v8Az51MlUHzjWZp8p2bttpkQKMeoIkIVu31t88Dn7MOZkwR5DQ8b1_tNV_3Nbqyc9OUUFTSrpfEJeU6VRrUilgvBq78lSov4q1tbKxp4OjhxdNv9NxfYCL11kTZVB_h","DataProtected":true} |
@ -0,0 +1 @@ | |||
{"Version":1,"Id":"870FA7120249C21C30ADE458B07918C1","Created":"2022-11-30T09:59:58.1299062Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8BCNHSqqa79Fqw5EoSNNrOMZDqq5p81RZsuqUc9iuvF_3nm9RHjc2lAsu-T8AjFPxhbWkSNnQAU7JGWpNh_TU1XK9-Skieb68wO2lUFauCBnoNf7R7JEpuw7zdjkInQZFOF1hBzhL0qKszqhM8-2gz2Q2V5c8Ng0C_2CzHNqCsyhTHBD8NgXfzpxdym7V8HZSS4SkJ3QpKLH-VMXRFssCM7PXEimNWBN2yky4IW5_fRgYgRkkCaXCU43ayGxLyBe_J6DzZ1fefO7ZBne2b8HaVVQaAEQDTlsFUxNqDLcAO-k6q3FoL9ZW2gm82a8ra1mSJTbEeAacNK5oE22urnovVF1GPwTVnnXMi_9eloBqyTM5xtTQPYbVlOkZl5pgRjKOY_Mc62Ix9p9hoH-gCfDHw2Q9X34JT1ymzBdYqtgTmWoY548-ZkX6v3AEpkhmsMdKMyNSdz-SJfasTqKkp4JkqGR-gXW17HOtxHmaOPg0ZRuQ4Dx_YHuQr98UQPMUktYL_kF_76FevNyfy5eyN6eawHNGwhXzn0YdyCjaRV1LXfD-4LqqJBhExUSyCtG0Pq0qjZ0ySAuKi72-0532XNRJ1gh7CxlfRdeLDID4ffs1cgmfODFv1k1JElYihqqGwXMwx-SfgpM9eo4j6jqd0kdAgj3n4ZsJKQMCDOGv84Gfhnw7dxlIL0e-D0sKKc6f7KagNFJCbt0LZ492IWzIPIGp5lXQwhrDPECjdNvBv0jhC6yMzZQZlVQXX6MYue_qzXb45TK23j8HjlgYPCip9eqH1NBA1MWkHykA6ZkdZ29eCCMuO-m8zB_Z_7YdEdzGcViK25T2q8qo_0BMzAzdrEJ3rOYOly7wzsoESn6aKr2U1b4qh-fljOwQrYqoSzuiu5l4QY1qVG4ZR3VMjV2lo3LEbMayA6Sis38LQy_ulUKGdOi4KeyNSlbL44wN48gDM86kNPkTM2kVoJc6RRZ5zptnGyXgAsuknHaF5EX48Ihxt2AFU_pVgKGnrx4XkYkxMEwbInoL19WgaKZyLAXjwtUG3FSOPdWswUqbcDNoetfY21Y9yWZsZ0rUTUa3x9rLzRP-H9NGbK_Yf8j75lpdx2sFUvJyyxZ2KEoq74bS-Zg203Q3jhz_RVJi8M3R0oRQHh2I-wUimdAHQ9JQu-M-VAtVLG6DVrY4vyhdv2JxIPyLrRTJPVhMDEqKqUcbm0VJE8MJOb3C6hdFrrzcM8AAkxGhYHh2vlJu0MAa8y7U0lHm8mcrNqsjPahAFvVMwUPkxaXFSYwJDdnBfODquWHt6reE9GwQw-ZFvigPGmY3SWMZiEBK-2Zkz9NglOKEgnojT6xcAbdaeEDadvY-Qriej8rMBZ3F-2IHYNMM4J2MkbexgloQgS6UWx7Nx1ln-pI-uHCDUmwsb5bl05Jma4_DXMsIjzinc2u-CvpEUXe1KmDddh7NYSrUv5RjaZfxgzjYCW3gSynFRGNfy57PaX7R7VV9pn3IRMKsbdWKlA957M2tDqmgkzC2M8KZAJRFmw_tCVk-swjPjyFmXUgsdLs0oX8OSAh4JX8DWvK42wHerh4gbjX_N7mYWh38e4-jMvyZ5Zdqf-2phj21z9uZla1mCsncjDDPJy7sVCrlJasgPLgtCD5j0lxp3wdAjvx2I4q6CKNcP8-MapHABe2TIgMmEf2VOJv42yrO99q49OwkopbyhMf-0-drAOo7Tw7eKHH7S174LfEKewCMmj2BVaM_I-rTgU28Be8pfbl6jTmj-9WCvuBbBiCtvhpeOApuMbJkc43As4QV031W9DEQpB520DWUdAHjvNkUMo0QDBLm7Zx4xzeO3Ei35Xw8_w9OjZBCZw3kalIbZAL9MlsyfFXczbZXMZnzkccPTwTK2SgS5lq1Ic07YB_uqY52CwYlpjbRP3ygM2mVCLTB921NhXKh9-Md4oJ9ieaDUNyNPch2MLQluKmyIH0sumgBkyn1k17IM4qN3EXRSsUiqa-FBU-PFa1gVIf_dhdnLI_GOPGEYBmZYTrk8J8HK2-wIi5shGDOB-WjBaAWbymdMmPhifRKiDKdOmPVArGptSyrM_5UvHF189Jl0ABxEWcsW6DScgw72GtcXxhDQligdcjanZHNVGEnfwFN604v68G42IC7clOVAno7cgaapHXXYSadEqVzP_Q4d-d-obG0v44yMu3WSdfhmJ8GC-8jS3b16q5_vTfyim5VFmLdG0PX1L9--QYop1Rns-64a5Dz8T8MmyrtgRXBAMfXC84R7N8I7SgRbsSo2pgMzRafCOpYWqfUlNcPjqQXSPj8oEt0iz0DBq9ui1dK0JiiOGOO9uOdswQP2r_cVlF-QvTszcyy1hn6QHCdsfqojSi70jmyhpBa5Vgba7nyP_qbJCtYiZwk7RLWT-jZ47BwZzEK73UXoAYCc8NT5HsIW8KFuManq8Mau7BLNj6WhXJOKmbP0oD88LMj3TeEvZ3","DataProtected":true} |
@ -0,0 +1 @@ | |||
{"Version":1,"Id":"B0B34AEB91E2638F61EB9A5543F3940F","Created":"2022-11-30T11:21:15.586844Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8Fzd_R2KZDdMrJulCrd4GFTnFtf9LeKAfSemG0-9e385u01WZOrLNU5PPCNSRvcQKMERaUvLDcAsYIsqdKPJ14QGDgSTlxggwrR50kCv3494z5uCvWOvWqYc3KKzhR-7e-mXViY4m2oYHpGaVCITKXYwCRYxO-P0mSdr6D7W-846cfdst47_rjIxxeMajL1suYSYe_sz_nz8ooKW8ZH_kl5M9cI0waB2XFviIP3f08pz_eyneKqy7tCc3ruSNiUzfUBAF7fBHDcyPrE2h4AKaXG1PT-I6JOFVj9lAgv_1ACSqTVEjJiRWfFIfft3sQ-chSv1yxy7b8GvhIsKKEn-OqXCnYr5pFW7VPagt7pf9IQdivU7I4m4zSyW0bjIWXdCL3sXQAChiDpvRCFqFfsiH4lWmI4X8Aq8Tz1_LkbicHW5-ZxeZpvgPlg6t77xqFIxFtixqdc1XxifVwJR5GwrFcbcI9VpnlBomkBSUFMslW9NxGwSztgnYatkQuiXTzdqFoOr5d4YGPEHe_Oc3h9yDyUxgBdJK30sSx1Dwki94CeQNBS7PisGbu5q60MbsyFQ2S6kBogReQdURLW326NkgOBJ27jHk3ccx24WfFuuDzquE7xetxvnX4AV0oKOudMClsOv4Wr1aMm98p4VimUQkAneFImQszK1-zSQfxeElamoYy-4h1P2V_A5siSmfKpDs1q2ja3C62lBr0YSx822O-QF4XI0id6GOZiCc_9v52B_teTlgMq6L54EjqDM-h6ERkPvoqNFL0fWz3ktOBPCQm56YrSyyaXNKG2TwL4AMLsaQ9us5SXnJ7UpiY_7ls4SVm_h3btoQcokOiO247mYo4-BRq4pCsFHNBsCReiKlJH8G8fwrujWcu_U7NdVWgmUgmcsIh9mfommuOdBdVQdP8CYZM8od1NBIIifIoj51WuZXtb9Wr4QVIddEjAQrYnexJAozF9TeBVfjhDkIIA2-b8yBGqNl5VrF4bo3b3LLm1rbq5vzfKN6pKzs2sDGple6kZn_t0Ym5fmf8iWXLzMnRyasEDuXX0-Vg9WxiWyXzHsUY-SZEfEXs_WfuzBZb9NP1JvrB_w33cUfgl9Sa6RD56O5F1YAR3WxiCiWGnlSnqpaiiyTb4NVr8N0qQl9xOz9KCSaN8H2WKgCML2DdjW5_O8Zea2EPpSf1NGB1G-Nm-9HWAn8xDAei5x8kzTIxka6Jb8GO6Rdffk7wwuYMoB4ZYuiuL1q-rqvaU5GffQWMjGz-yH-WCivVFCosvJgk9340K0bDl97CULs43LF3nQFcmuLQoW-fasNYk5ZAXYA8bjn9eYm-hWbsfA402c3iZKKZVcLAU-jPAr44Z1P9PbT-xwooGTKKnZuR55l24T3p4hDatpazlObRd27kPtSLgbXPkNKbru9N8--JUhoEeFGC8E01gCn9256wlpIO1hk9a-P87t7zJWdYkl9Uo8Poz_Hfm9Uaml55BWSghfXlB99hULUmpnPwyOVJnRhGOrnr6ys_2l6bCW-gZlWS7oevQ_BuJBcCXVuRYQPbVtH9ZZSKF-S-uczbtfzhJfH29tCNsHzAukVHFiIwHqzhLHNu1gX5COGzYdN57p7DXGkJXGJUipczm2ZAfI9g-Kgvy6MaXScuwaobPobL5AH5WG1CclEb8kg_08rnQsDfvNhmsAJE7dN1GjpA46fP2bJ09T4hfNdmW-IZbeQaDMGc_8RpoADdUY81swXUgrL56UxoCFxqifXtskbzxF5Z1ad4qRYNlqU_zMbxjSZTsSizNja4pmsx1O2NAhvN31bSoA8PWpLLz37Lq-craDpXW6-mIySm3NQWxTLx2hsk4i29-dD6AjUX2aDLEng4JLKLWKLcH_ulkujNP0oXdkXcGikLqFwUKG8_lEWV_ROlXUVlmwNf9WaohIX8caCK2CF_tyEkCxEjZIBzw5xazq7_zNbI66rXfqBHxDKqrdM3UJYBNFMk2LLyEGD4TetGu4RzBDt2r4rXatSXQh9a662LYlCXxlElP8xfH7BudBa9KgV0FQOdHQ4sm9chHiT9JPQX6bHwf_imYHmTyMV3cIN7f7bACLji-5hFf7if8ANbhY7zer9-rQWR9Wy-vYUyUaSWCWRXHQC2eXL0XklBNnv3vK1nFOcaeC5R0GGULfMnBluyClu2Z67OCsAzQqi-_aacxQVN91SNmAfUbVCrPgBTbQY_1u64fEMc8ZcL52xwT0qgCSvBPCNDEJ8YDVVd8L8eLX0hg7rV8SgCnAl5qG74BCdkmhCMLB-OiwGZxZ9s94yGdjX9jA4D70uOgn5t9OhpboX3OoRK40Xged6zCBEhUd9MilD9o-BtEPeAHloKqMrvrVDE4l9R739pjhgOfvXqGIytXD-bKlMZAY7dnJ4vu4GjVvhHfzxTV3zu_nAV8j3n41_knCe2RyCm9j74YZejnRC15ADqItTDmTre8_iS6hTOrVO9FP","DataProtected":true} |
@ -0,0 +1 @@ | |||
{"Version":1,"Id":"CFE19FEED1112F0740C7CEAAE490834F","Created":"2022-11-29T07:38:58.7490696Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8PyEV1YRvftHv5uH-VZQ5M7m2ypM-2X7o3A9EvjXNQQsFD50DwqX3UR8e4cDEITQrBowxyPPRXo_bTgEnifJjx1kdzA3q0L-FPJqCOjh6AaRqUg_TlnhvbEgG1HYoBZTAwvhi6g3h86RFPU4juwhOWHcD1vzCgW1vfJ9VFXXq3F-FSfbpdykS5bLn5hUdg3zgNWh7zZXOIFxgNkelR7lGMfF6hquTQMbCRhtDg0-N8O8m7M0JaNsB6_H6v-Z2RlYzH0R0B0Ka2fH3uPnudPrd1RIP4BONzMorJpDFZP9TF0DY9sJhkcHEhKDZlRlwI_Z-e0z8WeW183Gg_HSV23wO4B5R8ldXRSioqWyUuQfoYplUanuNeccblBe7hxQw9fhsanHGgbiuBjuypXarZmlVlbFO2zPKl46Vj8YZTxopOFOi4ALcNlUzGgPG5lwPjDfXEyAp7_ipMIW84UVfwqnPZFE1FASpFx1BPif-huoAaXw2GF4HJ2QVqfxkr_Gn8isrzBOT3fqmeZ1n0HNOc8VxCSdScbBSKFMnxNSgPQSexHGSKnXpvXF0OL6R8oqHoi8TNfYFitGE_qED6wCjQ77qMXOUVXB18kW7nJYqph2u1BH1PykRWaWDlnh3H7fKWRhO0ejaTJWl7oSLllYzi99yL5JvudDCjI0QdfbMz7o3SrdrZbIKQcbTPPgl_cfNneP4QA6MSlJFbG3bc3q8IUPhISrZHO_s7SsoX0BiP-AHcA7M_WrkAQYLkTMsBuO3x6N-bWjtTouqzHY7-h5ZYHqvk7Mg-jlgJjFjgKRDOJFiDorbf-5nvxjl3eLV8rjX7ML9KqjD-ZUjMsDt79lpjCi6B2EFqbiGT35I67hC6a7l8gPjwLKB7Oc8h4YggwyS1eLCq3CgmfMqlIAeAqLL7nVwDPReW-pirHix92q2tSkKCuz5UV5jphmDAnRFQmICaIinoSsBtjb1RlonM-yklDO-jdR5r0ZEFb8ZCssSojhfqEi5mcK3xJsyzM_Inzg71zAHCUl5cuBhbWekBeHiqb2p5ar9guCK_h7zmga1ZZTqfWAGzxHrHkHb6XiK_wLlwBjJEoGhgHfi0UzUECddK2DVwEuTsXhNDJ8CgB2oWNhIHgnlNxL_JA6_bY8BnGtcOKnEuaKpR6rXEZit2Jcr-1-Q8YJ29tVNubphYm2ZMrDTGdAVE8ODftey6BIN_HmBELf6a7x-Cd5DPrW46iVqlOuBqmjxjZ4HHTS08HHGSD4_xj2vNaWl8rIbE4BbDq9V_qyP548KLNbGqSIed38DDcbsRMIuac8po95Z3q6NUf2JR0mCOEizdrszZOW9sGVlO-WReaTkpNN8MaKYoUy6k84IxwPPv-Tov0yMdTN-jp7H2HaTOGDvEb72swcW39huoAbXwjZdrpFQjTD9_cnNrVdclDGN_yix_i8-vQDQlQ9Ronpk2NvAdnrmEzQ6w7D7PqAe5c4YJfn_0z45ERm9zBwCMCbw8lP_Yi3nwTbqGtFqAoXTSZ8vR5_UxQLqxsJy6U6gK4Es68-hnD-YR5p3CzBXxSEgV7_qvW2gly5VCB-1yoD8dTVInogMhOzDZD71gmpJURgKXX9WN92yq-YB24E8koOXdbxTvSblIVTxwDqQ7lb81y68HmZVSRLk3zhxbkEkjI1kkB8uzlArbXljN7LnxyXHU81DDeufVK5_myoIMA5NpSwEKxAkmCoF7p642qBwo90k0q0AvJVmoy8xpCRiJyLDR8wYHFsed3T99jvvvV7GyA4SG5Tt-ST5kKFKvOOwT_WrOg-ao5cz6Br8_YL2xW5dB231R4ZxfJlmrJY9WcuTQMZrWnYOOSj_5u2I4sgcnp7nrRF1l2xnieiym5ef23a-rSMBZi_leN6BAma6N1M7tu1N8cEw7wd_6FV6hqrcKFPGNm4HE5M-pmoWvzjBTo25rh_4atG5uEmDgmhmY6qiZc1eso__Fz29uJ4792kkpdGyPAcV8igzPMdKts1KiO9ThrDRH25Knwxa1tOWwezkeId-OuZRwC4LhGuZseww5TxzQtyN8mMWbLDPCmWya56F-i0jag4i-r2Z26VEmhpSywbACetljEsaVDJF2ZdIrIFJqjYLa9zhcKbD9GZPHZt0HKilXDp8DomCQHHWsIapwCiG6WLGOhVc0-0VJ28dBqIowyCQScefePKIIk7Oa0T4UzPXcXiXYNQktHGs9VrF2ux4De-SbxDRdJy1IRSH8_NQ4SFtUelO8IG27B_8Ywbonn9yRsoojmuS-fXPuWSJMHDjPcSYwJqQyxjfPjGCLsnQWRZCNT5Ft6cfcSDRZWoCqKK2qLfqcGs-FR71HM0G6ipAuVI_2IyfalS-fXqTPCncyFsOqp4BRhhxLFu8UxTZ5-HQyKH3fX75mZCD21d9RObyWv8g2T0HtEBLEf2eR8I8zCUcOf98asCoT2CU4AtOhVwqrE2u1qNKupc5Nvw","DataProtected":true} |
@ -0,0 +1 @@ | |||
{"AdditionalData":{},"Alg":"RS256","Crv":null,"D":"Pf7_o1LMYwcXEWHkZ54kP4ItOYu0lhOOn4vc88XYg9XmDR8DcPxyvRLWGIuV-jSioMP59HIN5yHeDrQbG3tY2lTUynRYHdwOFVQAzQ3LpGSF4eKfMOSDU05HkWv-w2E1XWogVFAafv2F4FHeYtqWAzjjcjlYy_n0GkCcjBvIneuVqWNZQ9-eBt28cApruVfTeoE01Liz-ww4T0CQ9ujJQf9zUxhYb6WWMQ1U-A0qGK9hmh00Ymf-rxS2HuZ9o8Bps9YEvy7rCBfy6nSMB8T2I2SNCHDdlR8Oi-j_l0VQyidnfnRY0_Lio8uSKDTiUL_vBKyCREAfCEHM6TCpdixR-Q","DP":"CR-Lyp15RyQV_TmxXq2EX8mntgdnT6ROePrkNlRCSXH-AAVJQ-24OlBH0qzehzMWI9Yk72bhqZdBIC1W8SGsTxf6T3xp-MwINJwHgYftX-D9RDQCYS5o6_gFW1775Of43Zv8tsjjwwkxUKHiwH8ImbEyqv2qVhdb_yMlgFwtXCc","DQ":"ulVv9t13HB8K8jKLomSoINMaFtNGN0LS6wY93JKtwGCjcvV5lwXRiHhq0AbDOrJggg-zZVedlbXpWRokMI-hLTHc9TguYEfiJ11DvgldTB5MUfMxHau0L8ofS5YAuTSEgg4EPIrWgvVEF1ljHwLQtDSBB3CvI6UHF7rXXy9cwIk","E":"AQAB","K":null,"KeyId":"F7383A0ED7CD960EF473FD2851803938","Kid":"F7383A0ED7CD960EF473FD2851803938","Kty":"RSA","N":"p3jTfCB0YsKpqJ6CNJi0tVmFBmoyI_D7QLLsbB-TCZ3-HIXDEr_k6zKb2GJ_QP7mncdSnYpJWSv7fWPfM0bL3A6NaMLF9MDjbfD5ti9irEW1dzBvIK0YjWmfks3eq6Mb2mM6PZtNEnoCqEzjgcRkDR1vtClEzUjs1E_i7TB-Y0J_aTYpLf-eN7yA1Obu8zMVRSSVBIwG5W5jljzA2nxk2u9qeDq8Sn0qgGwbX8cyYGQoVWBOPx7zap4cNcL6dHILjnlVrHqAUW9NVXtBWlVDP1Gvnm2zhCVJT_gW1twNhswyFULGVQH1ZWI0NukEqHG6LpN8Ti7Hx-K8MEEv_vQBYw","Oth":null,"P":"1XZw-ufz_e5fWroi8naLluW5Ow1f-Ems8oG1WYUJkc1Q4vZWklLRqlEsDH48gqk0dEgXLE9tz0dDTo03tAPJmR1InFpusyLp2XPPJ4XLhWFCMDDb0xr4oayV1XDOi3U9eKERtcASo1IDo8zDEuY8NHFdJlNKpfJ7P0JSB0hEwoc","Q":"yNg4tO9PVrwQ0CaouP0jniFouHAEfZMyJ0vVM1xPVim7jeKysOJi79lHO7OWNSABB3hY8dLcMSiKxS3BaU0Q2ns9Gw-NLR6eqT0BN9Nzh-a0sa0iuPUWCPgsWdrqjIf4FBX0Ra-6q7_LxuWCncehUAtoLFWMkcOrEj03Gwz1lUU","QI":"UWdYA2qY622GL97_tPvrgk1VWSqoO2a0-TlGu0gK51T_Z1hqLwzG9QpTujoxnRU7Js_QUe_9cIEO72N0qliEm6A-MClM-5LUA9XleyAosb1cblLjAPqT9gfGbRRAbB3Aj3YwsgASGUiSpGVfaUuXn-OnuLoidZQXw7w9xcPuxMo","Use":null,"X":null,"X5t":null,"X5tS256":null,"X5u":null,"Y":null,"KeySize":2048,"HasPrivateKey":true,"CryptoProviderFactory":{"CryptoProviderCache":{},"CustomCryptoProvider":null,"CacheSignatureProviders":true,"SignatureProviderObjectPoolCacheSize":16}} |
@ -0,0 +1 @@ | |||
window.location.href = document.querySelector("meta[http-equiv=refresh]").getAttribute("data-url"); |
@ -0,0 +1,6 @@ | |||
window.addEventListener("load", function () { | |||
var a = document.querySelector("a.PostLogoutRedirectUri"); | |||
if (a) { | |||
window.location = a.href; | |||
} | |||
}); |