Skip to content

Fix RoleService: remove hardcoded return values for isController() and isAdmin()#5274

Open
GauravD2t wants to merge 7 commits intoDSpace:mainfrom
GauravD2t:RoleService-hard-coded-return-isController-isAdmin
Open

Fix RoleService: remove hardcoded return values for isController() and isAdmin()#5274
GauravD2t wants to merge 7 commits intoDSpace:mainfrom
GauravD2t:RoleService-hard-coded-return-isController-isAdmin

Conversation

@GauravD2t
Copy link
Copy Markdown
Contributor

@GauravD2t GauravD2t commented Mar 11, 2026

References

(#3331)

Description

Previously:

isController() always returned true.

isAdmin() always returned false.

This caused incorrect UI behavior where users could see controller-related options even when they did not have the appropriate permissions.

This PR replaces those hardcoded values with proper authorization checks using AuthorizationService and FeatureID.

Changes

Updated isController() to check whether the authenticated user has either:

IsCollectionAdmin

IsCommunityAdmin

Updated isAdmin() to check whether the user has the AdministratorOf authorization.

Added distinctUntilChanged() to prevent unnecessary observable emissions.

Instructions for Reviewers

Verify that isController() returns true only when the user is a Collection Admin or Community Admin.

Verify that isAdmin() correctly detects repository administrators.

Confirm that MyDSpace UI options are displayed correctly based on user permissions.
List of changes in this PR:

  • First, ...
  • Second, ...

Include guidance for how to test or review your PR. This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.

Checklist

This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome).
However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!

  • My PR is created against the main branch of code (unless it is a backport or is fixing an issue specific to an older branch).
  • My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
  • My PR passes ESLint validation using npm run lint
  • My PR doesn't introduce circular dependencies (verified via npm run check-circ-deps)
  • My PR includes TypeDoc comments for all new (or modified) public methods and classes. It also includes TypeDoc for large or complex private methods.
  • My PR passes all specs/tests and includes new/updated specs or tests based on the Code Testing Guide.
  • My PR aligns with Accessibility guidelines if it makes changes to the user interface.
  • My PR uses i18n (internationalization) keys instead of hardcoded English text, to allow for translations.
  • My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
  • If my PR includes new libraries/dependencies (in package.json), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.
  • If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
  • If my PR fixes an issue ticket, I've linked them together.

@lgeggleston lgeggleston added bug component: MyDSpace 1 APPROVAL pull request only requires a single approval to merge labels Mar 11, 2026
@lgeggleston lgeggleston moved this to 🙋 Needs Reviewers Assigned in DSpace 10.0 Release Mar 11, 2026
Copy link
Copy Markdown
Contributor

@nwoodward nwoodward left a comment

Choose a reason for hiding this comment

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

Hi @GauravD2t. Thank you for this PR. I found a problem when testing it locally, and I think it needs to be fixed for the default new user scenario.

When I registered as a new user and clicked on MyDSpace the page flashed continuously because it was in an infinite loop. I saw the following log on the backend repeating over and over until I stopped the frontend:

2026-03-11 16:31:11,092 INFO f491abc1-73bf-4b05-8ac0-8fb43eb3d2d7 b5427d96-a378-4ac0-aeed-40c3bf28056c org.dspace.app.rest.utils.DSpaceAPIRequestLoggingFilter @ Before request [GET /server/api] originated from /home

Then I logged in as an admin in another browser and tested 1) adding the new user to the Administrator group and 2) making the new user a Community or a Collection admin. In every case, this stopped the infinite loop, and the MyDSpace page loaded successfully.

So the problem is with a new user who isn't a controller or an admin. In that scenario there is some code in the PR that continuously pings the backend.

@github-project-automation github-project-automation bot moved this from 🙋 Needs Reviewers Assigned to 👀 Under Review in DSpace 10.0 Release Mar 11, 2026
@GauravD2t
Copy link
Copy Markdown
Contributor Author

Thank you again for testing and identifying this issue.

After investigating the behavior for newly registered users, I found that when a user does not have any roles (not a submitter, controller, or admin), the available configuration list could be empty. This caused the MyDSpace page to repeatedly re-evaluate the configuration, which resulted in the continuous requests to /server/api.

