1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

Move claims issuance and security stamp checks out into profile service. moved context sets out of identity implementations and into get methods.

This commit is contained in:
Kyle Spearrin
2017-01-24 22:15:21 -05:00
parent cb5419aca8
commit 8a83600e52
9 changed files with 153 additions and 65 deletions

View File

@ -16,12 +16,12 @@ namespace Bit.Api.IdentityServer
ClaimTypes.Email,
"securitystamp",
"nam", // name
"eml", // email
"sst", // security stamp
"pln", // plan
"tex", // trial expiration
"dev" // device identifier
"name",
"email",
"sstamp", // security stamp
"plan",
"trial_exp",
"device"
})
};
}

View File

@ -23,6 +23,8 @@ namespace Bit.Api.IdentityServer
ClientId = id;
RequireClientSecret = false;
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword;
UpdateAccessTokenClaimsOnRefresh = true;
AccessTokenLifetime = 60 * 60; // 1 hour
AllowOfflineAccess = true;
AllowedScopes = new string[] { "api" };
}

View File

@ -3,6 +3,13 @@ using System.Threading.Tasks;
using IdentityServer4.Models;
using Bit.Core.Repositories;
using Bit.Core.Services;
using System.Security.Claims;
using Bit.Core.Domains;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using System.Linq;
using Microsoft.Extensions.Options;
using System;
namespace Bit.Api.IdentityServer
{
@ -10,25 +17,73 @@ namespace Bit.Api.IdentityServer
{
private readonly IUserService _userService;
private readonly IUserRepository _userRepository;
private IdentityOptions _identityOptions;
public ProfileService(
IUserRepository userRepository,
IUserService userService)
IUserService userService,
IOptions<IdentityOptions> identityOptionsAccessor)
{
_userRepository = userRepository;
_userService = userService;
_identityOptions = identityOptionsAccessor?.Value ?? new IdentityOptions();
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.AddFilteredClaims(context.Subject.Claims);
return Task.FromResult(0);
var claims = context.Subject.Claims.ToList();
var user = await GetUserAsync(context.Subject);
if(user != null)
{
claims.AddRange(new List<Claim> {
new Claim("plan", "0"), // free plan hard coded for now
new Claim("sstamp", user.SecurityStamp),
new Claim("email", user.Email),
// Deprecated claims for backwards compatability,
new Claim(_identityOptions.ClaimsIdentity.UserNameClaimType, user.Email),
new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp)
});
if(!string.IsNullOrWhiteSpace(user.Name))
{
claims.Add(new Claim("name", user.Name));
}
}
if(claims.Count > 0)
{
context.AddFilteredClaims(claims);
}
}
public Task IsActiveAsync(IsActiveContext context)
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.FromResult(0);
var securityTokenClaim = context.Subject?.Claims.FirstOrDefault(c =>
c.Type == _identityOptions.ClaimsIdentity.SecurityStampClaimType);
var user = await GetUserAsync(context.Subject);
if(user != null && securityTokenClaim != null)
{
context.IsActive = string.Equals(user.SecurityStamp, securityTokenClaim.Value,
StringComparison.InvariantCultureIgnoreCase);
return;
}
else
{
context.IsActive = true;
}
}
private async Task<User> GetUserAsync(ClaimsPrincipal principal)
{
var userId = _userService.GetProperUserId(principal);
if(userId.HasValue)
{
return await _userService.GetUserByIdAsync(userId.Value);
}
return null;
}
}
}

View File

@ -42,11 +42,10 @@ namespace Bit.Api.IdentityServer
{
Init();
var oldAuthBearer = context.Request.Raw["oldAuthBearer"]?.ToString();
var twoFactorCode = context.Request.Raw["twoFactorCode"]?.ToString();
var twoFactorProvider = context.Request.Raw["twoFactorProvider"]?.ToString();
var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorCode) &&
!string.IsNullOrWhiteSpace(twoFactorProvider);
var oldAuthBearer = context.Request.Raw["OldAuthBearer"]?.ToString();
var twoFactorToken = context.Request.Raw["TwoFactorToken"]?.ToString();
var twoFactorProvider = context.Request.Raw["TwoFactorProvider"]?.ToString();
var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) && !string.IsNullOrWhiteSpace(twoFactorProvider);
if(!string.IsNullOrWhiteSpace(oldAuthBearer) && _jwtBearerOptions != null)
{
@ -80,11 +79,11 @@ namespace Bit.Api.IdentityServer
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Two factor required.",
new Dictionary<string, object> {
{ "TwoFactorRequired", true },
{ "TwoFactorProvider", ((int?)user.TwoFactorProvider)?.ToString() } });
{ "TwoFactorProviders", new string[] { ((int?)user.TwoFactorProvider)?.ToString() } } });
return;
}
if(!twoFactorRequest || await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode))
if(!twoFactorRequest || await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorToken))
{
var device = await SaveDeviceAsync(user, context);
BuildSuccessResult(user, context, device);
@ -114,29 +113,18 @@ namespace Bit.Api.IdentityServer
private void BuildSuccessResult(User user, ResourceOwnerPasswordValidationContext context, Device device)
{
var claims = new List<Claim> {
new Claim("pln", "0"), // free plan
new Claim("sst", user.SecurityStamp),
new Claim("eml", user.Email),
// Deprecated claims for backwards compatability
new Claim(ClaimTypes.AuthenticationMethod, "Application"),
new Claim(_identityOptions.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
new Claim(_identityOptions.ClaimsIdentity.UserNameClaimType, user.Email),
new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp)
// Deprecated claims for backwards compatability
new Claim(ClaimTypes.AuthenticationMethod, "Application"),
new Claim(_identityOptions.ClaimsIdentity.UserIdClaimType, user.Id.ToString())
};
if(device != null)
{
claims.Add(new Claim("dev", device.Identifier));
}
if(!string.IsNullOrWhiteSpace(user.Name))
{
claims.Add(new Claim("nam", user.Name));
claims.Add(new Claim("device", device.Identifier));
}
context.Result = new GrantValidationResult(user.Id.ToString(), "Application", identityProvider: "bitwarden",
claims: claims);
claims: claims.Count > 0 ? claims : null);
}
private AuthenticationTicket ValidateOldAuthBearer(string token)

View File

@ -0,0 +1,33 @@
using Bit.Core;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Threading.Tasks;
namespace Bit.Api.Middleware
{
public class CurrentContextMiddleware
{
private readonly RequestDelegate _next;
public CurrentContextMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, CurrentContext currentContext)
{
if(httpContext.User != null)
{
var securityStampClaim = httpContext.User.Claims.FirstOrDefault(c => c.Type == "device");
currentContext.DeviceIdentifier = securityStampClaim?.Value;
}
if(currentContext.DeviceIdentifier == null && httpContext.Request.Headers.ContainsKey("Device-Identifier"))
{
currentContext.DeviceIdentifier = httpContext.Request.Headers["Device-Identifier"];
}
await _next.Invoke(httpContext);
}
}
}

View File

@ -268,6 +268,9 @@ namespace Bit.Api
// Add Jwt authentication to the request pipeline.
app.UseJwtBearerIdentity();
// Add current context
app.UseMiddleware<CurrentContextMiddleware>();
// Add MVC to the request pipeline.
app.UseMvc();
}