feat: implement grading permissions and authorization hooks#2988
feat: implement grading permissions and authorization hooks#2988bra-i-am wants to merge 6 commits intoopenedx:masterfrom
Conversation
|
Thanks for the pull request, @bra-i-am! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2988 +/- ##
========================================
Coverage 95.46% 95.46%
========================================
Files 1378 1380 +2
Lines 32596 32632 +36
Branches 7479 7277 -202
========================================
+ Hits 31117 31152 +35
- Misses 1410 1424 +14
+ Partials 69 56 -13 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
f36766a to
72cb564
Compare
cbce6d4 to
ad49a12
Compare
|
Cool! I'll try to review soon. I'm a bit behind in my reviews at the moment though, sorry. If @dcoa can confirm everything is tested and working, then I can do a faster review just by reading the code. |
|
Thank you @bra-i-am, I will review it today. |
There was a problem hiding this comment.
It works!!
I leave a couple of suggestions (no blockers) for enhance the hook’s readability.
Also the no having permission over the grading is displaying an alert what is good, but we can consider removing the option for the dropdown menu in that specific case. @bradenmacdonald, what do you think?
| * const { canViewGradingSettings, canEditGradingSettings } = permissions; | ||
| * ``` | ||
| */ | ||
| export const useUserPermissionsWithAuthzCourse = ( |
There was a problem hiding this comment.
I think the hook name is quite verbose maybe useValidateCourseAccess or useCourseUserPermissions.
For the description maybe we can change it a little bit to be more clear:
/**
* Custom hook to retrieve and evaluate user permissions for the current course using the openedx-authz service.
*
* The hook:
* 1. Validate if authz is enabled via waffle flag
* 2. Fetch user permissions when authz is enabled
* 3. Fallback all permissions to 'true' when authz is disabled
* 4. Provide fallback values for undefined permissions
*/Maybe to avoid the else if we can do
const resolvePermission = (key: string): boolean => {
if (!isAuthzEnabled) {
return true;
}
return userPermissions?.[key] ?? false;
};
const permissionResults: PermissionValidationAnswer = isLoadingUserPermissions
? {}
: Object.keys(permissions).reduce<PermissionValidationAnswer>((acc, key) => {
acc[key] = resolvePermission(key);
return acc;
}, {});
Yeah, I think it's better to remove it than to show it but display an error when the user clicks on it. |
bradenmacdonald
left a comment
There was a problem hiding this comment.
Looking good, just a couple suggestions:
| export const useUserPermissionsWithAuthzCourse = ( | ||
| courseId: string, | ||
| permissions: PermissionValidationQuery, | ||
| ): UseUserPermissionsWithAuthzCourseReturn => { |
There was a problem hiding this comment.
Right now this just returns a basic UseUserPermissionsWithAuthzCourseReturn type, which doesn't specify what permissions it contains. Please fix the types so that it returns the actual requested permissions:
--- a/src/authz/hooks.ts
+++ b/src/authz/hooks.ts
@@ -1,10 +1,10 @@
import { useWaffleFlags } from '@src/data/apiHooks';
import { useUserPermissions } from '@src/authz/data/apiHooks';
-import { PermissionValidationQuery, PermissionValidationAnswer } from '@src/authz/types';
+import { PermissionValidationQuery } from '@src/authz/types';
-interface UseUserPermissionsWithAuthzCourseReturn {
+interface UseUserPermissionsWithAuthzCourseReturn<Query extends PermissionValidationQuery> {
isLoading: boolean;
- permissions: PermissionValidationAnswer;
+ permissions: { [K in keyof Query]: boolean };
isAuthzEnabled: boolean;
}
@@ -40,10 +40,10 @@ interface UseUserPermissionsWithAuthzCourseReturn {
* const { canViewGradingSettings, canEditGradingSettings } = permissions;
* ```
*/
-export const useUserPermissionsWithAuthzCourse = (
+export const useUserPermissionsWithAuthzCourse = <Query extends PermissionValidationQuery>(
courseId: string,
- permissions: PermissionValidationQuery,
-): UseUserPermissionsWithAuthzCourseReturn => {
+ permissions: Query,
+): UseUserPermissionsWithAuthzCourseReturn<Query> => {
const waffleFlags = useWaffleFlags(courseId);
const isAuthzEnabled: boolean = waffleFlags?.enableAuthzCourseAuthoring ?? false;
@@ -52,7 +52,7 @@ export const useUserPermissionsWithAuthzCourse = (
data: userPermissions,
} = useUserPermissions(permissions, isAuthzEnabled);
- const permissionResults: PermissionValidationAnswer = {};
+ const permissionResults: Record<string, boolean> = {};
if (isAuthzEnabled && !isLoadingUserPermissions) {
Object.keys(permissions).forEach((permissionKey: string) => {
@@ -66,7 +66,7 @@ export const useUserPermissionsWithAuthzCourse = (
return {
isLoading: isAuthzEnabled ? isLoadingUserPermissions : false,
- permissions: permissionResults,
+ permissions: permissionResults as { [K in keyof Query]: boolean },
isAuthzEnabled,
};
};| * } | ||
| * ); | ||
| * | ||
| * const { canViewGradingSettings, canEditGradingSettings } = permissions; |
There was a problem hiding this comment.
Nit/optional:
It would be more useful it this returned the permissions right away:
Instead of:
const { isLoading, permissions, isAuthzEnabled } = useUserPermissionsWithAuthzCourse(...);
const { canViewGradingSettings, canEditGradingSettings } = permissions;I would rather write:
const { isLoading, canViewGradingSettings, canEditGradingSettings, isAuthzEnabled } = useUserPermissionsWithAuthzCourse(...);Especially in the future when we don't have isAuthzEnabled any more, this way will be more convenient.
c859e5d to
3327288
Compare
3327288 to
e330471
Compare
Description
This PR implements AuthZ permission checks for the Settings → Grading page in Studio, enabling role-based UI control based on the
courses.view_grading_settingsandcourses.edit_grading_settingspermissions fromopenedx-authz.What changed
New shared authz utilities
src/authz/hooks.ts— NewuseUserPermissionsWithAuthzCourse(courseId, permissions)hook that abstracts the common pattern of checking theenableAuthzCourseAuthoringwaffle flag, fetching permissions when enabled, and defaulting all permissions totruewhen disabled.src/authz/permissionHelpers.ts— NewgetGradingPermissions(courseId)andgetFilesPermissions(courseId)helpers that return structured permission query objects.src/authz/constants.ts— AddedVIEW_GRADING_SETTINGSandEDIT_GRADING_SETTINGStoCOURSE_PERMISSIONS.Grading Settings page (
GradingSettings.jsx):courses.view_grading_settings→ renders<PermissionDeniedAlert />(full page block).courses.edit_grading_settings→ all edit controls are disabled (read-only view).isEditableprop propagated to all child components:GradingScale,GradingScaleSegment,GradingScaleHandle,AssignmentSection,AssignmentItem,AssignmentTypeName,DeadlineSection,CreditSection.Supporting information
openedx_authz.constants.permissions.COURSES_VIEW_GRADING_SETTINGS/COURSES_EDIT_GRADING_SETTINGSauthz.enable_course_authoring(CourseWaffleFlag, defaultfalse)Testing instructions
Prerequisites:
authz.enable_course_authoringset to Everyone.Test view permission denied:
Test view-only (read-only UI):
course_auditorrole to a user for the course viaPUT /api/authz/v1/roles/users/withscope=course-v1:Org%2BCourse%2BRun.Test full edit access:
course_editorrole to a user.Other information
GET /api/contentstore/v1/course_settings/*). That PR must be merged before this frontend change.useUserPermissionsWithAuthzCoursehook introduced here should be reused for future authz integrations across other Settings pages, following the pattern established in PR Adding permission validations from authz for files page #2941.Best Practices Checklist
We're trying to move away from some deprecated patterns in this codebase. Please
check if your PR meets these recommendations before asking for a review:
.ts,.tsx).propTypesanddefaultPropsin any new or modified code — existing JSX child components were modified and retain their existing patterns. Full migration to TypeScript is out of scope for this PR.src/testUtils.tsx(specificallyinitializeMocks) — to be added in a follow-up.apiHooks.tsin this repo for examples.messages.tsfiles have adescriptionfor translators to use.../in import paths. To import from parent folders, use@src.