Skip to content

Conversation

@prograhamming
Copy link
Contributor

@prograhamming prograhamming commented Nov 20, 2025

🎟️ Tracking

PM-25576

📔 Objective

Request from architecture to use views in our member access query and we needed to bring the avatar color for the front end.

📸 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 November 20, 2025 04:34
@claude
Copy link
Contributor

claude bot commented Nov 20, 2025

Claude finished @prograhamming's task —— View job


Code Review Complete

I've reviewed the changes in this PR. Here's my analysis:


Summary of Changes Since Last Review

This PR refactors the member access query to use SQL views and adds the AvatarColor field for frontend requirements. The latest commits have addressed the previous Claude review's concern about missing CollectionGroupPermissionsView checks (lines 117-122 in the stored procedure now correctly check both user and group permissions).


Critical Issues

Finding 1: EDD violations for column rename require multi-release process

❌ The EF migrations (MySQL, Postgres, SQLite) use RenameColumn to change UserGuid to OrganizationUserId in the OrganizationMemberBaseDetails table. This violates Evolutionary Database Design (EDD) principles.

Problem: Direct column renames break compatibility with running application instances during deployment.

Required approach:

  1. Release 1: Add new column OrganizationUserId, sync data between columns
  2. Release 2: Update application code to use new column
  3. Release 3: Drop old column UserGuid

Files affected:

  • util/MySqlMigrations/Migrations/20251121151248_UpdateMemberAccessQuery.cs:13
  • util/PostgresMigrations/Migrations/20251121151253_UpdateMemberAccessQuery.cs:13
  • util/SqliteMigrations/Migrations/20251121151244_UpdateMemberAccessQuery.cs:13

Finding 2: Missing corresponding SQL Server migration for column rename

❌ The SQL Server migration script (util/Migrator/DbScripts/2025-11-19_00_UpdateMemberAccessQuery.sql) creates views and updates the stored procedure, but does NOT include a corresponding action for the column rename that exists in the EF migrations.

Problem: SQL Server deployments will be out of sync with MySQL/Postgres/SQLite deployments.

Required: Add SQL to handle the UserGuidOrganizationUserId migration (though this should follow EDD as noted in Finding 1).


Minor Issues

Finding 3: SQL formatting inconsistencies with code style guide

⛏️ All three new view files have INNER JOIN statements that are not vertically aligned with the FROM clause, violating the SQL code style guide.

Files affected:

  • src/Sql/dbo/Views/CollectionCipherDetailsView.sql:11-14
  • src/Sql/dbo/Views/CollectionGroupPermissionsView.sql:15-20
  • src/Sql/dbo/Views/CollectionUserPermissionsView.sql:13-14
  • Same issues in util/Migrator/DbScripts/2025-11-19_00_UpdateMemberAccessQuery.sql:11-14, 31-36, 51-52

Expected format:

FROM
    [dbo].[CollectionCipher] CC
    INNER JOIN [dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
    INNER JOIN [dbo].[Cipher] Ci ON Ci.[Id] = CC.[CipherId]

Finding 4: Potential null reference in MemberAccessReportQuery

💭 At src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs:71, if orgAbility is null, the expression could throw a NullReferenceException when accessing orgAbility.UseResetPassword.

Current code:

AccountRecoveryEnabled = !string.IsNullOrWhiteSpace(g.Key.ResetPasswordKey) && orgAbility?.UseResetPassword == true,

Analysis: The code uses null-conditional operator (orgAbility?), so this should be safe. When orgAbility is null, orgAbility?.UseResetPassword evaluates to null, and null == true evaluates to false. This is actually correct behavior.

Status: This is a non-issue - the code is safe as written.


Good Practices Observed

Comprehensive test coverage: Added extensive unit tests in MemberAccessReportQueryTests.cs with 974 lines covering various scenarios
Proper null handling: Stored procedure validates @OrganizationId IS NULL (line 7-8)
View encapsulation: Good architectural decision to use views for abstracting permission logic
Logic fix confirmed: The stored procedure correctly checks both CollectionUserPermissionsView AND CollectionGroupPermissionsView (lines 111-122), addressing the previous review concern


Action Items for Author

  1. CRITICAL: Refactor the column rename to follow EDD multi-release process (Findings 1 & 2)
  2. REQUIRED: Add corresponding SQL Server migration for the column rename (Finding 2)
  3. MINOR: Fix SQL formatting to align INNER JOIN with FROM clause (Finding 3)

Test Coverage Impact

📊 Codecov shows 68% patch coverage with 16 lines missing coverage. The uncovered lines are primarily in:

  • MemberAccessDetailReportResponseModel.cs (13 lines) - response model constructors
  • RiskInsightsReportQuery.cs (2 lines)
  • MemberAccessReportQuery.cs (1 partial line)

While response models typically don't require extensive test coverage, consider adding basic serialization tests if this is a pattern in your codebase.


Note: I've also added inline comments on the specific files and lines where issues were found. The most critical issue is the EDD violation for the column rename, which must be addressed before merging.


Comment on lines 104 to 109
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Critical Logic Error: This query only checks CollectionUserPermissionsView but doesn't check CollectionGroupPermissionsView.

Problem: Users who have collection access through groups (but not direct user-collection assignments) will incorrectly appear as "users without collection access."

Fix: Add a second NOT EXISTS check:

AND NOT EXISTS (
    SELECT 1
    FROM [dbo].[CollectionUserPermissionsView] CUP
    WHERE CUP.[OrganizationUserId] = OU.[Id]
      AND CUP.[OrganizationId] = @OrganizationId
)
AND NOT EXISTS (
    SELECT 1
    FROM [dbo].[CollectionGroupPermissionsView] CGP
    WHERE CGP.[OrganizationUserId] = OU.[Id]
      AND CGP.[OrganizationId] = @OrganizationId
)

@@ -0,0 +1,109 @@
CREATE PROCEDURE dbo.MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ File name mismatch: The file is named MemberAccessDetail_GetMemberAccessCipherDetailsByOrganizationId.sql but the procedure is named MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId (Detail vs Report).

The file should be renamed to match the procedure name for consistency and easier navigation.

public Guid? UserGuid { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string AvatarColor { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Good addition of AvatarColor field to support frontend requirements.

@@ -0,0 +1,15 @@
CREATE OR ALTER VIEW [dbo].[CollectionCipherDetailsView]
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 Performance consideration: These views don't have explicit indexes defined. For large organizations, consider testing query performance and adding indexed views if needed. However, SQL Server typically leverages indexes from the underlying tables, so this may not be necessary.

@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

❌ Patch coverage is 68.00000% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.95%. Comparing base (c0700a6) to head (7432a62).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
.../Response/MemberAccessDetailReportResponseModel.cs 0.00% 13 Missing ⚠️
.../Reports/ReportFeatures/RiskInsightsReportQuery.cs 0.00% 2 Missing ⚠️
.../Reports/ReportFeatures/MemberAccessReportQuery.cs 93.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6606      +/-   ##
==========================================
+ Coverage   56.83%   56.95%   +0.12%     
==========================================
  Files        1909     1910       +1     
  Lines       84810    84862      +52     
  Branches     7633     7636       +3     
==========================================
+ Hits        48199    48331     +132     
+ Misses      34785    34704      -81     
- Partials     1826     1827       +1     

☔ 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.

Comment on lines 104 to 109
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
);
Copy link
Contributor

Choose a reason for hiding this comment

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

CRITICAL: This logic error from the previous review is still not fixed. The query only checks CollectionUserPermissionsView but doesn't check CollectionGroupPermissionsView.

Problem: Users who have collection access exclusively through groups (but not direct user-collection assignments) will incorrectly appear as "users without collection access."

Fix:

Suggested change
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
);
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroupPermissionsView] CGP
WHERE CGP.[OrganizationUserId] = OU.[Id]
AND CGP.[OrganizationId] = @OrganizationId
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same thing here. Claude keeps add this suggestion, but the change has been made.

@@ -0,0 +1,109 @@
CREATE PROCEDURE dbo.MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ File naming mismatch: The file is named MemberAccessDetail_GetMemberAccessCipherDetailsByOrganizationId.sql but the procedure is named MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId (Detail vs Report).

The file should be renamed to MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId.sql for consistency.

public Guid? UserGuid { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string AvatarColor { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Good addition of AvatarColor field to support frontend requirements.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image

Email = g.Key.Email,
AvatarColor = g.Key.AvatarColor,
TwoFactorEnabled = orgUsersTwoFactorEnabled.FirstOrDefault(x => x.userId == g.Key.UserGuid).twoFactorIsEnabled,
AccountRecoveryEnabled = !string.IsNullOrWhiteSpace(g.Key.ResetPasswordKey) && orgAbility.UseResetPassword,
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Potential null reference: If orgAbility is null, this will throw a NullReferenceException.

Consider using null-safe navigation:

Suggested change
AccountRecoveryEnabled = !string.IsNullOrWhiteSpace(g.Key.ResetPasswordKey) && orgAbility.UseResetPassword,
AccountRecoveryEnabled = !string.IsNullOrWhiteSpace(g.Key.ResetPasswordKey) && orgAbility?.UseResetPassword == true,

Comment on lines +159 to +164
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same critical logic error: This migration script has the same issue as the stored procedure file - it only checks CollectionUserPermissionsView but not CollectionGroupPermissionsView.

Fix:

Suggested change
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroupPermissionsView] CGP
WHERE CGP.[OrganizationUserId] = OU.[Id]
AND CGP.[OrganizationId] = @OrganizationId
)

Copy link
Contributor Author

@prograhamming prograhamming Nov 20, 2025

Choose a reason for hiding this comment

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

same comment as the other suggestion.

@prograhamming prograhamming changed the title PM-25576 Member Access Query - Requested Changes PM-25576 Member Access Query - Requested Changes to Use Views in Member Access Query and Avatar Color for Front End Nov 20, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 20, 2025

