diff --git a/.gitignore b/.gitignore index 07370c31..c83e3a48 100644 --- a/.gitignore +++ b/.gitignore @@ -333,3 +333,6 @@ ASALocalRun/ # MacOS .DS_Store + +# Fake database +*fakedatabase.db diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountController.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountController.cs index 789d167f..227df37a 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountController.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountController.cs @@ -9,7 +9,6 @@ using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Stores; -using IdentityServer4.Test; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -82,36 +81,33 @@ public async Task Login(LoginInputModel model, string button) // 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.GrantConsentAsync(context, ConsentResponse.Denied); + if (context == null) + return Redirect("~/"); - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - if (await _clientStore.IsPkceClientAsync(context.ClientId)) - { - // if the client is PKCE then we assume it's native, so this change in how to - // return the response is for better UX for the end user. - return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); - } + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); - return Redirect(model.ReturnUrl); - } - else + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (await _clientStore.IsPkceClientAsync(context.ClientId)) { - // since we don't have a valid context, then we just go back to the home page - return Redirect("~/"); + // if the client is PKCE then we assume it's native, so this change in how to + // return the response is for better UX for the end user. + return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); } + + return Redirect(model.ReturnUrl); + + // since we don't have a valid context, then we just go back to the home page } if (ModelState.IsValid) { // validate username/password against in-memory store - if (_users.ValidateCredentials(model.Username, model.Password)) + if (await _users.ValidateCredentials(model.Username, model.Password)) { - var user = _users.FindByUsername(model.Username); + var user = await _users.FindByUsername(model.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.ClientId)); // only set explicit expiration here if user chooses "remember me". @@ -124,7 +120,7 @@ public async Task Login(LoginInputModel model, string button) IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; - }; + } // issue authentication cookie with subject ID and username await HttpContext.SignInAsync(user.SubjectId, user.Username, props); @@ -144,18 +140,13 @@ public async Task Login(LoginInputModel model, string button) // request for a local page if (Url.IsLocalUrl(model.ReturnUrl)) - { return Redirect(model.ReturnUrl); - } - else if (string.IsNullOrEmpty(model.ReturnUrl)) - { + + 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"); - } + + // 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?.ClientId)); @@ -166,7 +157,6 @@ public async Task Login(LoginInputModel model, string button) var vm = await BuildLoginViewModelAsync(model); return View(vm); } - /// /// Show logout page @@ -218,7 +208,7 @@ public async Task Logout(LogoutInputModel model) return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); } - return View("LoggedOut", vm); + return Redirect("/"); } [HttpGet] @@ -227,7 +217,6 @@ public IActionResult AccessDenied() return View(); } - /*****************************************/ /* helper APIs for the AccountController */ /*****************************************/ @@ -243,7 +232,7 @@ private async Task BuildLoginViewModelAsync(string returnUrl) { EnableLocalLogin = local, ReturnUrl = returnUrl, - Username = context?.LoginHint + Username = context.LoginHint }; if (!local) @@ -332,7 +321,7 @@ private async Task BuildLoggedOutViewModelAsync(string logou { AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, - ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, + ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout.ClientName, SignOutIframeUrl = logout?.SignOutIFrameUrl, LogoutId = logoutId }; diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountOptions.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountOptions.cs index f191706c..dc2e640e 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountOptions.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Account/AccountOptions.cs @@ -1,7 +1,6 @@ // 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; namespace src diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientRegistrationController.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientRegistrationController.cs index b974de5f..ba0201d5 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientRegistrationController.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientRegistrationController.cs @@ -1,18 +1,11 @@ using IdentityModel; -using IdentityServer4.Events; -using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Stores; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Primitives; using OpenActive.FakeDatabase.NET; using System; -using System.Collections.Generic; using System.Linq; -using System.Numerics; -using System.Security.Cryptography; -using System.Text; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -23,11 +16,11 @@ namespace IdentityServer // [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Consumes("application/json")] [Produces("application/json")] - public class ClientResistrationController : ControllerBase + public class ClientRegistrationController : ControllerBase { private readonly IClientStore _clients; - public ClientResistrationController(IClientStore clients) + public ClientRegistrationController(IClientStore clients) { _clients = clients; } @@ -38,59 +31,46 @@ public ClientResistrationController(IClientStore clients) [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task PostAsync([FromBody] ClientRegistrationModel model) { - if (model.GrantTypes == null) - { - model.GrantTypes = new[] { OidcConstants.GrantTypes.AuthorizationCode, OidcConstants.GrantTypes.RefreshToken }; - } + model.GrantTypes ??= new[] {OidcConstants.GrantTypes.AuthorizationCode, OidcConstants.GrantTypes.RefreshToken}; if (model.GrantTypes.Any(x => x == OidcConstants.GrantTypes.Implicit) || model.GrantTypes.Any(x => x == OidcConstants.GrantTypes.AuthorizationCode)) { if (!model.RedirectUris.Any()) - { return BadRequest("A redirect URI is required for the supplied grant type."); - } if (model.RedirectUris.Any(redirectUri => !Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))) - { return BadRequest("One or more of the redirect URIs are invalid."); - } } // generate a secret for the client var key = KeyGenerator.GenerateSecret(); - - StringValues headerValues; var registrationKey = string.Empty; - if (Request.Headers.TryGetValue("Authorization", out headerValues)) - { + if (Request.Headers.TryGetValue("Authorization", out var headerValues)) registrationKey = headerValues.FirstOrDefault().Substring("Bearer ".Length); - } // update the booking system - var bookingPartner = FakeBookingSystem.Database.GetBookingPartnerByInitialAccessToken(registrationKey); + var bookingPartner = await BookingPartnerTable.GetByInitialAccessToken(registrationKey); if (bookingPartner == null) return Unauthorized("Initial Access Token is not valid, or is expired"); bookingPartner.Registered = true; bookingPartner.ClientSecret = key.Sha256(); bookingPartner.Name = model.ClientName; - bookingPartner.ClientProperties = new OpenActive.FakeDatabase.NET.ClientModel - { - ClientUri = model.ClientUri, - LogoUri = model.LogoUri, - GrantTypes = model.GrantTypes, - RedirectUris = model.RedirectUris, - Scope = model.Scope, - }; - FakeBookingSystem.Database.SaveBookingPartner(bookingPartner); + bookingPartner.ClientUri = model.ClientUri; + bookingPartner.RestoreAccessUri = model.ClientRegistrationUri; + bookingPartner.LogoUri = model.LogoUri; + bookingPartner.GrantTypes = model.GrantTypes; + bookingPartner.RedirectUris = model.RedirectUris; + bookingPartner.Scope = model.Scope; + + await BookingPartnerTable.Save(bookingPartner); // Read the updated client from the database and reflect back in the request var client = await _clients.FindClientByIdAsync(bookingPartner.ClientId); if (bookingPartner.ClientSecret != client.ClientSecrets?.FirstOrDefault()?.Value) - { return Problem(title: "New client secret not updated in cache", statusCode: 500); - } + var response = new ClientRegistrationResponse { ClientId = client.ClientId, @@ -103,7 +83,8 @@ public async Task PostAsync([FromBody] ClientRegistrationModel mo Scope = string.Join(' ', client.AllowedScopes) }; - return CreatedAtAction("ClientRegistration", response); + var baseUrl = $"{Request.Scheme}://{Request.Host.Value}/connect/register"; + return Created($"{baseUrl}/{client.ClientId}", response); } } @@ -115,6 +96,9 @@ public class ClientRegistrationModel [JsonPropertyName(OidcConstants.ClientMetadata.ClientUri)] public string ClientUri { get; set; } + [JsonPropertyName(OidcConstants.ClientMetadata.InitiateLoginUris)] + public string ClientRegistrationUri { get; set; } + [JsonPropertyName(OidcConstants.ClientMetadata.LogoUri)] public string LogoUri { get; set; } @@ -122,7 +106,7 @@ public class ClientRegistrationModel public string[] GrantTypes { get; set; } [JsonPropertyName(OidcConstants.ClientMetadata.RedirectUris)] - public string[] RedirectUris { get; set; } = new string[] {}; + public string[] RedirectUris { get; set; } = {}; public string Scope { get; set; } = "openid profile email"; } diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientStore.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientStore.cs index 720b7990..fe527385 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientStore.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Client/ClientStore.cs @@ -1,8 +1,6 @@ using IdentityServer4.Models; using IdentityServer4.Stores; -using Microsoft.Extensions.Logging; using OpenActive.FakeDatabase.NET; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -11,34 +9,36 @@ namespace IdentityServer { public class ClientStore : IClientStore { - public Task FindClientByIdAsync(string clientId) + public async Task FindClientByIdAsync(string clientId) { - var bookingPartner = FakeBookingSystem.Database.GetBookingPartner(clientId); - return Task.FromResult(this.ConvertToIS4Client(bookingPartner)); + var bookingPartner = await BookingPartnerTable.GetByClientId(clientId); + return ConvertToIs4Client(bookingPartner); } - private Client ConvertToIS4Client(BookingPartnerTable bookingPartner) + private static Client ConvertToIs4Client(BookingPartnerTable bookingPartner) { - if (bookingPartner == null) return null; - return new Client() + if (bookingPartner == null) + return null; + + return new Client { Enabled = bookingPartner.Registered, ClientId = bookingPartner.ClientId, ClientName = bookingPartner.Name, - AllowedGrantTypes = bookingPartner.ClientProperties?.GrantTypes == null ? new List() : bookingPartner.ClientProperties.GrantTypes.ToList(), - ClientSecrets = bookingPartner.ClientSecret == null ? new List() : new List() { new Secret(bookingPartner.ClientSecret) }, - AllowedScopes = bookingPartner.ClientProperties?.Scope == null ? new List() : bookingPartner.ClientProperties.Scope.Split(' ').ToList(), + AllowedGrantTypes = bookingPartner.GrantTypes == null ? new List() : bookingPartner.GrantTypes.ToList(), + ClientSecrets = bookingPartner.ClientSecret == null ? new List() : new List { new Secret(bookingPartner.ClientSecret) }, + AllowedScopes = bookingPartner.Scope == null ? new List() : bookingPartner.Scope.Split(' ').ToList(), Claims = bookingPartner.ClientId == null ? new List() : new List() { new System.Security.Claims.Claim("https://openactive.io/clientId", bookingPartner.ClientId) }, ClientClaimsPrefix = "", AlwaysSendClientClaims = true, AlwaysIncludeUserClaimsInIdToken = true, AllowOfflineAccess = true, UpdateAccessTokenClaimsOnRefresh = true, - RedirectUris = bookingPartner.ClientProperties?.RedirectUris == null ? new List() : bookingPartner.ClientProperties.RedirectUris.ToList(), + RedirectUris = bookingPartner.RedirectUris == null ? new List() : bookingPartner.RedirectUris.ToList(), RequireConsent = true, RequirePkce = true, - LogoUri = bookingPartner.ClientProperties?.LogoUri, - ClientUri = bookingPartner.ClientProperties?.ClientUri + LogoUri = bookingPartner.LogoUri, + ClientUri = bookingPartner.ClientUri }; } } diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnerViewModel.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnerViewModel.cs index 26cb3e2e..127dcdeb 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnerViewModel.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnerViewModel.cs @@ -1,16 +1,41 @@ // 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 OpenActive.FakeDatabase.NET; using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace src { public class BookingPartnerViewModel { public IEnumerable BookingPartners { get; set; } + + public static async Task Build(long? sellerUserId = null) + { + var bookingPartners = sellerUserId.HasValue + ? await BookingPartnerTable.GetBySellerUserId(sellerUserId.Value) + : await BookingPartnerTable.Get(); + var list = (await Task.WhenAll(bookingPartners.Select(async bookingPartner => + { + var bookingStatistics = await BookingStatistics.Get(bookingPartner.ClientId); + return new BookingPartnerModel + { + ClientId = bookingPartner.ClientId, + ClientName = bookingPartner.Name, + ClientLogoUrl = bookingPartner.LogoUri, + ClientUrl = bookingPartner.ClientUri, + RestoreAccessUrl = bookingPartner.RestoreAccessUri, + BookingPartner = bookingPartner, + SellersEnabled = bookingStatistics.SellersEnabled, + BookingsByBroker = bookingStatistics.BookingsByBroker + }; + }))).ToList(); + + return new BookingPartnerViewModel { BookingPartners = list }; + } } public class BookingPartnerModel @@ -18,12 +43,14 @@ public class BookingPartnerModel public string ClientId { get; set; } public string ClientName { get; set; } public string ClientUrl { get; set; } + public string RestoreAccessUrl { get; set; } public string ClientLogoUrl { get; set; } public DateTime Created { get; set; } public DateTime? Expires { get; set; } public IEnumerable IdentityGrantNames { get; set; } public IEnumerable ApiGrantNames { get; set; } public BookingPartnerTable BookingPartner { get; set; } + public IDictionary BookingsByBroker { get; set; } + public int SellersEnabled { get; set; } } - } \ No newline at end of file diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnersController.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnersController.cs index 1915f813..ff4e22a1 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnersController.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/BookingPartnersController.cs @@ -1,21 +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. - using IdentityServer4.Services; using IdentityServer4.Stores; using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using IdentityServer4.Events; using IdentityServer4.Extensions; using OpenActive.FakeDatabase.NET; -using System.Security.Cryptography; using System; -using IdentityServer4.Models; -using System.Text.RegularExpressions; using IdentityServer; namespace src @@ -25,48 +19,44 @@ namespace src /// [SecurityHeaders] [Authorize] + [Route("booking-partners")] public class BookingPartnersController : Controller { private readonly IIdentityServerInteractionService _interaction; private readonly IClientStore _clients; - private readonly IResourceStore _resources; private readonly IEventService _events; - public BookingPartnersController(IIdentityServerInteractionService interaction, - IClientStore clients, - IResourceStore resources, - IEventService events) + public BookingPartnersController(IIdentityServerInteractionService interaction, IClientStore clients, IEventService events) { _interaction = interaction; _clients = clients; - _resources = resources; _events = events; } - /// - /// Show list of grants - /// - [HttpGet] - public async Task Index() + [HttpGet("seller-admin")] + public async Task SellerAdmin() { - return View("Index", await BuildViewModelAsync()); + var sellerUserId = long.Parse(User.GetSubjectId()); + return View("SellerAdmin", await BookingPartnerViewModel.Build(sellerUserId)); } - /// - /// Show list of grants - /// - [HttpGet] - public async Task Edit(string Id) + [HttpGet("sys-admin")] + public async Task SysAdmin() { - var content = await BuildBookingPartnerViewModelAsync(Id); - if (content == null) return NotFound(); + return View("SysAdmin", await BookingPartnerViewModel.Build()); + } + + [HttpGet("edit/{id}")] + public async Task Edit(string id) + { + var content = await BuildBookingPartnerModel(id); + if (content == null) + return NotFound(); + return View("BookingPartnerEdit", content); } - /// - /// Show list of grants - /// - [HttpGet] + [HttpGet("create")] public async Task Create() { return View("BookingPartnerCreate", await Task.FromResult(new BookingPartnerModel())); @@ -75,11 +65,11 @@ public async Task Create() /// /// Add a new booking partner /// - [HttpPost] + [HttpPost("create")] [ValidateAntiForgeryToken] - public async Task CreateBookingPartner(string email, string bookingPartnerName) + public async Task Create([FromForm] string email, [FromForm] string bookingPartnerName) { - var newBookingPartner = new BookingPartnerTable() + var newBookingPartner = new BookingPartnerTable { ClientId = Guid.NewGuid().ToString(), Name = bookingPartnerName, @@ -92,112 +82,97 @@ public async Task CreateBookingPartner(string email, string booki BookingsSuspended = false }; - FakeBookingSystem.Database.AddBookingPartner(newBookingPartner); + await BookingPartnerTable.Add(newBookingPartner); + return Redirect($"/booking-partners/edit/{newBookingPartner.ClientId}"); + } + + /// + /// Handle postback to remove a client + /// + [HttpPost("remove")] + [ValidateAntiForgeryToken] + public async Task Remove([FromForm] string clientId) + { + await _interaction.RevokeUserConsentAsync(clientId); + await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); - return View("BookingPartnerEdit", await BuildBookingPartnerViewModelAsync(newBookingPartner.ClientId)); + return Redirect("/booking-partners/seller-admin"); } /// - /// Handle postback to revoke a client + /// Handle postback to delete a client /// - [HttpPost] + [HttpPost("delete")] [ValidateAntiForgeryToken] - public async Task Revoke(string clientId) + public async Task Delete([FromForm] string clientId) { await _interaction.RevokeUserConsentAsync(clientId); + await FakeBookingSystem.Database.DeleteBookingPartner(clientId); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); - return RedirectToAction("Index"); + return Redirect("/booking-partners/sys-admin"); } /// /// Handle postback to suspend a client /// - [HttpPost] + [HttpPost("suspend")] [ValidateAntiForgeryToken] - public async Task Suspend(string clientId) + public async Task Suspend([FromForm] string clientId) { var client = await _clients.FindClientByIdAsync(clientId); client.AllowedScopes.Remove("openactive-openbooking"); await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); - FakeBookingSystem.Database.UpdateBookingPartnerScope( - clientId, - "openid profile openactive-ordersfeed", - true - ); - - return RedirectToAction("Index"); + await BookingPartnerTable.UpdateScope(clientId, "openid profile openactive-ordersfeed", true); + return Redirect("/booking-partners/seller-admin"); } /// /// Handle postback to generate a registration key /// - [HttpPost] + [HttpPost("regenerate-key")] [ValidateAntiForgeryToken] - public async Task RegenerateKey(string clientId) + public async Task RegenerateKey([FromForm] string clientId) { - var bookingPartner = FakeBookingSystem.Database.GetBookingPartner(clientId); - FakeBookingSystem.Database.SetBookingPartnerKey(clientId, KeyGenerator.GenerateInitialAccessToken(bookingPartner.Name)); + var bookingPartner = await BookingPartnerTable.GetByClientId(clientId); + await BookingPartnerTable.SetKey(clientId, KeyGenerator.GenerateInitialAccessToken(bookingPartner.Name)); - return View("BookingPartnerEdit", await BuildBookingPartnerViewModelAsync(clientId)); + return Redirect($"/booking-partners/edit/{clientId}"); } /// /// Handle postback to generate a registration key, and a new client secret /// - [HttpPost] + [HttpPost("regenerate-all-keys")] [ValidateAntiForgeryToken] - public async Task RegenerateAllKeys(string clientId) + public async Task RegenerateAllKeys([FromForm] string clientId) { - var bookingPartner = FakeBookingSystem.Database.GetBookingPartner(clientId); - FakeBookingSystem.Database.ResetBookingPartnerKey(clientId, KeyGenerator.GenerateInitialAccessToken(bookingPartner.Name)); + var bookingPartner = await BookingPartnerTable.GetByClientId(clientId); + await BookingPartnerTable.ResetKey(clientId, KeyGenerator.GenerateInitialAccessToken(bookingPartner.Name)); // TODO: Is this cached in memory, does it need updating?? //var client = await _clients.FindClientByIdAsync(clientId); //client.ClientSecrets = new List() { new Secret(clientSecret.Sha256()) }; - return View("BookingPartnerEdit", await BuildBookingPartnerViewModelAsync(clientId)); + return Redirect($"/booking-partners/edit/{clientId}"); } - private async Task BuildBookingPartnerViewModelAsync(string clientId) + private static async Task BuildBookingPartnerModel(string clientId) { // var client = await _clients.FindClientByIdAsync(clientId); - var bookingPartner = FakeBookingSystem.Database.GetBookingPartner(clientId); + var bookingPartner = await BookingPartnerTable.GetByClientId(clientId); + if (bookingPartner == null) + return null; - if (bookingPartner == null) return null; - - return new BookingPartnerModel() + return new BookingPartnerModel { ClientId = bookingPartner.ClientId, ClientName = bookingPartner.Name, - ClientLogoUrl = bookingPartner.ClientProperties?.LogoUri, - ClientUrl = bookingPartner.ClientProperties?.ClientUri, + ClientLogoUrl = bookingPartner.LogoUri, + ClientUrl = bookingPartner.ClientUri, BookingPartner = bookingPartner }; } - private async Task BuildViewModelAsync() - { - var bookingPartners = FakeBookingSystem.Database.GetBookingPartners(); - var list = new List(); - foreach (var bookingPartner in bookingPartners) - { - var item = new BookingPartnerModel() - { - ClientId = bookingPartner.ClientId, - ClientName = bookingPartner.Name, - ClientLogoUrl = bookingPartner.ClientProperties?.LogoUri, - ClientUrl = bookingPartner.ClientProperties?.ClientUri, - BookingPartner = bookingPartner - }; - - list.Add(item); - } - - return new BookingPartnerViewModel - { - BookingPartners = list - }; - } } } \ No newline at end of file diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/PersistedGrantStore.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/PersistedGrantStore.cs index 9408faa6..0f50aca9 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/PersistedGrantStore.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Grants/PersistedGrantStore.cs @@ -1,7 +1,6 @@ using IdentityServer4.Models; using IdentityServer4.Stores; using OpenActive.FakeDatabase.NET; -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -10,32 +9,28 @@ namespace IdentityServer { public class AcmePersistedGrantStore : IPersistedGrantStore { - public Task> GetAllAsync(string subjectId) + public async Task> GetAllAsync(string subjectId) { - var grants = FakeBookingSystem.Database.GetAllGrants(subjectId); - - List persistedGrants = new List(); - foreach(var grant in grants) + var grants = await FakeBookingSystem.Database.GetAllGrants(subjectId); + var persistedGrants = grants.Select(grant => new PersistedGrant { - persistedGrants.Add(new PersistedGrant() - { - Key = grant.Key, - Type = grant.Type, - SubjectId = grant.SubjectId, - ClientId = grant.ClientId, - CreationTime = grant.CreationTime, - Expiration = grant.Expiration, - Data = grant.Data - }); - } - return Task.FromResult>(persistedGrants); + Key = grant.Key, + Type = grant.Type, + SubjectId = grant.SubjectId, + ClientId = grant.ClientId, + CreationTime = grant.CreationTime, + Expiration = grant.Expiration, + Data = grant.Data + }).ToList(); + + return persistedGrants; } - public Task GetAsync(string key) + public async Task GetAsync(string key) { - var grant = FakeBookingSystem.Database.GetGrant(key); + var grant = await FakeBookingSystem.Database.GetGrant(key); - return Task.FromResult(grant != null ? new PersistedGrant() + return grant != null ? new PersistedGrant { Key = grant.Key, Type = grant.Type, @@ -44,27 +39,31 @@ public Task GetAsync(string key) CreationTime = grant.CreationTime, Expiration = grant.Expiration, Data = grant.Data - } : null); + } : null; } - public async Task RemoveAllAsync(string subjectId, string clientId) + public Task RemoveAllAsync(string subjectId, string clientId) { FakeBookingSystem.Database.RemoveGrant(subjectId, clientId); + return Task.CompletedTask; } - public async Task RemoveAllAsync(string subjectId, string clientId, string type) + public Task RemoveAllAsync(string subjectId, string clientId, string type) { FakeBookingSystem.Database.RemoveGrant(subjectId, clientId, type); + return Task.CompletedTask; } - public async Task RemoveAsync(string key) + public Task RemoveAsync(string key) { FakeBookingSystem.Database.RemoveGrant(key); + return Task.CompletedTask; } - public async Task StoreAsync(PersistedGrant grant) + public Task StoreAsync(PersistedGrant grant) { FakeBookingSystem.Database.AddGrant(grant.Key, grant.Type, grant.SubjectId, grant.ClientId, grant.CreationTime, grant.Expiration, grant.Data); + return Task.CompletedTask; } } } diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Profile/ProfileService.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Profile/ProfileService.cs index 510b6a4e..08756234 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Profile/ProfileService.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Profile/ProfileService.cs @@ -1,13 +1,8 @@ using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; -using IdentityServer4.Test; -using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; namespace IdentityServer @@ -40,14 +35,14 @@ public ProfileService(IUserRepository users, ILogger logger) /// /// The context. /// - public virtual Task GetProfileDataAsync(ProfileDataRequestContext context) + public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context) { context.LogProfileRequest(Logger); // Claims added here are defined be the API and RESOURCE configurations in Config.cs, only the requested claims will be added to the IssuedClaims collection if (context.RequestedClaimTypes.Any()) { - var user = Users.FindBySubjectId(context.Subject.GetSubjectId()); + var user = await Users.FindBySubjectId(context.Subject.GetSubjectId()); if (user != null) { context.AddRequestedClaims(user.Claims); @@ -57,13 +52,10 @@ public virtual Task GetProfileDataAsync(ProfileDataRequestContext context) { context.IssuedClaims.Add(sellerIdClaim); } - } } context.LogIssuedClaims(Logger); - - return Task.CompletedTask; } /// @@ -72,15 +64,12 @@ public virtual Task GetProfileDataAsync(ProfileDataRequestContext context) /// /// The context. /// - public virtual Task IsActiveAsync(IsActiveContext context) + public virtual async Task IsActiveAsync(IsActiveContext context) { - Logger.LogDebug("IsActive called from: {caller}", context.Caller); + Logger.LogDebug("IsActive called from: {Caller}", context.Caller); - var user = Users.FindBySubjectId(context.Subject.GetSubjectId()); + var user = await Users.FindBySubjectId(context.Subject.GetSubjectId()); context.IsActive = user?.IsActive == true; - - return Task.CompletedTask; } - } } diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/Config.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/Config.cs index 0b7b2bac..0e949d76 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/Config.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/Config.cs @@ -3,7 +3,6 @@ using IdentityModel; -using IdentityServer4; using IdentityServer4.Models; using System.Collections.Generic; diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/CustomIdentityResource.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/CustomIdentityResource.cs index b4f87edb..d5ae5b43 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/CustomIdentityResource.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Resources/CustomIdentityResource.cs @@ -1,8 +1,5 @@ using IdentityServer4.Models; -using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace IdentityServer { diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Extensions.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Extensions.cs index 8ebf91c5..358a7df1 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Extensions.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Extensions.cs @@ -1,8 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace IdentityServer { diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Interfaces.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Interfaces.cs index d81a4d1f..2900161a 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Interfaces.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/Interfaces.cs @@ -1,15 +1,11 @@ -using OpenActive.FakeDatabase.NET; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace IdentityServer { public interface IUserRepository { - bool ValidateCredentials(string username, string password); - UserWithClaims FindBySubjectId(string subjectId); - User FindByUsername(string username); + Task ValidateCredentials(string username, string password); + Task FindBySubjectId(string subjectId); + Task FindByUsername(string username); } } diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/ResourceOwnerPasswordValidator.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/ResourceOwnerPasswordValidator.cs index 95224345..07025669 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/ResourceOwnerPasswordValidator.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/ResourceOwnerPasswordValidator.cs @@ -1,8 +1,5 @@ using IdentityModel; using IdentityServer4.Validation; -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace IdentityServer @@ -16,16 +13,14 @@ public ResourceOwnerPasswordValidator(IUserRepository userRepository) _userRepository = userRepository; } - public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) + public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { // context.Username refers to the cardId; context.Password refers to Lastname - if (_userRepository.ValidateCredentials(context.UserName, context.Password)) + if (await _userRepository.ValidateCredentials(context.UserName, context.Password)) { - var user = _userRepository.FindByUsername(context.UserName); + var user = await _userRepository.FindByUsername(context.UserName); context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password); } - - return Task.FromResult(0); } } } diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/UserRepository.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/UserRepository.cs index 09a9bcc8..ae3b5089 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/UserRepository.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Custom/Users/UserRepository.cs @@ -1,8 +1,5 @@ -using IdentityModel; -using OpenActive.FakeDatabase.NET; -using System; +using OpenActive.FakeDatabase.NET; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using System.Threading.Tasks; @@ -10,33 +7,28 @@ namespace IdentityServer { public class UserRepository : IUserRepository { - private string jsonLdIdBaseUrl; + private readonly string _jsonLdIdBaseUrl; public UserRepository(string jsonLdIdBaseUrl) { - this.jsonLdIdBaseUrl = jsonLdIdBaseUrl; + this._jsonLdIdBaseUrl = jsonLdIdBaseUrl; } - public bool ValidateCredentials(string username, string password) + public Task ValidateCredentials(string username, string password) { return FakeBookingSystem.Database.ValidateSellerUserCredentials(username, password); } - public UserWithClaims FindBySubjectId(string subjectId) + public async Task FindBySubjectId(string subjectId) { - if (long.TryParse(subjectId, out long longSubjectId)) - { - return GetUserFromSellerUserWithClaims(FakeBookingSystem.Database.GetSellerUserById(longSubjectId)); - } - else - { - return null; - } + return long.TryParse(subjectId, out var longSubjectId) + ? GetUserFromSellerUserWithClaims(await FakeBookingSystem.Database.GetSellerUserById(longSubjectId)) + : null; } - public User FindByUsername(string username) + public async Task FindByUsername(string username) { - return GetUserFromSellerUser(FakeBookingSystem.Database.GetSellerUser(username)); + return GetUserFromSellerUser(await FakeBookingSystem.Database.GetSellerUser(username)); } // TODO: Make this an extension method @@ -58,16 +50,19 @@ private UserWithClaims GetUserFromSellerUserWithClaims(SellerUserTable sellerUse IsActive = true, Claims = new List() }; + AddClaimIfNotNull(user.Claims, "https://openactive.io/sellerName", sellerUser.SellerTable.Name); - AddClaimIfNotNull(user.Claims, "https://openactive.io/sellerId", jsonLdIdBaseUrl + "/api/identifiers/sellers/" + sellerUser.SellerTable.Id); + AddClaimIfNotNull(user.Claims, "https://openactive.io/sellerId", _jsonLdIdBaseUrl + "/api/identifiers/sellers/" + sellerUser.SellerTable.Id); AddClaimIfNotNull(user.Claims, "https://openactive.io/sellerUrl", sellerUser.SellerTable.Url); AddClaimIfNotNull(user.Claims, "https://openactive.io/sellerLogo", sellerUser.SellerTable.LogoUrl); return user; } - private User GetUserFromSellerUser(SellerUserTable sellerUser) + private static User GetUserFromSellerUser(SellerUserTable sellerUser) { - if (sellerUser == null) return null; + if (sellerUser == null) + return null; + return new User { Username = sellerUser.Username, diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Program.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Program.cs index 8c6d9a5d..e5fb8053 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Program.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Program.cs @@ -43,7 +43,7 @@ public static int Main(string[] args) } catch (Exception ex) { - Log.Fatal(ex, "Host terminated unexpectedly."); + Log.Fatal(ex, "Host terminated unexpectedly"); return 1; } finally diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Startup.cs b/Examples/BookingSystem.AspNetCore.IdentityServer/Startup.cs index b759c40f..aaf3bebf 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Startup.cs +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Startup.cs @@ -1,15 +1,12 @@ // 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 IdentityServer4.Services; using IdentityServer4.Stores; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Configuration; -using src; namespace IdentityServer { diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/LoggedOut.cshtml b/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/LoggedOut.cshtml deleted file mode 100644 index 99599c0c..00000000 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/LoggedOut.cshtml +++ /dev/null @@ -1,34 +0,0 @@ -@model LoggedOutViewModel - -@{ - // set this so the layout rendering sees an anonymous user - ViewData["signed-out"] = true; -} - - - -@section scripts -{ - @if (Model.AutomaticRedirectAfterSignOut) - { - - } -} diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/Login.cshtml b/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/Login.cshtml index 5905626e..1e69d86c 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/Login.cshtml +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Views/Account/Login.cshtml @@ -1,3 +1,4 @@ +@using OpenActive.FakeDatabase.NET @model LoginViewModel \ No newline at end of file diff --git a/Examples/BookingSystem.AspNetCore.IdentityServer/Views/BookingPartners/BookingPartnerCreate.cshtml b/Examples/BookingSystem.AspNetCore.IdentityServer/Views/BookingPartners/BookingPartnerCreate.cshtml index b010dae8..b4069a94 100644 --- a/Examples/BookingSystem.AspNetCore.IdentityServer/Views/BookingPartners/BookingPartnerCreate.cshtml +++ b/Examples/BookingSystem.AspNetCore.IdentityServer/Views/BookingPartners/BookingPartnerCreate.cshtml @@ -1,42 +1,42 @@ @model BookingPartnerModel -@using System; -
-