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

Move idenityserver implementations into API project

This commit is contained in:
Kyle Spearrin
2017-01-18 18:34:14 -05:00
parent 1e16644f52
commit 3348b07ce2
9 changed files with 13 additions and 10 deletions

View File

@ -0,0 +1,22 @@
using IdentityServer4.Models;
using System.Collections.Generic;
using System.Security.Claims;
namespace Bit.Api.IdentityServer
{
public class ApiResources
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api", "Vault API", new string[] {
ClaimTypes.AuthenticationMethod,
ClaimTypes.NameIdentifier,
ClaimTypes.Email,
"securitystamp"
})
};
}
}
}

View File

@ -0,0 +1,31 @@
using IdentityServer4.Models;
using System.Collections.Generic;
namespace Bit.Api.IdentityServer
{
public class Clients
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new ApiClient("mobile"),
new ApiClient("web"),
new ApiClient("browser"),
new ApiClient("desktop")
};
}
public class ApiClient : Client
{
public ApiClient(string id)
{
ClientId = id;
RequireClientSecret = false;
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword;
AllowOfflineAccess = true;
AllowedScopes = new string[] { "api" };
}
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Domains;
using Bit.Core.Repositories;
using IdentityServer4.Models;
using IdentityServer4.Stores;
namespace Bit.Api.IdentityServer
{
public class PersistedGrantStore : IPersistedGrantStore
{
private readonly IGrantRepository _grantRepository;
public PersistedGrantStore(
IGrantRepository grantRepository)
{
_grantRepository = grantRepository;
}
public async Task<IEnumerable<PersistedGrant>> GetAllAsync(string subjectId)
{
var grants = await _grantRepository.GetManyAsync(subjectId);
var pGrants = grants.Select(g => ToPersistedGrant(g));
return pGrants;
}
public async Task<PersistedGrant> GetAsync(string key)
{
var grant = await _grantRepository.GetByKeyAsync(key);
if(grant == null)
{
return null;
}
var pGrant = ToPersistedGrant(grant);
return pGrant;
}
public async Task RemoveAllAsync(string subjectId, string clientId)
{
await _grantRepository.DeleteAsync(subjectId, clientId);
}
public async Task RemoveAllAsync(string subjectId, string clientId, string type)
{
await _grantRepository.DeleteAsync(subjectId, clientId, type);
}
public async Task RemoveAsync(string key)
{
await _grantRepository.DeleteAsync(key);
}
public async Task StoreAsync(PersistedGrant pGrant)
{
var grant = ToGrant(pGrant);
await _grantRepository.SaveAsync(grant);
}
private Grant ToGrant(PersistedGrant pGrant)
{
return new Grant
{
Key = pGrant.Key,
Type = pGrant.Type,
SubjectId = pGrant.SubjectId,
ClientId = pGrant.ClientId,
CreationDate = pGrant.CreationTime,
ExpirationDate = pGrant.Expiration,
Data = pGrant.Data
};
}
private PersistedGrant ToPersistedGrant(Grant grant)
{
return new PersistedGrant
{
Key = grant.Key,
Type = grant.Type,
SubjectId = grant.SubjectId,
ClientId = grant.ClientId,
CreationTime = grant.CreationDate,
Expiration = grant.ExpirationDate,
Data = grant.Data
};
}
}
}

View File

@ -0,0 +1,34 @@
using IdentityServer4.Services;
using System.Threading.Tasks;
using IdentityServer4.Models;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Api.IdentityServer
{
public class ProfileService : IProfileService
{
private readonly IUserService _userService;
private readonly IUserRepository _userRepository;
public ProfileService(
IUserRepository userRepository,
IUserService userService)
{
_userRepository = userRepository;
_userService = userService;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.AddFilteredClaims(context.Subject.Claims);
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.FromResult(0);
}
}
}

View File

@ -0,0 +1,118 @@
using Bit.Core.Domains;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using IdentityServer4.Models;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Bit.Api.IdentityServer
{
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly UserManager<User> _userManager;
private readonly IdentityOptions _identityOptions;
private readonly IDeviceRepository _deviceRepository;
public ResourceOwnerPasswordValidator(
UserManager<User> userManager,
IOptions<IdentityOptions> optionsAccessor,
IDeviceRepository deviceRepository)
{
_userManager = userManager;
_identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
_deviceRepository = deviceRepository;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var twoFactorCode = context.Request.Raw["twoFactorCode"]?.ToString();
var twoFactorProvider = context.Request.Raw["twoFactorProvider"]?.ToString();
var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorCode) &&
!string.IsNullOrWhiteSpace(twoFactorProvider);
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
if(user != null)
{
if(await _userManager.CheckPasswordAsync(user, context.Password))
{
if(!twoFactorRequest && await TwoFactorRequiredAsync(user))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Two factor code required.",
// TODO: return something better?
new System.Collections.Generic.Dictionary<string, object> { { "TwoFactorRequired", true } });
return;
}
if(!twoFactorRequest || await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorProvider, twoFactorCode))
{
await SaveDeviceAsync(user, context);
context.Result = new GrantValidationResult(user.Id.ToString(), "Application", identityProvider: "bitwarden",
claims: new Claim[] {
// 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.ToString()),
new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp)
});
return;
}
}
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
twoFactorRequest ? "Code is not correct. Try again." : "Username or password is incorrect. Try again.",
new System.Collections.Generic.Dictionary<string, object> { { "Error", true } });
}
private async Task<bool> TwoFactorRequiredAsync(User user)
{
return _userManager.SupportsUserTwoFactor &&
await _userManager.GetTwoFactorEnabledAsync(user) &&
(await _userManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
}
private Device GetDeviceFromRequest(ResourceOwnerPasswordValidationContext context)
{
var deviceIdentifier = context.Request.Raw["deviceIdentifier"]?.ToString();
var deviceType = context.Request.Raw["deviceType"]?.ToString();
var deviceName = context.Request.Raw["deviceName"]?.ToString();
var devicePushToken = context.Request.Raw["devicePushToken"]?.ToString();
DeviceType type;
if(string.IsNullOrWhiteSpace(deviceIdentifier) || string.IsNullOrWhiteSpace(deviceType) ||
string.IsNullOrWhiteSpace(deviceName) || !Enum.TryParse(deviceType, out type))
{
return null;
}
return new Device
{
Identifier = deviceIdentifier,
Name = deviceName,
Type = type,
PushToken = devicePushToken
};
}
private async Task SaveDeviceAsync(User user, ResourceOwnerPasswordValidationContext context)
{
var device = GetDeviceFromRequest(context);
if(device != null)
{
var existingDevice = await _deviceRepository.GetByIdentifierAsync(device.Identifier, user.Id);
if(existingDevice == null)
{
device.UserId = user.Id;
await _deviceRepository.CreateAsync(device);
}
}
}
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Linq;
namespace Bit.Api.IdentityServer
{
public static class TokenRetrieval
{
public static Func<HttpRequest, string> FromAuthorizationHeaderOrQueryString(string headerScheme = "Bearer",
string qsName = "account_token")
{
return (request) =>
{
string authorization = request.Headers["Authorization"].FirstOrDefault();
if(string.IsNullOrWhiteSpace(authorization))
{
return request.Query[qsName].FirstOrDefault();
}
if(authorization.StartsWith(headerScheme + " ", StringComparison.OrdinalIgnoreCase))
{
return authorization.Substring(headerScheme.Length + 1).Trim();
}
return null;
};
}
}
}

View File

@ -29,6 +29,7 @@ using IdentityServer4.Stores;
using Bit.Core.Utilities;
using Serilog;
using Serilog.Events;
using Bit.Api.IdentityServer;
namespace Bit.Api
{

View File

@ -22,7 +22,9 @@
"Microsoft.ApplicationInsights.AspNetCore": "1.0.2",
"AspNetCoreRateLimit": "1.0.5",
"Serilog.Extensions.Logging": "1.3.1",
"Serilog.Sinks.AzureDocumentDb": "3.5.17"
"Serilog.Sinks.AzureDocumentDb": "3.5.17",
"IdentityServer4": "1.0.1",
"IdentityServer4.AccessTokenValidation": "1.0.2"
},
"tools": {