I updated the logic to ensure that the Workspace configuration is available as a fallback when the user has no roles assigned. This prevents the configuration list from being empty and resolves the infinite loop for the default new user scenario.

Please let me know if you notice any further issues while testing.

@nwoodward
Copy link
Copy Markdown
Contributor

Hi @GauravD2t. Thank you for the most recent change. The MyDSpace page now loads successfully for a new user without any roles. I think the logic can be simplified even further, since the first if clause and the last if clause in public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> produce the same result.

if (isSubmitter) {
  availableConf.push(MyDSpaceConfigurationValueType.Workspace);
}
if (isController || isAdmin) {
  availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems);
  availableConf.push(MyDSpaceConfigurationValueType.Workflow);
}
if (!isSubmitter && !isController && !isAdmin) {
  availableConf.push(MyDSpaceConfigurationValueType.Workspace);
}

If fact, the last if clause could be an else, since it only occurs when the other conditions are false. So it seems to me that all users regardless of their roles should get MyDSpaceConfigurationValueType.Workspace and then only controllers or admins should get the additional permissions. Something like this:

public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> {
  return combineLatest(this.isSubmitter$, this.isController$, this.isAdmin$).pipe(
    first(),
    map(([isSubmitter, isController, isAdmin]: [boolean, boolean, boolean]) => {
      const availableConf: MyDSpaceConfigurationValueType[] = [];
      availableConf.push(MyDSpaceConfigurationValueType.Workspace);
      if (isController || isAdmin) {
        availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems);
        availableConf.push(MyDSpaceConfigurationValueType.Workflow);
      }
      return availableConf;
    }));
}

What do you think?

@GauravD2t
Copy link
Copy Markdown
Contributor Author

Hi @GauravD2t. Thank you for the most recent change. The MyDSpace page now loads successfully for a new user without any roles. I think the logic can be simplified even further, since the first if clause and the last if clause in public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> produce the same result.

if (isSubmitter) {
  availableConf.push(MyDSpaceConfigurationValueType.Workspace);
}
if (isController || isAdmin) {
  availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems);
  availableConf.push(MyDSpaceConfigurationValueType.Workflow);
}
if (!isSubmitter && !isController && !isAdmin) {
  availableConf.push(MyDSpaceConfigurationValueType.Workspace);
}

If fact, the last if clause could be an else, since it only occurs when the other conditions are false. So it seems to me that all users regardless of their roles should get MyDSpaceConfigurationValueType.Workspace and then only controllers or admins should get the additional permissions. Something like this:

public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> {
  return combineLatest(this.isSubmitter$, this.isController$, this.isAdmin$).pipe(
    first(),
    map(([isSubmitter, isController, isAdmin]: [boolean, boolean, boolean]) => {
      const availableConf: MyDSpaceConfigurationValueType[] = [];
      availableConf.push(MyDSpaceConfigurationValueType.Workspace);
      if (isController || isAdmin) {
        availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems);
        availableConf.push(MyDSpaceConfigurationValueType.Workflow);
      }
      return availableConf;
    }));
}

What do you think?

Hi @GauravD2t. Thank you for the most recent change. The MyDSpace page now loads successfully for a new user without any roles. I think the logic can be simplified even further, since the first if clause and the last if clause in public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> produce the same result.

if (isSubmitter) {
  availableConf.push(MyDSpaceConfigurationValueType.Workspace);
}
if (isController || isAdmin) {
  availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems);
  availableConf.push(MyDSpaceConfigurationValueType.Workflow);
}
if (!isSubmitter && !isController && !isAdmin) {
  availableConf.push(MyDSpaceConfigurationValueType.Workspace);
}

If fact, the last if clause could be an else, since it only occurs when the other conditions are false. So it seems to me that all users regardless of their roles should get MyDSpaceConfigurationValueType.Workspace and then only controllers or admins should get the additional permissions. Something like this:

public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> {
  return combineLatest(this.isSubmitter$, this.isController$, this.isAdmin$).pipe(
    first(),
    map(([isSubmitter, isController, isAdmin]: [boolean, boolean, boolean]) => {
      const availableConf: MyDSpaceConfigurationValueType[] = [];
      availableConf.push(MyDSpaceConfigurationValueType.Workspace);
      if (isController || isAdmin) {
        availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems);
        availableConf.push(MyDSpaceConfigurationValueType.Workflow);
      }
      return availableConf;
    }));
}

What do you think?

Hi @nwoodward , thank you for the suggestion.

One concern I have is about users who have controller rights but do not have submit rights. In that case, if MyDSpaceConfigurationValueType.Workspace is always added by default, the user might see the Workspace section even though they cannot submit new items.

My understanding is that controller permissions allow users to manage or review submissions (e.g., Workflow or Supervised Items), but not necessarily create new submissions. Because of that, I initially handled the Workspace configuration based on the submitter role.

Please let me know your thoughts on this.

@GauravD2t GauravD2t requested a review from nwoodward March 26, 2026 04:16
@nwoodward
Copy link
Copy Markdown
Contributor

nwoodward commented Mar 26, 2026

Hi @GauravD2t. Thank you for your response, and I apologize for not getting back to you sooner. I tested this again today, and I think the issue I have is what is meant by "controller". I don't know how to define the term or where it is defined in the code.

In #3331 (comment) you said "isController() checks whether the authenticated user has either the Collection Admin or Community Admin authorization". And that is how the function works in this PR:

  /**
   * Check if current user is a controller
   */
  isController(): Observable<boolean> {
    return combineLatest([
      this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
      this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
    ]).pipe(
      map(([isCollectionAdmin, isCommunityAdmin]) => isCollectionAdmin || isCommunityAdmin),
      distinctUntilChanged(),
    );
  }

So far, so good.

Then in your most recent comment above you said "My understanding is that controller permissions allow users to manage or review submissions (e.g., Workflow or Supervised Items), but not necessarily create new submissions." That sounds correct to me. Users who have a role such as Reviewer or Editor or Final Editor need to have access to workflow tasks.

So with this PR deployed I created a new collection with roles for Administrators, Reviewers and Submitters. Then I created a new administrator user, a new reviewer user, and a new submitter user. I submitted a new item to the collection and then logged in on a different browser as the reviewer user. But when the reviewer user goes to MyDSpace they don't have access to Workflow tasks, presumably because they aren't a community or collection admin.

Currently, they do have access to "Workflow tasks" and "Supervised items" in the dropdown because isController() always returns true. I think your PR is potentially moving in the right direction, but isController() needs to account for users with non-admin roles such as Reviewer or Editor.

And the really hard part is I can see how this could hypothetically get more complicated if a repository has defined custom workflow steps in the backend configuration in config/spring/api/workflow.xml, such as in the screenshot. Users in any of these Groups would need access to Workflow tasks in MyDSpace. I'm not sure how to account for that situation on the frontend.

Screenshot 2026-03-26 at 4 47 22 PM

@GauravD2t
Copy link
Copy Markdown
Contributor Author

Hi @nwoodward, thank you for the detailed feedback and for catching that Reviewers were being excluded! You're absolutely right—isController() was too narrow.

I am updating the logic to include a check for workflow participation (using FeatureID.CanManageWorkflows or similar) so that Reviewers and Editors regain access to their tasks. This should also handle the custom workflow steps you mentioned, as the backend authorization service will evaluate those roles automatically. I'll push an update shortly!"

Would you like me to double-check the exact FeatureID names in the DSpace source code for you to make sure we use the right one?

@nwoodward
Copy link
Copy Markdown
Contributor

@GauravD2t yes, that sounds good!

@GauravD2t
Copy link
Copy Markdown
Contributor Author

@GauravD2t yes, that sounds good!

@nwoodward ,Does this approach work for me? If so, I can proceed with creating the backend PR first, then update this frontend PR to use the new FeatureID once it's available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1 APPROVAL pull request only requires a single approval to merge bug component: MyDSpace

Projects

Status: 👀 Under Review

Development

Successfully merging this pull request may close these issues.

missing implementation in RoleService: hard-coded return values in isController / isAdmin

3 participants