Skip to content

PM-18939 refactoring send service to 'cqrs' #5652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

prograhamming
Copy link
Contributor

🎟️ Tracking

(https://bitwarden.atlassian.net/browse/PM-18939)

📔 Objective

Refactor current send codebase to align with our version of CQRS

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@prograhamming prograhamming requested review from a team as code owners April 14, 2025 17:55
Copy link

codecov bot commented Apr 14, 2025

Codecov Report

Attention: Patch coverage is 45.19774% with 194 lines in your changes missing coverage. Please review.

Project coverage is 46.62%. Comparing base (c9a42d8) to head (05b1120).
Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...ols/SendFeatures/Services/SendValidationService.cs 0.00% 106 Missing ⚠️
src/Api/Tools/Controllers/SendsController.cs 18.00% 40 Missing and 1 partial ⚠️
.../SendFeatures/Services/SendAuthorizationService.cs 55.38% 28 Missing and 1 partial ⚠️
...s/SendFeatures/Commands/NonAnonymousSendCommand.cs 87.36% 11 Missing and 1 partial ⚠️
...ols/SendFeatures/Services/SendCoreHelperService.cs 0.00% 3 Missing ⚠️
src/Admin/Tools/Jobs/DeleteSendsJob.cs 0.00% 2 Missing ⚠️
src/Api/Tools/Models/Request/SendRequestModel.cs 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5652      +/-   ##
==========================================
- Coverage   46.69%   46.62%   -0.07%     
==========================================
  Files        1609     1619      +10     
  Lines       73195    73621     +426     
  Branches     6560     6617      +57     
==========================================
+ Hits        34181    34329     +148     
- Misses      37578    37854     +276     
- Partials     1436     1438       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

github-actions bot commented Apr 14, 2025

Logo
Checkmarx One – Scan Summary & Details5a5c5499-027c-4b0a-a8ee-3fff29b38691

New Issues (1)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/AdminConsole/Controllers/GroupsController.cs: 164
detailsMethod Put at line 164 of /src/Api/AdminConsole/Controllers/GroupsController.cs gets a parameter from a user request from model. This parameter val...
ID: yt2PajNvte2IY7Xe%2FRtOPf8qKlo%3D
Attack Vector
Fixed Issues (1)

Great job! The following issues were fixed in this Pull Request

Severity Issue Source File / Package
MEDIUM Use_Of_Hardcoded_Password /src/Core/Identity/Claims.cs: 39

Copy link
Member

@audreyality audreyality left a comment

Choose a reason for hiding this comment

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

⚠️ Public and protected properties that you introduced in this PR should have XMLdoc comments.

👍🏻 The bulk of this code is looking really great! Thank you for partitioning the send service. This code's easier to follow than the service.


namespace Bit.Core.Tools.SendFeatures.Commands.Interfaces;

public interface INonAnonymousSendCommand
Copy link
Member

Choose a reason for hiding this comment

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

⛏️ Try to restate this using affirmative language. Define this as what it is as opposed to what it isn't.

🤔 It looks like there's 4 commands implemented on this interface. I'd expect only 1. Is this me not following along with how we use the pattern?

Copy link
Contributor Author

@prograhamming prograhamming Apr 16, 2025

Choose a reason for hiding this comment

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

CQRS doesn't strictly dictate that each command should only have one method. The core principles of CQRS are:

  • Separate read operations (queries) from write operations (commands)
  • Commands modify state but don't return data
  • Queries return data but don't modify state

A lot of .NET folks use MediatR with the CQRS design pattern and typically you will see one command under one interface/class. However, it is valid to have command classes with multiple methods as long as they're all commands (changing state) and logically belong together.

I separated the commands by how they are used in the controller (Anonymous/non-anonymous). I can change this, but I kept them to together since we don't structure our backend code like MediatR.

Also, In NonAnonymousSendCommand.SaveFileSendAsync() I am cheating by returning a string, but didn't want to change the code too much for the 1st refactor with the send stuff you are working on. It feels like we using Controllers to be mediators or orchestrators and strong opinions about throwing only in the controller.

Comment on lines 10 to 11
Task ValidateUserCanSaveAsync(Guid? userId, Send send);
Task ValidateUserCanSaveAsync_vNext(Guid? userId, Send send);
Copy link
Member

Choose a reason for hiding this comment

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

🤔 Is it necessary to support both the original flavor and vNext implementations here? Would a different pattern, such as creating multiple implementations of the interface, work better?

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 kept this code the way it was because of the feature flag, but could change it to use the strategy pattern for better isolation and testing purposes.

Copy link
Member

Choose a reason for hiding this comment

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

So long as it won't cause other adverse effects, I think I'd like this updated to use the strategy pattern.

throw new BadRequestException("Can only get a download URL for a file type of Send");
}

var (grantAccess, passwordRequired, passwordInvalid) = _sendAuthorizationService.SendCanBeAccessed(send, password);
Copy link
Member

Choose a reason for hiding this comment

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

🌱 This pattern might need to be adjusted since we're moving to an authorization token model. How might this change in a situation where send can't load without a proper claim? (i.e. from a JWT bearer token received in an auth header.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could use IHttpContextAccessor within this service to be able to check the roles of the user. I think this method could have these pieces refactored:

  • Move the 'validation' pieces to the ISendValidationService to check user input (i.e lines 38-40 and IsNullOrWhiteSpace checks)
  • Verification of the password would be moved the GetSendFileDownloadUrlAsync
  • SendCanBeAccessed would just be concerned with the claims.

Copy link
Member

@audreyality audreyality Apr 16, 2025

Choose a reason for hiding this comment

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

We could use IHttpContextAccessor within this service to be able to check the roles of the user.

I'd prefer to use something independent of the execution context (ie. an HTTP request). The controller has access to the HttpContext, for example, and could extract the claim.

Verification of the password would be moved the GetSendFileDownloadUrlAsync

This won't work. The password won't be given to the file download URL. Instead, they'll have a bearer token containing a claim authorizing access to the download.

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