Skip to content

[EC-787] Create a method in PolicyService to check if a policy applies to a user#2537

Merged
r-tome merged 33 commits intomasterfrom
EC-787-create-a-method-in-policy-service-to-check-if-a-policy-applies-to-a-user
May 12, 2023
Merged

[EC-787] Create a method in PolicyService to check if a policy applies to a user#2537
r-tome merged 33 commits intomasterfrom
EC-787-create-a-method-in-policy-service-to-check-if-a-policy-applies-to-a-user

Conversation

@r-tome
Copy link
Contributor

@r-tome r-tome commented Jan 4, 2023

Type of change

- [ ] Bug fix
- [ ] New feature development
- [X] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other

Objective

Whilst working on EC-758: Add environment var to enforce SSO for all users it was identified that the logic to check if a policy applies to a user is duplicated in:

  • Stored procedure [dbo].[PolicyApplicableToUser] which is used by methods PolicyRepository.GetManyByTypeApplicableToUserIdAsync and PolicyRepository.GetCountByTypeApplicableToUserIdAsync

  • BaseRequestValidator has the same logic to check for the SSO policy

The goal is to lift the business logic from the stored procedure [dbo].[PolicyApplicableToUser] into a method (PolicyService.GetPoliciesApplicableToUser(OrganizationUser orgUser, PolicyType policyType, OrganizationUserType minUserType, OrganizationUserStatusType minStatus)) which will retrieve the user’s organization policies from the database and then filter the results in memory according to our business rules.

Code changes

  • src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserPolicyDetails.cs: Model output for the new Stored Procedure [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
  • src/Core/Repositories/IOrganizationUserRepository.cs: Added method GetByUserIdWithPolicyDetailsAsync(Guid userId) to call Stored Procedure [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
  • src/Core/Services/IPolicyService.cs: Added method GetPoliciesApplicableToUserAsync that will call IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync and apply business rules to filter results
  • src/Core/Services/Implementations/CipherService.cs* Switched from using IPolicyRepository. GetCountByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync
  • src/Core/Services/Implementations/OrganizationService.cs: Switched from using IPolicyRepository. GetCountByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync
  • src/Core/Services/Implementations/PolicyService.cs: Added method GetPoliciesApplicableToUserAsync that will call IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync and apply business rules to filter results
  • src/Core/Services/Implementations/SendService.cs: Switched from using IPolicyRepository. GetCountByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync
  • src/Core/Services/Implementations/UserService.cs: Switched from using IPolicyRepository. GetCountByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync
  • src/Identity/IdentityServer/BaseRequestValidator.cs: Deleted own implementation to use IPolicyService.GetPoliciesApplicableToUserAsync
  • src/Identity/IdentityServer/CustomTokenRequestValidator.cs: Added base dependency IPolicyService
  • src/Identity/IdentityServer/ResourceOwnerPasswordValidator.cs: Added base dependency IPolicyService
  • src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs: Added method GetByUserIdWithPolicyDetailsAsync(Guid userId) to call Stored Procedure [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
  • src/Infrastructure.EntityFramework/Repositories/OrganizationUserRepository.cs: EF query implementation of Stored Procedure [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
  • src/Sql/Sql.sqlproj: Added new stored procedure file reference for [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
  • src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByUserIdWithPolicyDetails.sql: Added new stored procedure for [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
  • test/Core.Test/Services/PolicyServiceTests.cs: Unit testing the business rules being applied
  • test/Core.Test/Services/SendServiceTests.cs: Updated existing unit tests to use IPolicyService
  • test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/OrganizationUserPolicyDetailsCompare.cs: Created comparer to be used on EF integration tests
  • test/Infrastructure.EFIntegration.Test/Repositories/OrganizationUserRepositoryTests.cs: Testing the multiple implementations of the new query
  • test/Infrastructure.EFIntegration.Test/Repositories/PolicyRepositoryTests.cs: Fixed issue that was preventing the test from working
  • util/Migrator/DbScripts/2022-12-26_00_OrganizationUserReadByUserIdWithPolicyDetails.sql: Script to add new stored procedure [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]

Before you submit

  • Please check for formatting errors (dotnet format --verify-no-changes) (required)
  • If making database changes - make sure you also update Entity Framework queries and/or migrations
  • Please add unit tests where it makes sense to do so (encouraged but not required)
  • If this change requires a documentation update - notify the documentation team
  • If this change has particular deployment requirements - notify the DevOps team

r-tome and others added 14 commits December 29, 2022 16:03
…ableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync
…ge of IPolicyService.GetPoliciesApplicableToUserAsync
…erIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete
@r-tome r-tome requested review from a team and eliykat January 4, 2023 15:05
@r-tome r-tome marked this pull request as ready for review January 4, 2023 15:06
Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it! Thank you for cleaning up my past sins (when I wrote that SQL query 😁 )

Comment on lines +24 to +33
LEFT JOIN
(SELECT
PU.UserId,
PO.OrganizationId
FROM
[dbo].[ProviderUserView] PU
INNER JOIN
[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]) PUPO
ON PUPO.UserId = OU.UserId
AND PUPO.OrganizationId = P.OrganizationId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this nested query/join but I don't know if we can avoid it. Any ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the query, let me know what you think.

@eliykat
Copy link
Member

eliykat commented Jan 25, 2023

Also please resolve conflicts (my fault, I'll make sure to re-review promptly)

@r-tome r-tome requested a review from a team as a code owner March 10, 2023 15:28
@r-tome r-tome requested a review from eliykat March 10, 2023 17:17
}

[Theory, BitAutoData]
public async Task AnyPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultUserTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsFalse(Guid userId, SutProvider<PolicyService> sutProvider)
Copy link
Member

@eliykat eliykat Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion only: these test names are too focused on inputs and outputs and do not describe the actual behaviour being tested. What's the general idea here? "returns false if policy does not apply to user"? "RequireSSO policy does not apply to Owners"? I know we have to jam it into a method name so it won't look exactly like that (I prefer Jest for this reason 😁 ) but you get the idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is non-blocking, a suggestion only.

Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, and good job picking it back up again - I know it's hard to load it all back into memory 😁 a few final changes/questions, then let's be done with it!

var organizationUserPolicyDetails = await _organizationUserRepository.GetByUserIdWithPolicyDetailsAsync(userId);
var excludedUserTypes = GetUserTypesExcludedFromPolicy(policyType);
return organizationUserPolicyDetails.Where(o =>
o.PolicyType == policyType &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice this before (sorry!) but we don't need to fetch every policy type from the database just to filter them here.

The other checks here are fine because they're business logic that is more complex and likely to change in the future. But just getting the requested policy type seems like it should be done at the sproc level to minimize the amount of data being fetched from the db.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, good point. I've added a parameter for PolicyType in the sproc.

Comment on lines +20 to +29
LEFT JOIN
(SELECT
PU.UserId,
PO.OrganizationId
FROM
[dbo].[ProviderUserView] PU
INNER JOIN
[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]) PUPO
ON PUPO.UserId = OU.UserId
AND PUPO.OrganizationId = P.OrganizationId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any benefit to this nested SELECT query, or can we just move the JOINS out into the main query?

o.PolicyEnabled &&
!excludedUserTypes.Contains(o.OrganizationUserType) &&
o.OrganizationUserStatus >= minStatus &&
!o.IsProvider);
Copy link
Member

@eliykat eliykat Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please confirm with Product that a ProviderUser for an organization is never subject to that organization's policies (e.g. not even RequireSSO or password complexity). They apply to owners so we may want to extend it there. Or Providers might be responsible for managing their own security.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment Providers are not subject to any policies - if this is going to change, we can do it in a separate PR just so we can merge this in the meantime. It might require client changes as well.

@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 1 Code Smell

No Coverage information No Coverage information
0.0% 0.0% Duplication

@r-tome r-tome requested a review from eliykat March 30, 2023 09:54
…o-check-if-a-policy-applies-to-a-user

# Conflicts:
#	src/Core/Services/IPolicyService.cs
#	src/Core/Services/Implementations/PolicyService.cs
#	src/Identity/IdentityServer/BaseRequestValidator.cs
#	src/Identity/IdentityServer/CustomTokenRequestValidator.cs
#	src/Identity/IdentityServer/ResourceOwnerPasswordValidator.cs
#	test/Core.Test/Services/PolicyServiceTests.cs
#	test/Core.Test/Tools/Services/SendServiceTests.cs
@r-tome r-tome requested review from a team as code owners April 18, 2023 15:42
r-tome and others added 2 commits April 19, 2023 08:47
…erIdWithPolicyDetails.sql

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
eliykat
eliykat previously approved these changes Apr 19, 2023
…o-check-if-a-policy-applies-to-a-user

# Conflicts:
#	src/Sql/Sql.sqlproj
eliykat
eliykat previously approved these changes May 4, 2023
@r-tome r-tome removed the needs-qa label May 10, 2023
…o-check-if-a-policy-applies-to-a-user

# Conflicts:
#	src/Identity/IdentityServer/BaseRequestValidator.cs
@r-tome r-tome merged commit 8d3fe12 into master May 12, 2023
@r-tome r-tome deleted the EC-787-create-a-method-in-policy-service-to-check-if-a-policy-applies-to-a-user branch May 12, 2023 07:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants