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

[Require SSO] Enterprise policy enforcement (#970)

* Initial commit of require sso authentication policy enforcement

* Updated sproc to send UseSso flag // Updated base validator to send back error message // Added changes to EntityFramework (just so its there for the future

* Update policy name // adjusted conditional to demorgan's

* Updated sproc // Added migrator script

* Added .sql file extension to DeleteOrgUserWithOrg migrator script

* Added policy // edit // strings // validation to business portal

* Change requests from review // Added Owner & Admin exemption

* Updated repository function used to get org user's type

* Updated with requested changes
This commit is contained in:
Vincent Salucci
2020-10-26 11:56:16 -05:00
committed by GitHub
parent e872b4df9d
commit 66e44759f0
15 changed files with 195 additions and 11 deletions

View File

@ -6,5 +6,6 @@
MasterPassword = 1,
PasswordGenerator = 2,
OnlyOrg = 3,
RequireSso = 4,
}
}

View File

@ -35,6 +35,7 @@ namespace Bit.Core.IdentityServer
private readonly ILogger<ResourceOwnerPasswordValidator> _logger;
private readonly CurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
private readonly IPolicyRepository _policyRepository;
public BaseRequestValidator(
UserManager<User> userManager,
@ -49,7 +50,8 @@ namespace Bit.Core.IdentityServer
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
CurrentContext currentContext,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
{
_userManager = userManager;
_deviceRepository = deviceRepository;
@ -64,6 +66,7 @@ namespace Bit.Core.IdentityServer
_logger = logger;
_currentContext = currentContext;
_globalSettings = globalSettings;
_policyRepository = policyRepository;
}
protected async Task ValidateAsync(T context, ValidatedTokenRequest request)
@ -112,9 +115,18 @@ namespace Bit.Core.IdentityServer
twoFactorToken = null;
}
var device = await SaveDeviceAsync(user, request);
await BuildSuccessResultAsync(user, context, device, twoFactorRequest && twoFactorRemember);
return;
if (await IsValidAuthTypeAsync(user, request.GrantType)) // Returns true if can finish validation process
{
var device = await SaveDeviceAsync(user, request);
await BuildSuccessResultAsync(user, context, device, twoFactorRequest && twoFactorRemember);
}
else
{
SetSsoResult(context, new Dictionary<string, object>
{{
"ErrorModel", new ErrorResponseModel("SSO authentication is required.")
}});
}
}
protected abstract Task<(User, bool)> ValidateContextAsync(T context);
@ -229,6 +241,8 @@ namespace Bit.Core.IdentityServer
}
protected abstract void SetTwoFactorResult(T context, Dictionary<string, object> customResponse);
protected abstract void SetSsoResult(T context, Dictionary<string, object> customResponse);
protected abstract void SetSuccessResult(T context, User user, List<Claim> claims,
Dictionary<string, object> customResponse);
@ -259,11 +273,63 @@ namespace Bit.Core.IdentityServer
return new Tuple<bool, Organization>(individualRequired || firstEnabledOrg != null, firstEnabledOrg);
}
private async Task<bool> IsValidAuthTypeAsync(User user, string grantType)
{
if (grantType == "authorization_code")
{
return true; // Already using SSO to authorize, finish successfully
}
// Is user apart of any orgs? Use cache for initial checks.
var orgs = (await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id))
.ToList();
if (orgs.Any())
{
// Get all org abilities
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
// Parse all user orgs that are enabled and have the ability to use sso
var ssoOrgs = orgs.Where(o => OrgCanUseSso(orgAbilities, o.Id));
if (ssoOrgs.Any())
{
// Parse users orgs and determine if require sso policy is enabled
var userOrgs = await _organizationRepository.GetManyByUserIdAsync(user.Id);
foreach (var org in userOrgs)
{
if (!(org.Enabled && org.UseSso))
{
continue;
}
var orgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.RequireSso);
if (orgPolicy != null && orgPolicy.Enabled)
{
var userType = await _organizationUserRepository.GetByOrganizationAsync(org.Id, user.Id);
// Owners and Admins are exempt from this policy
if (userType != null
&& userType.Type != OrganizationUserType.Owner
&& userType.Type != OrganizationUserType.Admin)
{
return false;
}
}
}
}
}
return true; // Default - continue validation process
}
private bool OrgUsing2fa(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
{
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
orgAbilities[orgId].Enabled && orgAbilities[orgId].Using2fa;
}
private bool OrgCanUseSso(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
{
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
orgAbilities[orgId].Enabled && orgAbilities[orgId].UseSso;
}
private Device GetDeviceFromRequest(ValidatedRequest request)
{

View File

@ -31,10 +31,11 @@ namespace Bit.Core.IdentityServer
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
CurrentContext currentContext,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
: base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings)
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository)
{
_userManager = userManager;
}
@ -78,6 +79,9 @@ namespace Bit.Core.IdentityServer
context.Result.CustomResponse = customResponse;
}
protected override void SetSsoResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse) => throw new System.NotImplementedException();
protected override void SetErrorResult(CustomTokenRequestValidationContext context,
Dictionary<string, object> customResponse)
{

View File

@ -31,10 +31,11 @@ namespace Bit.Core.IdentityServer
IMailService mailService,
ILogger<ResourceOwnerPasswordValidator> logger,
CurrentContext currentContext,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IPolicyRepository policyRepository)
: base(userManager, deviceRepository, deviceService, userService, eventService,
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
applicationCacheService, mailService, logger, currentContext, globalSettings)
applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository)
{
_userManager = userManager;
_userService = userService;
@ -77,6 +78,13 @@ namespace Bit.Core.IdentityServer
customResponse);
}
protected override void SetSsoResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Sso authentication required.",
customResponse);
}
protected override void SetErrorResult(ResourceOwnerPasswordValidationContext context,
Dictionary<string, object> customResponse)
{

View File

@ -16,6 +16,7 @@ namespace Bit.Core.Models.Data
organization.TwoFactorProviders != "{}";
UsersGetPremium = organization.UsersGetPremium;
Enabled = organization.Enabled;
UseSso = organization.UseSso;
}
public Guid Id { get; set; }
@ -24,5 +25,6 @@ namespace Bit.Core.Models.Data
public bool Using2fa { get; set; }
public bool UsersGetPremium { get; set; }
public bool Enabled { get; set; }
public bool UseSso { get; set; }
}
}

View File

@ -99,6 +99,7 @@ namespace Bit.Core.Repositories.EntityFramework
UseEvents = e.UseEvents,
UsersGetPremium = e.UsersGetPremium,
Using2fa = e.Use2fa && e.TwoFactorProviders != null,
UseSso = e.UseSso,
}).ToListAsync();
}
}

View File

@ -548,4 +548,19 @@
<data name="OnlyOrganizationPolicyWarning" xml:space="preserve">
<value>Organization members who are already a part of another organization will be removed from this organization and will receive an email notifying them about the change.</value>
</data>
<data name="RequireSso" xml:space="preserve">
<value>Single Sign-On Authentication</value>
</data>
<data name="RequireSsoDescription" xml:space="preserve">
<value>Require users to log in with the Enterprise Single Sign-On method.</value>
</data>
<data name="Prerequisite" xml:space="preserve">
<value>Prerequisite</value>
</data>
<data name="RequireSsoPolicyReq" xml:space="preserve">
<value>The Single Organization enterprise policy must be enabled before activating this policy.</value>
</data>
<data name="RequireSsoPolicyReqError" xml:space="preserve">
<value>Single Organization policy not enabled.</value>
</data>
</root>