Skip to content

SF-3940 Fix inconsistent permissions when applying chapter draft - #4236

Merged
RaymondLuong3 merged 2 commits into
masterfrom
fix/SF-3940-chapter-permission
Nov 6, 2026
Merged

RaymondLuong3 merged 2 commits into
masterfrom
fix/SF-3940-chapter-permission

Conversation

@RaymondLuong3

@RaymondLuong3 RaymondLuong3 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

If a user had applied a draft to a chapter and synchronizes with Paratext, those chapters will appear as not editable since the permission in Paratext is not granted for new books or chapters. We bypass this to allow the user to add a new books or chapter chapters to their project.

This PR improves this behaviour so that users who add chapters to existing books (when the user does not have book edit permission) will successfully apply those chapters. Previously, when a user applied a draft chapters to an existing book the whole book would be marked editable.


This change is Reviewable

@RaymondLuong3 RaymondLuong3 added the will require testing PR should not be merged until testers confirm testing is complete label Oct 29, 2026
@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.33333% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.31%. Comparing base (c429a36) to head (3bb2f00).
⚠️ Report is 1 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...SIL.XForge.Scripture/Services/MachineApiService.cs 84.61% 2 Missing and 4 partials ⚠️
...c/SIL.XForge.Scripture/Services/ParatextService.cs 94.44% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4236      +/-   ##
==========================================
+ Coverage   81.30%   81.31%   +0.01%     
==========================================
  Files         671      671              
  Lines       43020    43083      +63     
  Branches     7138     7142       +4     
==========================================
+ Hits        34976    35035      +59     
- Misses       6877     6879       +2     
- Partials     1167     1169       +2     

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

@Nateowami Nateowami left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Nateowami made 1 comment.
Reviewable status: 0 of 5 files reviewed, 1 unresolved discussion (waiting on RaymondLuong3).


src/SIL.XForge.Scripture/Services/ParatextService.cs line 1876 at r1 (raw file):

                    bool isExistingBook =
                        addedChapters is not null && addedChapters.TryGetValue(bookNum, out addedChaptersInBook);
                    if (!hasPermissionInMongo && isExistingBook && !hasPermissionInParatext)

Have not verified, but Devin claims this gives permission even to non-admins: https://app.devin.ai/review/sillsdev/web-xforge/pull/4236#security-SEC_pr-review-job-cc5723243c1b424baf8a59a02016d195_0001

@pmachapman pmachapman self-assigned this Nov 3, 2026
@pmachapman
pmachapman self-requested a review November 3, 2026 20:16

@pmachapman pmachapman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@pmachapman reviewed 5 files and all commit messages, and made 3 comments.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on RaymondLuong3).


src/SIL.XForge.Scripture/Services/ParatextService.cs line 1876 at r1 (raw file):

                    bool isExistingBook =
                        addedChapters is not null && addedChapters.TryGetValue(bookNum, out addedChaptersInBook);
                    if (!hasPermissionInMongo && isExistingBook && !hasPermissionInParatext)

Should we check for currentUserIsAdministrator here? I could not get a non-admin user to get write permissions, as they are blocked earlier in the add draft workflow, but perhaps if this funciton gets used by other code in the future, it might not make that check before calling this function?

Code quote:

 if (!hasPermissionInMongo && isExistingBook && !hasPermissionInParatext)

src/SIL.XForge.Scripture/Services/ParatextService.cs line 1889 at r1 (raw file):

                                        p => p.Texts[textIndex].Chapters[chapterIndex].Permissions[user.SFUserId],
                                        TextInfoPermission.Write
                                    );

These variables captured into the lambda are modified in their outer scopes, which can sometimes cause their values to be unexpected due to how the C# compiler builds lambdas at runtime (it passes the variables ByRef, not ByVal). It should be something along the lines of:

int i = textIndex;
int j = chapterIndex;
op.Set(
	p => p.Texts[i].Chapters[j].Permissions[user.SFUserId],
	TextInfoPermission.Write
);

Code quote:

                                    op.Set(
                                        p => p.Texts[textIndex].Chapters[chapterIndex].Permissions[user.SFUserId],
                                        TextInfoPermission.Write
                                    );

