Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,6 @@ ASALocalRun/

# MacOS
.DS_Store

# Fake database
*fakedatabase.db
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,36 +81,33 @@ public async Task<IActionResult> 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".
Expand All @@ -124,7 +120,7 @@ public async Task<IActionResult> 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);
Expand All @@ -144,18 +140,13 @@ public async Task<IActionResult> 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));
Expand All @@ -166,7 +157,6 @@ public async Task<IActionResult> Login(LoginInputModel model, string button)
var vm = await BuildLoginViewModelAsync(model);
return View(vm);
}


/// <summary>
/// Show logout page
Expand Down Expand Up @@ -227,7 +217,6 @@ public IActionResult AccessDenied()
return View();
}


/*****************************************/
/* helper APIs for the AccountController */
/*****************************************/
Expand All @@ -243,7 +232,7 @@ private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
{
EnableLocalLogin = local,
ReturnUrl = returnUrl,
Username = context?.LoginHint
Username = context.LoginHint
};

if (!local)
Expand Down Expand Up @@ -332,7 +321,7 @@ private async Task<LoggedOutViewModel> 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
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
}
Expand All @@ -38,59 +31,48 @@ public ClientResistrationController(IClientStore clients)
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> 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 FakeBookingSystem.Database.GetBookingPartnerByInitialAccessToken(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
bookingPartner.ClientProperties = new ClientModel
{
ClientUri = model.ClientUri,
LogoUri = model.LogoUri,
GrantTypes = model.GrantTypes,
RedirectUris = model.RedirectUris,
Scope = model.Scope,
};
FakeBookingSystem.Database.SaveBookingPartner(bookingPartner);

await FakeBookingSystem.Database.SaveBookingPartner(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,
Expand All @@ -103,7 +85,8 @@ public async Task<IActionResult> 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);
}
}

Expand All @@ -122,7 +105,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";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,16 +9,18 @@ namespace IdentityServer
{
public class ClientStore : IClientStore
{
public Task<Client> FindClientByIdAsync(string clientId)
public async Task<Client> FindClientByIdAsync(string clientId)
{
var bookingPartner = FakeBookingSystem.Database.GetBookingPartner(clientId);
return Task.FromResult(this.ConvertToIS4Client(bookingPartner));
var bookingPartner = await FakeBookingSystem.Database.GetBookingPartner(clientId);
return ConvertToIS4Client(bookingPartner);
}

private 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 OpenActive.FakeDatabase.NET;
using System;
using System.Collections.Generic;
Expand All @@ -25,5 +24,4 @@ public class BookingPartnerModel
public IEnumerable<string> ApiGrantNames { get; set; }
public BookingPartnerTable BookingPartner { get; set; }
}

}
Loading