Logo
Checkmarx One – Scan Summary & Details4dfdefea-e05a-4619-8f42-b0349a940e6c

Great job! No new security vulnerabilities introduced in this pull request

Copy link
Contributor

@mkincaid-bw mkincaid-bw left a comment

Choose a reason for hiding this comment

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

Some minor formatting nitpicks, and some critical findings regarding EDD.

Ci.[DeletedDate]
FROM
[dbo].[CollectionCipher] CC
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ INNER JOIN statement should be vertically aligned with FROM clause
https://contributing.bitwarden.com/contributing/code-style/sql/#select-statements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the settings on my IDE and fixed this formatting issue.

[dbo].[CollectionCipher] CC
INNER JOIN
[dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ INNER JOIN statement should be vertically aligned with FROM clause
https://contributing.bitwarden.com/contributing/code-style/sql/#select-statements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the settings on my IDE and fixed this formatting issue.

CG.[Manage]
FROM
[dbo].[GroupUser] GU
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ INNER JOIN statement should be vertically aligned with FROM clause
https://contributing.bitwarden.com/contributing/code-style/sql/#select-statements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the settings on my IDE and fixed this formatting issue.

[dbo].[GroupUser] GU
INNER JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ INNER JOIN statement should be vertically aligned with FROM clause
https://contributing.bitwarden.com/contributing/code-style/sql/#select-statements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the settings on my IDE and fixed this formatting issue.

[dbo].[Group] G ON G.[Id] = GU.[GroupId]
INNER JOIN
[dbo].[CollectionGroup] CG ON CG.[GroupId] = G.[Id]
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ INNER JOIN statement should be vertically aligned with FROM clause
https://contributing.bitwarden.com/contributing/code-style/sql/#select-statements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the settings on my IDE and fixed this formatting issue.

CU.[Manage]
FROM
[dbo].[CollectionUser] CU
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ INNER JOIN statement should be vertically aligned with FROM clause
https://contributing.bitwarden.com/contributing/code-style/sql/#select-statements

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the settings on my IDE and fixed this formatting issue.

/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
Copy link
Contributor

Choose a reason for hiding this comment

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

Renaming a database column does not follow EDD. In order to achieve this, a new column must be added, the data must be synced between the old and new columns, then the old column is dropped. This is a multi-release process. See this example for more details.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is no migration action that corresponds with the EF migrations. We can't do a rename anyway but you'll need a corresponding SQL migration in this file to match the EF migrations.

migrationBuilder.RenameColumn(
name: "UserGuid",
table: "OrganizationMemberBaseDetails",
newName: "OrganizationUserId");
Copy link
Contributor

Choose a reason for hiding this comment

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

CRITICAL - EDD Violation: Direct column rename violates Evolutionary Database Design principles.

Problem: This breaks compatibility with running application instances during deployment.

Required multi-release process:

  1. Release 1: Add new column OrganizationUserId, sync data from UserGuid
  2. Release 2: Update application code to read/write new column
  3. Release 3: Drop old column UserGuid

See the EDD example for detailed guidance.

Ci.[DeletedDate]
FROM
[dbo].[CollectionCipher] CC
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ Formatting: INNER JOIN statements should be vertically aligned with FROM clause per SQL code style guide.

Suggested change
INNER JOIN
FROM
[dbo].[CollectionCipher] CC
INNER JOIN [dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
INNER JOIN [dbo].[Cipher] Ci ON Ci.[Id] = CC.[CipherId]

CG.[Manage]
FROM
[dbo].[GroupUser] GU
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ Formatting: INNER JOIN statements should be vertically aligned with FROM clause per SQL code style guide.

Suggested change
INNER JOIN
FROM
[dbo].[GroupUser] GU
INNER JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId]
INNER JOIN [dbo].[CollectionGroup] CG ON CG.[GroupId] = G.[Id]
INNER JOIN [dbo].[Collection] C ON C.[Id] = CG.[CollectionId]

CU.[Manage]
FROM
[dbo].[CollectionUser] CU
INNER JOIN
Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ Formatting: INNER JOIN statements should be vertically aligned with FROM clause per SQL code style guide.

Suggested change
INNER JOIN
FROM
[dbo].[CollectionUser] CU
INNER JOIN [dbo].[Collection] C ON C.[Id] = CU.[CollectionId]

Comment on lines +111 to +122
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionUserPermissionsView] CUP
WHERE CUP.[OrganizationUserId] = OU.[Id]
AND CUP.[OrganizationId] = @OrganizationId
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroupPermissionsView] CGP
WHERE CGP.[OrganizationUserId] = OU.[Id]
AND CGP.[OrganizationId] = @OrganizationId
)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Logic Fix Confirmed: This correctly checks both CollectionUserPermissionsView AND CollectionGroupPermissionsView to identify users without collection access. This addresses the concern raised in the previous Claude review.

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.

3 participants