src/SIL.XForge.Scripture/Services/ParatextService.cs line 1912 at r1 (raw file):

                            for (int chapterIndex = 0; chapterIndex < text.Chapters.Count; chapterIndex++)
                            {
                                int chapterNumber = text.Chapters[chapterIndex].Number;

Same bug here - the two variable declarations that were removed were necessary to stop the outer scope modifying the variables in the lambda.

Code quote:

                        ];
                        await projectDoc.SubmitJson0OpAsync(op =>
                        {
                            op.Set(p => p.Texts[textIndex].Permissions[user.SFUserId], TextInfoPermission.Write);
                            for (int chapterIndex = 0; chapterIndex < text.Chapters.Count; chapterIndex++)
                            {
                                int chapterNumber = text.Chapters[chapterIndex].Number;

@RaymondLuong3
RaymondLuong3 force-pushed the fix/SF-3940-chapter-permission branch from 64086ce to c21e6e4 Compare November 4, 2026 00:01

@RaymondLuong3 RaymondLuong3 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@RaymondLuong3 made 4 comments.
Reviewable status: 4 of 5 files reviewed, 4 unresolved discussions (waiting on Nateowami and pmachapman).


src/SIL.XForge.Scripture/Services/ParatextService.cs line 1876 at r1 (raw file):

Previously, Nateowami wrote…

Have not verified, but Devin claims this gives permission even to non-admins: https://app.devin.ai/review/sillsdev/web-xforge/pull/4236#security-SEC_pr-review-job-cc5723243c1b424baf8a59a02016d195_0001

Thanks for pointing that out. I'll add the additional guard to ensure that this path is only available to admin users.


src/SIL.XForge.Scripture/Services/ParatextService.cs line 1876 at r1 (raw file):

Previously, pmachapman (Peter Chapman) wrote…

Should we check for currentUserIsAdministrator here? I could not get a non-admin user to get write permissions, as they are blocked earlier in the add draft workflow, but perhaps if this funciton gets used by other code in the future, it might not make that check before calling this function?

Thanks for looking into this. I have added the additional guard.


src/SIL.XForge.Scripture/Services/ParatextService.cs line 1889 at r1 (raw file):

Previously, pmachapman (Peter Chapman) wrote…

These variables captured into the lambda are modified in their outer scopes, which can sometimes cause their values to be unexpected due to how the C# compiler builds lambdas at runtime (it passes the variables ByRef, not ByVal). It should be something along the lines of:

int i = textIndex;
int j = chapterIndex;
op.Set(
	p => p.Texts[i].Chapters[j].Permissions[user.SFUserId],
	TextInfoPermission.Write
);

Thanks for catching that. I am surprised that the lambdas are constructed by ref rather than by value. I'm glad I know that now!


src/SIL.XForge.Scripture/Services/ParatextService.cs line 1912 at r1 (raw file):

Previously, pmachapman (Peter Chapman) wrote…

Same bug here - the two variable declarations that were removed were necessary to stop the outer scope modifying the variables in the lambda.

Done.

@RaymondLuong3 RaymondLuong3 added the ai-review-author-sign-off PR author has looked at AI review of change and determined no findings warrant further changes label Nov 4, 2026
@pmachapman
pmachapman requested a review from Nateowami November 4, 2026 00:33

@pmachapman pmachapman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

:lgtm:

@pmachapman reviewed 1 file and all commit messages, made 1 comment, and resolved 3 discussions.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on Nateowami).

@pmachapman pmachapman added ready to test and removed will require testing PR should not be merged until testers confirm testing is complete labels Nov 4, 2026
@RaymondLuong3
RaymondLuong3 force-pushed the fix/SF-3940-chapter-permission branch from c21e6e4 to 3bb2f00 Compare November 6, 2026 22:57
@RaymondLuong3
RaymondLuong3 deployed to screenshot_diff November 6, 2026 23:05 — with GitHub Actions Active
@RaymondLuong3 RaymondLuong3 added testing complete Testing of PR is complete and should no longer hold up merging of the PR and removed ready to test labels Nov 6, 2026
@RaymondLuong3
RaymondLuong3 merged commit 1544d2b into master Nov 6, 2026
35 of 36 checks passed
@RaymondLuong3
RaymondLuong3 deleted the fix/SF-3940-chapter-permission branch November 6, 2026 23:10

This branch was successfully deployed

1 active deployment
screenshot_diff — 3bb2f000 Deployed Nov 6, 2026 by RaymondLuong3 via Compare Screenshots #1300
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-author-sign-off PR author has looked at AI review of change and determined no findings warrant further changes testing complete Testing of PR is complete and should no longer hold up merging of the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants