mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-11 01:35:22 +01:00
Request consent from user before giving application access to the user's data & services.
This commit is contained in:
parent
1447b5e8be
commit
c5227d9996
15 changed files with 410 additions and 188 deletions
|
@ -114,20 +114,27 @@ namespace BTCPayServer.Tests
|
|||
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid&nonce={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
var url = s.Driver.Url;
|
||||
var results = url.Split("#").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
|
||||
|
||||
//in Implicit mode, you renew your token by hitting the same endpoint but adding prompt=none. If you are still logged in on the site, you will receive a fresh token.
|
||||
var implicitAuthorizeUrlSilentModel = new Uri($"{implicitAuthorizeUrl.OriginalString}&prompt=none");
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrlSilentModel);
|
||||
url = s.Driver.Url;
|
||||
results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
|
||||
LogoutFlow(tester, id, s);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
|
||||
Assert.Throws<NoSuchElementException>(() => s.Driver.FindElement(By.Id("consent-yes")));
|
||||
results = url.Split("#").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
await TestApiAgainstAccessToken(results["access_token"], tester, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +178,7 @@ namespace BTCPayServer.Tests
|
|||
$"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access&state={Guid.NewGuid().ToString()}");
|
||||
s.Driver.Navigate().GoToUrl(authorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
var url = s.Driver.Url;
|
||||
var results = url.Split("?").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
|
@ -204,6 +212,15 @@ namespace BTCPayServer.Tests
|
|||
var refreshedAccessToken = await RefreshAnAccessToken(result.RefreshToken, httpClient, id, secret);
|
||||
|
||||
await TestApiAgainstAccessToken(refreshedAccessToken, tester, user);
|
||||
|
||||
LogoutFlow(tester, id, s);
|
||||
s.Driver.Navigate().GoToUrl(authorizeUrl);
|
||||
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
|
||||
|
||||
Assert.Throws<NoSuchElementException>(() => s.Driver.FindElement(By.Id("consent-yes")));
|
||||
results = url.Split("?").Last().Split("&")
|
||||
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
|
||||
Assert.True(results.ContainsKey("code"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class AuthorizationCodeGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn
|
||||
{
|
||||
public AuthorizationCodeGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
public AuthorizationCodeGrantTypeEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions,
|
||||
UserManager<ApplicationUser> userManager) : base(applicationManager, authorizationManager, signInManager,
|
||||
identityOptions, userManager)
|
||||
{
|
||||
}
|
||||
|
@ -18,4 +24,4 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
return request.IsAuthorizationCodeGrantType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class AuthorizationEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleAuthorizationRequest>
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(
|
||||
OpenIddictServerEvents.HandleAuthorizationRequest notification)
|
||||
{
|
||||
if (!notification.Context.Request.IsAuthorizationRequest())
|
||||
{
|
||||
return OpenIddictServerEventState.Unhandled;
|
||||
}
|
||||
|
||||
var auth = await notification.Context.HttpContext.AuthenticateAsync();
|
||||
if (!auth.Succeeded)
|
||||
{
|
||||
// If the client application request promptless authentication,
|
||||
// return an error indicating that the user is not logged in.
|
||||
if (notification.Context.Request.HasPrompt(OpenIdConnectConstants.Prompts.None))
|
||||
{
|
||||
var properties = new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIdConnectConstants.Properties.Error] = OpenIdConnectConstants.Errors.LoginRequired,
|
||||
[OpenIdConnectConstants.Properties.ErrorDescription] = "The user is not logged in."
|
||||
});
|
||||
|
||||
|
||||
// Ask OpenIddict to return a login_required error to the client application.
|
||||
await notification.Context.HttpContext.ForbidAsync(properties);
|
||||
notification.Context.HandleResponse();
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
await notification.Context.HttpContext.ChallengeAsync();
|
||||
notification.Context.HandleResponse();
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
// Retrieve the profile of the logged in user.
|
||||
var user = await _userManager.GetUserAsync(auth.Principal);
|
||||
if (user == null)
|
||||
{
|
||||
notification.Context.Reject(
|
||||
error: OpenIddictConstants.Errors.InvalidGrant,
|
||||
description: "An internal error has occurred");
|
||||
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
// Create a new authentication ticket.
|
||||
var ticket = await CreateTicketAsync(notification.Context.Request, user);
|
||||
|
||||
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
|
||||
notification.Context.Validate(ticket);
|
||||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
|
||||
public AuthorizationEventHandler(
|
||||
UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions) : base(signInManager, identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Extensions;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
|
@ -15,88 +13,30 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
public abstract class BaseOpenIdGrantHandler<T> : IOpenIddictServerEventHandler<T>
|
||||
where T : class, IOpenIddictServerEvent
|
||||
{
|
||||
private readonly OpenIddictApplicationManager<BTCPayOpenIdClient> _applicationManager;
|
||||
private readonly OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> _authorizationManager;
|
||||
protected readonly SignInManager<ApplicationUser> _signInManager;
|
||||
protected readonly IOptions<IdentityOptions> _identityOptions;
|
||||
|
||||
protected BaseOpenIdGrantHandler(SignInManager<ApplicationUser> signInManager,
|
||||
protected BaseOpenIdGrantHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_authorizationManager = authorizationManager;
|
||||
_signInManager = signInManager;
|
||||
_identityOptions = identityOptions;
|
||||
}
|
||||
|
||||
|
||||
protected async Task<AuthenticationTicket> CreateTicketAsync(
|
||||
OpenIdConnectRequest request, ApplicationUser user,
|
||||
AuthenticationProperties properties = null)
|
||||
{
|
||||
// Create a new ClaimsPrincipal containing the claims that
|
||||
// will be used to create an id_token, a token or a code.
|
||||
var principal = await _signInManager.CreateUserPrincipalAsync(user);
|
||||
|
||||
// Create a new authentication ticket holding the user identity.
|
||||
var ticket = new AuthenticationTicket(principal, properties,
|
||||
OpenIddictServerDefaults.AuthenticationScheme);
|
||||
|
||||
if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
|
||||
{
|
||||
// Note: in this sample, the granted scopes match the requested scope
|
||||
// but you may want to allow the user to uncheck specific scopes.
|
||||
// For that, simply restrict the list of scopes before calling SetScopes.
|
||||
ticket.SetScopes(request.GetScopes());
|
||||
}
|
||||
|
||||
foreach (var claim in ticket.Principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(claim, ticket));
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetDestinations(Claim claim, AuthenticationTicket ticket)
|
||||
{
|
||||
// Note: by default, claims are NOT automatically included in the access and identity tokens.
|
||||
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
|
||||
// whether they should be included in access tokens, in identity tokens or in both.
|
||||
|
||||
|
||||
switch (claim.Type)
|
||||
{
|
||||
case OpenIddictConstants.Claims.Name:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Profile))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
|
||||
case OpenIddictConstants.Claims.Email:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Email))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
|
||||
case OpenIddictConstants.Claims.Role:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Roles))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
default:
|
||||
if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType)
|
||||
{
|
||||
// Never include the security stamp in the access and identity tokens, as it's a secret value.
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
return await OpenIdExtensions.CreateAuthenticationTicket(_applicationManager, _authorizationManager,
|
||||
_identityOptions.Value, _signInManager, request, user, properties);
|
||||
}
|
||||
|
||||
public abstract Task<OpenIddictServerEventState> HandleAsync(T notification);
|
||||
|
|
|
@ -21,9 +21,12 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public ClientCredentialsGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
public ClientCredentialsGrantTypeEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions,
|
||||
UserManager<ApplicationUser> userManager) : base(applicationManager, authorizationManager, signInManager,
|
||||
identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
|
||||
public class LogoutEventHandler: BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleLogoutRequest>
|
||||
public class LogoutEventHandler : BaseOpenIdGrantHandler<OpenIddictServerEvents.HandleLogoutRequest>
|
||||
{
|
||||
public LogoutEventHandler(SignInManager<ApplicationUser> signInManager, IOptions<IdentityOptions> identityOptions) : base(signInManager, identityOptions)
|
||||
public LogoutEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager, IOptions<IdentityOptions> identityOptions) : base(
|
||||
applicationManager, authorizationManager,
|
||||
signInManager, identityOptions)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(OpenIddictServerEvents.HandleLogoutRequest notification)
|
||||
public override async Task<OpenIddictServerEventState> HandleAsync(
|
||||
OpenIddictServerEvents.HandleLogoutRequest notification)
|
||||
{
|
||||
// Ask ASP.NET Core Identity to delete the local and external cookies created
|
||||
// when the user agent is redirected from the external identity provider
|
||||
|
|
139
BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs
Normal file
139
BTCPayServer/Authentication/OpenId/OpenIdExtensions.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Extensions;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public static class OpenIdExtensions
|
||||
{
|
||||
public static async Task<AuthenticationTicket> CreateAuthenticationTicket(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
IdentityOptions identityOptions,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
OpenIdConnectRequest request,
|
||||
ApplicationUser user,
|
||||
AuthenticationProperties properties = null)
|
||||
{
|
||||
// Create a new ClaimsPrincipal containing the claims that
|
||||
// will be used to create an id_token, a token or a code.
|
||||
var principal = await signInManager.CreateUserPrincipalAsync(user);
|
||||
|
||||
// Create a new authentication ticket holding the user identity.
|
||||
var ticket = new AuthenticationTicket(principal, properties,
|
||||
OpenIddictServerDefaults.AuthenticationScheme);
|
||||
|
||||
if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
|
||||
{
|
||||
ticket.SetScopes(request.GetScopes());
|
||||
}
|
||||
else if (request.IsAuthorizationCodeGrantType() &&
|
||||
string.IsNullOrEmpty(ticket.GetInternalAuthorizationId()))
|
||||
{
|
||||
var app = await applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
var authorizationId = await IsUserAuthorized(authorizationManager, request, user.Id, app.Id);
|
||||
if (!string.IsNullOrEmpty(authorizationId))
|
||||
{
|
||||
ticket.SetInternalAuthorizationId(authorizationId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var claim in ticket.Principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(identityOptions, claim, ticket));
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetDestinations(IdentityOptions identityOptions, Claim claim,
|
||||
AuthenticationTicket ticket)
|
||||
{
|
||||
// Note: by default, claims are NOT automatically included in the access and identity tokens.
|
||||
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
|
||||
// whether they should be included in access tokens, in identity tokens or in both.
|
||||
|
||||
|
||||
switch (claim.Type)
|
||||
{
|
||||
case OpenIddictConstants.Claims.Name:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Profile))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
|
||||
case OpenIddictConstants.Claims.Email:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Email))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
|
||||
case OpenIddictConstants.Claims.Role:
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
|
||||
if (ticket.HasScope(OpenIddictConstants.Scopes.Roles))
|
||||
yield return OpenIddictConstants.Destinations.IdentityToken;
|
||||
|
||||
yield break;
|
||||
default:
|
||||
if (claim.Type == identityOptions.ClaimsIdentity.SecurityStampClaimType)
|
||||
{
|
||||
// Never include the security stamp in the access and identity tokens, as it's a secret value.
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return OpenIddictConstants.Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> IsUserAuthorized(
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
OpenIdConnectRequest request, string userId, string applicationId)
|
||||
{
|
||||
var authorizations =
|
||||
await authorizationManager.ListAsync(queryable =>
|
||||
queryable.Where(authorization =>
|
||||
authorization.Subject.Equals(userId, StringComparison.OrdinalIgnoreCase) &&
|
||||
applicationId.Equals(authorization.Application.Id, StringComparison.OrdinalIgnoreCase) &&
|
||||
authorization.Status.Equals(OpenIddictConstants.Statuses.Valid,
|
||||
StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
|
||||
if (authorizations.Length > 0)
|
||||
{
|
||||
var scopeTasks = authorizations.Select(authorization =>
|
||||
(authorizationManager.GetScopesAsync(authorization).AsTask(), authorization.Id));
|
||||
await Task.WhenAll(scopeTasks.Select((tuple) => tuple.Item1));
|
||||
|
||||
var authorizationsWithSufficientScopes = scopeTasks
|
||||
.Select((tuple) => (tuple.Id, Scopes: tuple.Item1.Result))
|
||||
.Where((tuple) => !request.GetScopes().Except(tuple.Scopes).Any());
|
||||
|
||||
if (authorizationsWithSufficientScopes.Any())
|
||||
{
|
||||
return authorizationsWithSufficientScopes.First().Id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
|
@ -14,8 +16,12 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
protected OpenIdGrantHandlerCheckCanSignIn(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
protected OpenIdGrantHandlerCheckCanSignIn(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(
|
||||
applicationManager, authorizationManager, signInManager,
|
||||
identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
|
@ -36,7 +42,6 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
var scheme = notification.Context.Scheme.Name;
|
||||
var authenticateResult = (await notification.Context.HttpContext.AuthenticateAsync(scheme));
|
||||
|
||||
|
||||
var user = await _userManager.GetUserAsync(authenticateResult.Principal);
|
||||
if (user == null)
|
||||
{
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.U2F;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
|
@ -14,9 +16,13 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly U2FService _u2FService;
|
||||
|
||||
public PasswordGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
public PasswordGrantTypeEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IOptions<IdentityOptions> identityOptions, U2FService u2FService) : base(signInManager, identityOptions)
|
||||
IOptions<IdentityOptions> identityOptions, U2FService u2FService) : base(applicationManager,
|
||||
authorizationManager, signInManager, identityOptions)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_u2FService = u2FService;
|
||||
|
@ -54,4 +60,4 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
return OpenIddictServerEventState.Handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Core;
|
||||
|
||||
namespace BTCPayServer.Authentication.OpenId
|
||||
{
|
||||
public class RefreshTokenGrantTypeEventHandler : OpenIdGrantHandlerCheckCanSignIn
|
||||
{
|
||||
public RefreshTokenGrantTypeEventHandler(SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(signInManager,
|
||||
public RefreshTokenGrantTypeEventHandler(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOptions<IdentityOptions> identityOptions, UserManager<ApplicationUser> userManager) : base(
|
||||
applicationManager, authorizationManager, signInManager,
|
||||
identityOptions, userManager)
|
||||
{
|
||||
}
|
||||
|
@ -18,4 +24,4 @@ namespace BTCPayServer.Authentication.OpenId
|
|||
return request.IsRefreshTokenGrantType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
136
BTCPayServer/Controllers/AuthorizationController.cs
Normal file
136
BTCPayServer/Controllers/AuthorizationController.cs
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
* the license and the contributors participating to this project.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AspNet.Security.OpenIdConnect.Extensions;
|
||||
using AspNet.Security.OpenIdConnect.Primitives;
|
||||
using BTCPayServer.Authentication.OpenId;
|
||||
using BTCPayServer.Authentication.OpenId.Models;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.Authorization;
|
||||
using BTCPayServer.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public class AuthorizationController : Controller
|
||||
{
|
||||
private readonly OpenIddictApplicationManager<BTCPayOpenIdClient> _applicationManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> _authorizationManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IOptions<IdentityOptions> _IdentityOptions;
|
||||
|
||||
public AuthorizationController(
|
||||
OpenIddictApplicationManager<BTCPayOpenIdClient> applicationManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
OpenIddictAuthorizationManager<BTCPayOpenIdAuthorization> authorizationManager,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IOptions<IdentityOptions> identityOptions)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_signInManager = signInManager;
|
||||
_authorizationManager = authorizationManager;
|
||||
_userManager = userManager;
|
||||
_IdentityOptions = identityOptions;
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[HttpGet("/connect/authorize")]
|
||||
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
|
||||
{
|
||||
// Retrieve the application details from the database.
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
|
||||
if (application == null)
|
||||
{
|
||||
return View("Error",
|
||||
new ErrorViewModel
|
||||
{
|
||||
Error = OpenIddictConstants.Errors.InvalidClient,
|
||||
ErrorDescription =
|
||||
"Details concerning the calling client application cannot be found in the database"
|
||||
});
|
||||
}
|
||||
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (!string.IsNullOrEmpty(
|
||||
await OpenIdExtensions.IsUserAuthorized(_authorizationManager, request, userId, application.Id)))
|
||||
{
|
||||
return await Authorize(request, "YES", false);
|
||||
}
|
||||
|
||||
// Flow the request_id to allow OpenIddict to restore
|
||||
// the original authorization request from the cache.
|
||||
return View(new AuthorizeViewModel
|
||||
{
|
||||
ApplicationName = await _applicationManager.GetDisplayNameAsync(application),
|
||||
RequestId = request.RequestId,
|
||||
Scope = request.Scope
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
|
||||
[HttpPost("/connect/authorize")]
|
||||
public async Task<IActionResult> Authorize(OpenIdConnectRequest request,
|
||||
string consent, bool createAuthorization = true)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
return View("Error",
|
||||
new ErrorViewModel
|
||||
{
|
||||
Error = OpenIddictConstants.Errors.ServerError,
|
||||
ErrorDescription = "The specified user could not be found"
|
||||
});
|
||||
}
|
||||
|
||||
string type = null;
|
||||
switch (consent.ToUpperInvariant())
|
||||
{
|
||||
case "YESTEMPORARY":
|
||||
type = OpenIddictConstants.AuthorizationTypes.AdHoc;
|
||||
break;
|
||||
case "YES":
|
||||
type = OpenIddictConstants.AuthorizationTypes.Permanent;
|
||||
break;
|
||||
case "NO":
|
||||
default:
|
||||
// Notify OpenIddict that the authorization grant has been denied by the resource owner
|
||||
// to redirect the user agent to the client application using the appropriate response_mode.
|
||||
return Forbid(OpenIddictServerDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
|
||||
// Create a new authentication ticket.
|
||||
var ticket =
|
||||
await OpenIdExtensions.CreateAuthenticationTicket(_applicationManager, _authorizationManager,
|
||||
_IdentityOptions.Value, _signInManager,
|
||||
request, user);
|
||||
if (createAuthorization)
|
||||
{
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
var authorization = await _authorizationManager.CreateAsync(User, user.Id, application.Id,
|
||||
type, ticket.GetScopes().ToImmutableArray(),
|
||||
ticket.Properties.Items.ToImmutableDictionary());
|
||||
ticket.SetInternalAuthorizationId(authorization.Id);
|
||||
}
|
||||
|
||||
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
|
||||
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,30 +10,21 @@ namespace BTCPayServer
|
|||
{
|
||||
public static class OpenIddictExtensions
|
||||
{
|
||||
private static SecurityKey _key = null;
|
||||
public static SecurityKey GetSigningKey(IConfiguration configuration)
|
||||
{
|
||||
if (_key != null)
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
var file = Path.Combine(configuration.GetDataDir(), "rsaparams");
|
||||
|
||||
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(2048);
|
||||
|
||||
var rsa = new RSACryptoServiceProvider(2048);
|
||||
if (File.Exists(file))
|
||||
{
|
||||
RSA.FromXmlString2(File.ReadAllText(file));
|
||||
rsa.FromXmlString2(File.ReadAllText(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
var contents = RSA.ToXmlString2(true);
|
||||
var contents = rsa.ToXmlString2(true);
|
||||
File.WriteAllText(file, contents);
|
||||
}
|
||||
|
||||
RSAParameters KeyParam = RSA.ExportParameters(true);
|
||||
_key = new RsaSecurityKey(KeyParam);
|
||||
return _key;
|
||||
return new RsaSecurityKey(rsa.ExportParameters(true));;
|
||||
}
|
||||
public static OpenIddictServerBuilder ConfigureSigningKey(this OpenIddictServerBuilder builder,
|
||||
IConfiguration configuration)
|
||||
|
|
|
@ -147,7 +147,7 @@ namespace BTCPayServer.Hosting
|
|||
})
|
||||
.AddServer(options =>
|
||||
{
|
||||
|
||||
options.EnableRequestCaching();
|
||||
//Disabled so that Tor works with OpenIddict too
|
||||
options.DisableHttpsRequirement();
|
||||
// Register the ASP.NET Core MVC binder used by OpenIddict.
|
||||
|
@ -182,7 +182,6 @@ namespace BTCPayServer.Hosting
|
|||
options.AddEventHandler<AuthorizationCodeGrantTypeEventHandler>();
|
||||
options.AddEventHandler<RefreshTokenGrantTypeEventHandler>();
|
||||
options.AddEventHandler<ClientCredentialsGrantTypeEventHandler>();
|
||||
options.AddEventHandler<AuthorizationEventHandler>();
|
||||
options.AddEventHandler<LogoutEventHandler>();
|
||||
|
||||
options.ConfigureSigningKey(Configuration);
|
||||
|
|
14
BTCPayServer/Models/Authorization/AuthorizeViewModel.cs
Normal file
14
BTCPayServer/Models/Authorization/AuthorizeViewModel.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Models.Authorization
|
||||
{
|
||||
public class AuthorizeViewModel
|
||||
{
|
||||
[Display(Name = "Application")] public string ApplicationName { get; set; }
|
||||
|
||||
[BindNever] public string RequestId { get; set; }
|
||||
|
||||
[Display(Name = "Scope")] public string Scope { get; set; }
|
||||
}
|
||||
}
|
31
BTCPayServer/Views/Authorization/Authorize.cshtml
Normal file
31
BTCPayServer/Views/Authorization/Authorize.cshtml
Normal file
|
@ -0,0 +1,31 @@
|
|||
@model BTCPayServer.Models.Authorization.AuthorizeViewModel
|
||||
<form method="post">
|
||||
<input type="hidden" name="request_id" value="@Model.RequestId"/>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2 class="section-heading">Authorization Request</h2>
|
||||
<hr class="primary">
|
||||
<p>@Model.ApplicationName is requesting access to your account.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-lg btn-success" name="consent" id="consent-yes" type="submit" value="Yes">Authorize app</button>
|
||||
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<button class="dropdown-item" name="consent" id="consent-yes-temporary" type="submit" value="YesTemporary">Authorize app until session ends</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-secondary" id="consent-no" name="consent" type="submit" value="No">Cancel</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
Loading…
Add table
Reference in a new issue