Skip to content
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

Add switch orga to legacy routes #8257

Merged
merged 17 commits into from
Dec 11, 2024

Conversation

MichaelBuessemeyer
Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer commented Dec 3, 2024

Follow up bug introduced by #8075. In #8075 we missed to support the switch orga functionality for legacy routes. This pr add this functionally now.

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • I suggest testing locally as it might be easier
  • In one browser window (e.g. default window) open your local wk with sample user
  • turn on isWkOrgInstance in application.conf
  • in another browser window with a new session ( e.g. private window) create a new orga
  • in that orga upload some dataset
  • after successful upload open the edit view of the ds
  • open the advanced tab in the data settings. On top of this, the orgaId/directoryPath combination should be listed. e.g.: Dataset Configuration (as stored on datastore localhost at 15ca057a580b27dc/png_rgb). Copy the orgaId/directoryPath part.
  • switch back to the window where the sample (!super user!) is logged in. Now open <wk>/datasets/<orgaId/directoryPath>/view. The page should automatically redirect to <wk>/datasets/<name-datasetId>/view and then fail. The user should be prompted to switch the organization. Upon switching the DS should load

Issues:


(Please delete unneeded items, merge only when none are left open)

Copy link
Contributor

coderabbitai bot commented Dec 3, 2024

Warning

Rate limit exceeded

@MichaelBuessemeyer has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 08c0de7 and ae3f9be.

📒 Files selected for processing (1)
  • app/security/AccessibleBySwitchingService.scala (1 hunks)
📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces significant modifications across several files, primarily focusing on the DatasetDAO, DatasetController, and AuthenticationController. Key changes include the refactoring of dataset retrieval methods to enhance backward compatibility by using only directoryName for dataset identification. The AsyncRedirect component has been transitioned from a class-based to a functional component utilizing React hooks. Additionally, the AuthenticationController has been restructured to centralize access control logic within a new AccessibleBySwitchingService, streamlining user access management.

Changes

File Path Change Summary
app/models/dataset/Dataset.scala Updated findOneByNameAndOrganization to query only by directoryName. Refactored findOneByDataSourceId to call findOneByDirectoryNameAndOrganization.
frontend/javascripts/components/redirect.tsx Converted AsyncRedirect from class-based to functional component using hooks. Simplified redirect logic.
app/controllers/DatasetController.scala Enhanced error handling and validation across multiple methods, including dataset accessibility and metadata checks.
app/controllers/AuthenticationController.scala Refactored to use authenticationService for organization switching and removed old access checking methods.
app/security/AccessibleBySwitchingService.scala Introduced a new service for managing organizational access switching based on user roles.

Assessment against linked issues

Objective Addressed Explanation
Legacy link resolution should only use directoryName not the name (#8237)

Possibly related PRs

Suggested reviewers

  • fm3
  • philippotto

Poem

🐰 In the meadow where datasets play,
With directoryName leading the way,
Old links now find their rightful place,
As rabbits hop with joyful grace.
Functional hooks and services new,
Make managing data a breeze to pursue! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Experiment)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@MichaelBuessemeyer
Copy link
Contributor Author

@daniel-wer Please ignore the request that I assigned you for review. I intended to ask @philippotto but first misclicked and the was unable to reassign you in the view where one can prepare a PR :/ So sorry for the ping

In case you prefer to take over the frontend review part fo this PR, feel free to do so :)

@MichaelBuessemeyer MichaelBuessemeyer removed the request for review from daniel-wer December 3, 2024 12:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
frontend/javascripts/router.tsx (1)

161-164: Consider parameterizing the toast identifier

Currently, the toast identifier "404" is hardcoded in Toast.close("404"). Consider defining this identifier as a constant or ensuring it aligns with toast IDs used elsewhere to maintain consistency and ease future updates.

app/models/dataset/Dataset.scala (1)

437-445: Clarify method purpose or consider renaming

The method findOneByNameAndOrganization now queries by directoryName instead of name to maintain backward compatibility with legacy links:

WHERE (directoryName = $name)

Since the method name implies that it searches by name, consider renaming it to accurately reflect its functionality, such as findOneByDirectoryNameForLegacySupport, or update its documentation to clearly indicate that it matches on directoryName.

frontend/javascripts/components/redirect.tsx (1)

43-45: Consider handling errors without rethrowing

In the performRedirect function, when an error occurs, the code sets the hasError state to true and rethrows the error:

} catch (e) {
  setHasError(true);
  throw e;
}

Rethrowing the error after updating the state may prevent the errorComponent from rendering if an error boundary catches the error. Consider removing throw e; to allow the component to render the errorComponent without interruption, ensuring a smoother user experience.

frontend/javascripts/admin/admin_rest_api.ts (1)

Line range hint 1781-1803: Add JSDoc comments to document the new parameter type.

The implementation correctly handles the new case for switching organizations based on dataset directory name and organization ID. However, the function would benefit from JSDoc comments explaining the different parameter types and their use cases.

+/**
+ * Checks if a dataset or annotation is accessible by switching to another organization.
+ * @param commandType - One of:
+ *   - TraceOrViewCommand with annotationId for tracing mode
+ *   - TraceOrViewCommand with datasetId for viewing mode
+ *   - Object with directoryName and organizationId for legacy route support
+ * @returns The organization to switch to if the resource is accessible, null/undefined otherwise
+ */
 export async function isDatasetAccessibleBySwitching(
   commandType: TraceOrViewCommand | { directoryName: string; organizationId: string; type: "VIEW" },
 ): Promise<APIOrganization | null | undefined> {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fb1f275 and 5a4b16d.

📒 Files selected for processing (6)
  • app/controllers/AuthenticationController.scala (3 hunks)
  • app/models/dataset/Dataset.scala (1 hunks)
  • conf/webknossos.latest.routes (1 hunks)
  • frontend/javascripts/admin/admin_rest_api.ts (2 hunks)
  • frontend/javascripts/components/redirect.tsx (1 hunks)
  • frontend/javascripts/router.tsx (5 hunks)
🔇 Additional comments (4)
frontend/javascripts/router.tsx (3)

5-5: Imports added correctly

The necessary imports of isDatasetAccessibleBySwitching, useSelector, useFetch, BrainSpinnerWithError, CoverWithLogin, and Toast are appropriately added to support the new functionality in the component.

Also applies to: 52-52, 77-79


139-169: Well-implemented LegacyLinkDisambiguateErrorView component

The LegacyLinkDisambiguateErrorView component is correctly implemented as a React Functional Component. It effectively uses useSelector to access the activeUser from the Redux store and useFetch to determine if the dataset is accessible by switching organizations. Conditional rendering based on the presence of user is appropriately handled.


240-257: Proper use of errorComponent in AsyncRedirect

In tracingViewModeLegacy, the errorComponent prop is correctly utilized in AsyncRedirect to render LegacyLinkDisambiguateErrorView when an error occurs during redirection. This enhances error handling by providing a user-friendly interface.

conf/webknossos.latest.routes (1)

30-30: Route definition updated appropriately

The route for /auth/accessibleBySwitching is updated to include the datasetDirectoryName and organizationId parameters, matching the updated method signature in AuthenticationController:

GET /auth/accessibleBySwitching controllers.AuthenticationController.accessibleBySwitching(datasetId: Option[String], datasetDirectoryName: Option[String], organizationId: Option[String], annotationId: Option[String], workflowHash: Option[String])

Comment on lines 270 to 280
case (Some(datasetId), None, None, None, None) =>
for {
dataset <- datasetDAO.findOne(datasetId)
organization <- organizationDAO.findOne(dataset._organization)
} yield organization
case (None, Some(annotationId), None) =>
case (None, Some(datasetDirectoryName), Some(organizationId), None, None) =>
for {
dataset <- datasetDAO.findOneByDirectoryNameAndOrganization(datasetDirectoryName, organizationId)
organization <- organizationDAO.findOne(dataset._organization)
} yield organization
case (None, Some(annotationId), None, None, None) =>
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 3, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical issue: Incorrect parameter mapping in pattern matching

In the pattern matching expressions of accessibleBySwitchingForSuperUser and canAccessDatasetOrAnnotationOrWorkflow, annotationIdOpt is incorrectly assigned to datasetDirectoryNameOpt. For example:

case (None, Some(annotationId), None, None, None) => // ...

Here, Some(annotationId) is mapped to datasetDirectoryNameOpt, which is incorrect. This misalignment can lead to unintended behavior and logical errors.

Apply this diff to correct the parameter mapping:

- case (None, Some(annotationId), None, None, None) =>
+ case (None, None, None, Some(annotationId), None) =>

Ensure that annotationIdOpt is correctly positioned in the pattern matching to accurately reflect the function parameters. Repeat this correction in both accessibleBySwitchingForSuperUser and canAccessDatasetOrAnnotationOrWorkflow methods.

Also applies to: 323-327

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh good catch. That's what I get for using ai auto completion

Copy link
Contributor

Choose a reason for hiding this comment

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

@MichaelBuessemeyer, I'm glad I could help catch that issue! Let me know if there's anything else I can assist with.


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

datasetDirectoryName,
organizationId,
annotationId,
workflowHash)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Re-evaluate the organization comparison logic

The check:

_ <- bool2Fox(selectedOrganization._id != request.identity._organization) // User is already in correct orga, but still could not see dataset. Assume this had a reason.

Prevents switching organizations if the selected organization is the same as the current one. However, there may be valid scenarios where a user lacks access to a dataset within their current organization due to permission restrictions. Consider revising this logic to allow for permission checks even when the organization is the same, or provide a more specific error message to guide the user.

@MichaelBuessemeyer
Copy link
Contributor Author

@fm3 The backend changes only include changes to support the change orga mechanism via directoryName & orgaId and not only by datasetId. This is needed as for legacy links the datasetId is unknown and also cannot be resolved via the disambiguation route.

Moreover, I sneaked in the changes for #8237

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
app/controllers/AuthenticationController.scala (3)

244-247: Consider combining the parameter validation checks.

The validation logic for datasetDirectoryName and organizationId parameters could be more concise.

Consider this alternative implementation:

- _ <- Fox
-   .runIf(datasetDirectoryName.isDefined)(bool2Fox(organizationId.isDefined)) ?~> "Organization id must be defined if dataset directory name is defined."
- _ <- Fox
-   .runIf(organizationId.isDefined)(bool2Fox(datasetDirectoryName.isDefined)) ?~> "Directory name must be defined if dataset organization id is defined."
+ _ <- bool2Fox(datasetDirectoryName.isDefined == organizationId.isDefined) ?~> "Dataset directory name and organization id must be either both defined or both undefined."

273-284: Improve error message for invalid parameter combinations.

The error message "Can either test access for dataset or annotation or workflow, not a combination" could be more specific about which parameters were provided and what combinations are valid.

Consider enhancing the error message:

- case _ => Fox.failure("Can either test access for dataset or annotation or workflow, not a combination")
+ case _ => Fox.failure("Invalid parameter combination. Valid combinations are: (datasetId), (datasetDirectoryName, organizationId), (annotationId), or (workflowHash)")

344-349: Enhance error handling and debugging capabilities.

Consider adding:

  1. Validation for the organization ID format
  2. Debug logging to help troubleshoot access issues

Consider this enhanced implementation:

 private def canAccessDatasetByDirectoryNameAndOrganization(ctx: DBAccessContext,
                                                          datasetDirectoryName: String,
                                                          organizationId: String): Fox[Boolean] = {
+  logger.debug(s"Checking access to dataset $datasetDirectoryName in organization $organizationId")
+  for {
+    _ <- ObjectId.fromString(organizationId).toFox
+    foundFox = datasetDAO.findOneByDirectoryNameAndOrganization(datasetDirectoryName, organizationId)(ctx)
+    result <- foundFox.futureBox.map(_.isDefined)
+    _ = logger.debug(s"Access result for dataset $datasetDirectoryName: $result")
+  } yield result
-  val foundFox = datasetDAO.findOneByDirectoryNameAndOrganization(datasetDirectoryName, organizationId)(ctx)
-  foundFox.futureBox.map(_.isDefined)
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5a4b16d and e6db2c3.

📒 Files selected for processing (1)
  • app/controllers/AuthenticationController.scala (3 hunks)
🔇 Additional comments (1)
app/controllers/AuthenticationController.scala (1)

261-261: Re-evaluate the organization comparison logic.

The check prevents switching organizations if the user is already in the correct organization, which might prevent legitimate access scenarios where a user lacks access to a dataset within their current organization due to permission restrictions.

Copy link
Member

@fm3 fm3 left a comment

Choose a reason for hiding this comment

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

Backend LGTM. A little sad that yet another route needs to take all variants, but I don’t see a good other way either.

app/controllers/AuthenticationController.scala Outdated Show resolved Hide resolved
const organizationToSwitchTo = useFetch(
async () => {
return user
? isDatasetAccessibleBySwitching({ directoryName, organizationId, type: "VIEW" })
Copy link
Member

Choose a reason for hiding this comment

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

hmm, now isDatasetAccessibleBySwitching is used in router.tsx for the legacy links and model.ts for the new links, right? can we avoid that this happens at two places. ideally, i would have hoped that one of these two options would work:

  1. there is a conversion from legacy to non legacy and afterwards the accessibility check is done
  2. the accessibility check can work with the old and new format so that it is only called at one code location

but maybe this isn't possible? wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. there is a conversion from legacy to non legacy and afterwards the accessibility check is done

Sadly that's not possible in the current model of the backend. @fm3 what do you think about adjusting the permissions for the disambiguation route to include positive answers in case the user can access the dataset when they would switch into the correct organization?

  1. the accessibility check can work with the old and new format so that it is only called at one code location

Working with the old format is technically possible but here we have the reverse problem: In case the user opens a URL with the new schema .../datasets/ds_name-12390u1902u31u2301u20/view and is not in the orga of theaccessibleBySwitching route only works on dataset ids, legacy links do not work, and if the route works on organization id and directory name, the new links do not work anymore. Therefore, I'd say both variations must be supported or we do your 1. suggestion

Copy link
Member

Choose a reason for hiding this comment

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

fine by me, don’t have a strong opinion here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I'll implement 1.

@MichaelBuessemeyer
Copy link
Contributor Author

A little sad that yet another route needs to take all variants,

Maybe internally we could create a DatasetDescriptor or so that has datasetId, dataSourceId (directoryName, organizationId) as optional members while enforcing that at least one needs to be defined. Then the datasetDAO would support a findOneByDescriptor. This would maybe make some code in the backend easier and reduce the amount of code / arguments needed in some functions?

@fm3
Copy link
Member

fm3 commented Dec 4, 2024

Hmm, I’m not sure. It would be yet another descriptor/id/selector thingy floating around. I’d rather hope that we can unify this so that only one disambiguate route can serve all purposes where this is needed. But that’s not for this PR.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
app/controllers/AuthenticationController.scala (1)

244-244: Consider improving the error message

While the validation logic is correct, the error message could be more descriptive to help API consumers understand the requirement better.

-        _ <- bool2Fox(datasetDirectoryName.isDefined == organizationId.isDefined) ?~> "Organization id and directory name must be defined together."
+        _ <- bool2Fox(datasetDirectoryName.isDefined == organizationId.isDefined) ?~> "Dataset directory name and organization ID must either both be provided or both be omitted."
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e6db2c3 and 57f5135.

📒 Files selected for processing (1)
  • app/controllers/AuthenticationController.scala (3 hunks)
🔇 Additional comments (5)
app/controllers/AuthenticationController.scala (5)

237-238: LGTM: Method signature changes align with requirements

The addition of datasetDirectoryName and organizationId parameters enables support for legacy routes, which is consistent with the PR objectives.


247-251: LGTM: Super user access logic properly updated

The parameter passing to accessibleBySwitchingForSuperUser is correctly ordered and consistent with the method signature.


258-258: Re-evaluate the organization comparison logic

The check prevents switching organizations if the selected organization is the same as the current one. However, there may be valid scenarios where a user lacks access to a dataset within their current organization due to permission restrictions.


270-281: LGTM: Pattern matching correctly implemented

The pattern matching has been properly structured with the new case for directory name and organization ID. The parameter mapping issues identified in previous reviews have been addressed.


341-346: Verify the usage of the new access check method

The implementation looks correct, but let's verify its usage across the codebase.

✅ Verification successful

Let me gather more context about the usage to ensure proper validation.


Let me check the DAO method implementation to ensure complete verification.


Let me check the implementation in Dataset.scala to verify the DAO method.


New access check method is properly implemented and used

The verification shows that:

  • The method is used in a pattern-matched context ensuring mutually exclusive access checks
  • Parameters are properly validated through the DAO layer
  • The implementation follows the same pattern as other access check methods
  • The DAO method is consistently used across the codebase with proper context and organization validation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for all calls to the new access check method
# and verify that the parameters are properly validated.

rg -A 2 "canAccessDatasetByDirectoryNameAndOrganization" 

Length of output: 828


Script:

#!/bin/bash
# Get more context around the usage to verify parameter validation
rg -B 10 -A 5 "canAccessDatasetByDirectoryNameAndOrganization\(" app/controllers/AuthenticationController.scala

Length of output: 1959


Script:

#!/bin/bash
# Check the DAO implementation for findOneByDirectoryNameAndOrganization
ast-grep --pattern 'findOneByDirectoryNameAndOrganization'

Length of output: 1455


Script:

#!/bin/bash
# Get the implementation of findOneByDirectoryNameAndOrganization from Dataset.scala
rg -B 2 -A 6 "def findOneByDirectoryNameAndOrganization" app/models/dataset/Dataset.scala

Length of output: 437

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 57f5135 and a865502.

📒 Files selected for processing (3)
  • app/controllers/DatasetController.scala (3 hunks)
  • app/models/dataset/Dataset.scala (1 hunks)
  • frontend/javascripts/components/redirect.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/models/dataset/Dataset.scala
🔇 Additional comments (4)
app/controllers/DatasetController.scala (3)

23-23: Import necessary classes from LiftWeb Common

The import statement import net.liftweb.common.{Failure, Full, Empty} brings in the Failure, Full, and Empty classes used for pattern matching with Box types below.


88-88: Inject AuthenticationController into DatasetController

Adding authenticationController: AuthenticationController to the constructor parameters allows the controller to utilize authentication methods required for the new logic in dataset retrieval.


410-433: ⚠️ Potential issue

Ensure secure access control when switching organizations

The method attempts to access datasets from other organizations using GlobalAccessContext and authenticationController.accessibleBySwitching. While this facilitates access to datasets via organization switching, it's crucial to ensure that this does not expose datasets to unauthorized users.

Please verify that appropriate access controls are in place within accessibleBySwitching to prevent security vulnerabilities.

As a follow-up, consider adding tests to ensure that datasets from other organizations cannot be accessed without proper permissions.

frontend/javascripts/components/redirect.tsx (1)

12-39: Refactor to functional component using React hooks

Converting AsyncRedirect to a functional component with hooks simplifies the component structure and improves code readability.

Comment on lines 410 to 433
datasetBox <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId).futureBox
result <- (datasetBox match {
case Full(dataset) =>
Fox.successful(
Ok(
Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case Empty =>
for {
dataset <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
result <- isAccessibleResult.header.status match {
case 200 => Fox.successful(Ok(Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case _ => Fox.failure(notFoundMessage(datasetName))
}
} yield result
case _ => Fox.failure(notFoundMessage(datasetName))
}) ?~> notFoundMessage(datasetName) ~> NOT_FOUND
} yield result
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle missing datasets when accessing with GlobalAccessContext

In the case Empty branch of the getDatasetIdFromNameAndOrganization method, if datasetDAO.findOneByNameAndOrganization returns None, attempting to access dataset._id will result in a NullPointerException.

Please add error handling to manage scenarios where the dataset is not found in GlobalAccessContext to prevent potential exceptions.

Apply this diff to handle the missing dataset case:

case Empty =>
  for {
-   dataset <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
+   datasetOption <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
+   dataset <- datasetOption ?~> notFoundMessage(datasetName)
    isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
    result <- isAccessibleResult.header.status match {
      case 200 => Fox.successful(Ok(Json.obj("id" -> dataset._id,
                                             "name" -> dataset.name,
                                             "organization" -> dataset._organization,
                                             "directoryName" -> dataset.directoryName)))
      case _ => Fox.failure(notFoundMessage(datasetName))
    }
  } yield result
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
datasetBox <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId).futureBox
result <- (datasetBox match {
case Full(dataset) =>
Fox.successful(
Ok(
Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case Empty =>
for {
dataset <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
result <- isAccessibleResult.header.status match {
case 200 => Fox.successful(Ok(Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case _ => Fox.failure(notFoundMessage(datasetName))
}
} yield result
case _ => Fox.failure(notFoundMessage(datasetName))
}) ?~> notFoundMessage(datasetName) ~> NOT_FOUND
} yield result
datasetBox <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId).futureBox
result <- (datasetBox match {
case Full(dataset) =>
Fox.successful(
Ok(
Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case Empty =>
for {
datasetOption <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
dataset <- datasetOption ?~> notFoundMessage(datasetName)
isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
result <- isAccessibleResult.header.status match {
case 200 => Fox.successful(Ok(Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case _ => Fox.failure(notFoundMessage(datasetName))
}
} yield result
case _ => Fox.failure(notFoundMessage(datasetName))
}) ?~> notFoundMessage(datasetName) ~> NOT_FOUND
} yield result

@@ -1,46 +1,41 @@
import type { RouteComponentProps } from "react-router-dom";
import { withRouter } from "react-router-dom";
import React from "react";
import type React from "react";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the import statement for React

The import statement import type React from "react"; imports React as a type-only import, which does not include the runtime value of React. Since React is needed for JSX transformation and to use React.FC, importing it as a type-only import may cause runtime errors.

Please update the import statement to include the React namespace:

-import type React from "react";
+import React from "react";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type React from "react";
import React from "react";

Comment on lines +14 to +36
const redirect = async () => {
const newPath = await redirectTo();

if (newPath.startsWith(location.origin)) {
// The link is absolute which react-router does not support
// apparently. See https://stackoverflow.com/questions/42914666/react-router-external-link
if (pushToHistory) {
location.assign(newPath);
} else {
location.replace(newPath);
}
return;
}

if (newPath.startsWith(location.origin)) {
// The link is absolute which react-router does not support
// apparently. See https://stackoverflow.com/questions/42914666/react-router-external-link
if (this.props.pushToHistory) {
location.assign(newPath);
if (pushToHistory) {
history.push(newPath);
} else {
location.replace(newPath);
history.replace(newPath);
}
return;
}
};

if (this.props.pushToHistory) {
this.props.history.push(newPath);
} else {
this.props.history.replace(newPath);
}
}
redirect();
});
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 9, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to the asynchronous redirect function

In the redirect async function, any errors thrown by redirectTo() or during the redirection process are not currently caught. This can lead to unhandled promise rejections and make debugging difficult.

Consider adding a try-catch block to handle potential errors gracefully.

Apply this diff to add error handling:

const redirect = async () => {
+  try {
    const newPath = await redirectTo();

    if (newPath.startsWith(location.origin)) {
      // The link is absolute which react-router does not support
      if (pushToHistory) {
        location.assign(newPath);
      } else {
        location.replace(newPath);
      }
      return;
    }

    if (pushToHistory) {
      history.push(newPath);
    } else {
      history.replace(newPath);
    }
+  } catch (error) {
+    console.error('Redirect failed:', error);
+    // Optionally, display an error message or navigate to an error page
+  }
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const redirect = async () => {
const newPath = await redirectTo();
if (newPath.startsWith(location.origin)) {
// The link is absolute which react-router does not support
// apparently. See https://stackoverflow.com/questions/42914666/react-router-external-link
if (pushToHistory) {
location.assign(newPath);
} else {
location.replace(newPath);
}
return;
}
if (newPath.startsWith(location.origin)) {
// The link is absolute which react-router does not support
// apparently. See https://stackoverflow.com/questions/42914666/react-router-external-link
if (this.props.pushToHistory) {
location.assign(newPath);
if (pushToHistory) {
history.push(newPath);
} else {
location.replace(newPath);
history.replace(newPath);
}
return;
}
};
if (this.props.pushToHistory) {
this.props.history.push(newPath);
} else {
this.props.history.replace(newPath);
}
}
redirect();
});
const redirect = async () => {
try {
const newPath = await redirectTo();
if (newPath.startsWith(location.origin)) {
// The link is absolute which react-router does not support
// apparently. See https://stackoverflow.com/questions/42914666/react-router-external-link
if (pushToHistory) {
location.assign(newPath);
} else {
location.replace(newPath);
}
return;
}
if (pushToHistory) {
history.push(newPath);
} else {
history.replace(newPath);
}
} catch (error) {
console.error('Redirect failed:', error);
// Optionally, display an error message or navigate to an error page
}
};
redirect();
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's how it has always been @philippotto. Do you think we should catch errors here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Ok I gave this PR a whole new rework.

There are nearly no frontend changes left. I only kept the refactoring of the AsyncRedirect component to make it a functional hook-based component.

The original issue / bug is now solved backend. The disambiguate route now checks whether the dataset is accessible by the current user if that user would to an organization change, in case the dataset could not be found by a simple user context based db request. If the user is able to view the dataset when switching the organization, the route still returns the id of the dataset.
=> This lets the frontend change the route from the old schema (orgaId/datasetDirectoryName) to the new format (name-datasetId) and at that point the frontend automatically realizes that the user needs to perform an orga switch (because the backend disallows access to the DS but returns a positive result on the orga switching route request).

This change results in less edited code lines but the changes in the backend might need some more hands on. Depending on whether it is ok to have the DatasetController have the AuthenticationController as an injected dependency @fm3. Maybe most of the function used from the AuthenticationController can be put into the service class.

Besides these thoughts, this pr is ready for another review round

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
CHANGELOG.unreleased.md (1)

24-24: Enhance the changelog entry to be more descriptive.

While the current entry correctly identifies the fix, it could be more descriptive about the specific issue and technical changes. Consider expanding it to:

-- Fixed the organization switching feature for datasets opened via old links. [#8257](https://github.com/scalableminds/webknossos/pull/8257)
+- Fixed the organization switching feature for datasets accessed through legacy routes by ensuring proper resolution using directoryName instead of datasetId. [#8257](https://github.com/scalableminds/webknossos/pull/8257)

This provides more context about:

  • The specific component affected (legacy routes)
  • The technical change made (using directoryName)
  • The reason for the change (proper resolution)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a865502 and 4f03707.

📒 Files selected for processing (1)
  • CHANGELOG.unreleased.md (1 hunks)

@fm3
Copy link
Member

fm3 commented Dec 9, 2024

Sweet! I think that’s a good approach! (Did not go through the code again yet).
I would like to avoid the controller classes depending on each other. Maybe you could extract the relevant logic into a service?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
app/controllers/DatasetController.scala (2)

410-438: Consider adding logging for debugging organization switching.

Since this code handles organization switching functionality, adding debug logging would help track and diagnose issues in production.

Add logging statements:

case Empty =>
  for {
+   _ = logger.debug(s"Dataset not found in current context, attempting organization switch for dataset: $datasetName")
    datasetOption <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
    dataset <- datasetOption ?~> notFoundMessage(datasetName)
+   _ = logger.debug(s"Found dataset in global context, attempting to switch organization for dataset: ${dataset._id}")
    isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
    result <- isAccessibleResult.header.status match {
      case 200 =>
+       _ = logger.debug(s"Successfully switched organization for dataset: ${dataset._id}")
        Fox.successful(Ok(Json.obj("id" -> dataset._id,
                                 "name" -> dataset.name,
                                 "organization" -> dataset._organization,
                                 "directoryName" -> dataset.directoryName)))
      case _ =>
+       _ = logger.debug(s"Failed to switch organization for dataset: ${dataset._id}")
        Fox.failure(notFoundMessage(datasetName))
    }
  } yield result

410-438: Consider extracting the JSON response into a helper method.

The JSON response structure is duplicated in both the Full and Empty cases. Consider extracting it into a helper method to improve maintainability and reduce code duplication.

Create a helper method:

+ private def datasetToJson(dataset: Dataset): JsObject = {
+   Json.obj(
+     "id" -> dataset._id,
+     "name" -> dataset.name,
+     "organization" -> dataset._organization,
+     "directoryName" -> dataset.directoryName
+   )
+ }

  def getDatasetIdFromNameAndOrganization(datasetName: String, organizationId: String): Action[AnyContent] =
    sil.UserAwareAction.async { implicit request =>
      for {
        datasetBox <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId).futureBox
        result <- (datasetBox match {
          case Full(dataset) =>
-           Fox.successful(Ok(Json.obj("id" -> dataset._id,
-                                    "name" -> dataset.name,
-                                    "organization" -> dataset._organization,
-                                    "directoryName" -> dataset.directoryName)))
+           Fox.successful(Ok(datasetToJson(dataset)))
          case Empty =>
            for {
              datasetOption <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
              dataset <- datasetOption ?~> notFoundMessage(datasetName)
              isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
              result <- isAccessibleResult.header.status match {
-               case 200 => Fox.successful(Ok(Json.obj("id" -> dataset._id,
-                                                    "name" -> dataset.name,
-                                                    "organization" -> dataset._organization,
-                                                    "directoryName" -> dataset.directoryName)))
+               case 200 => Fox.successful(Ok(datasetToJson(dataset)))
                case _ => Fox.failure(notFoundMessage(datasetName))
              }
            } yield result
          case _ => Fox.failure(notFoundMessage(datasetName))
        }) ?~> notFoundMessage(datasetName) ~> NOT_FOUND
      } yield result
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4f03707 and 1981fbf.

📒 Files selected for processing (1)
  • app/controllers/DatasetController.scala (3 hunks)
🔇 Additional comments (1)
app/controllers/DatasetController.scala (1)

410-438: ⚠️ Potential issue

Potential null pointer exception in the Empty case.

In the Empty case, if findOneByNameAndOrganization returns None, accessing dataset._id will result in a NullPointerException. The code needs to handle the case where the dataset is not found in GlobalAccessContext.

Apply this diff to handle the missing dataset case:

case Empty =>
  for {
-   dataset <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
+   datasetOption <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
+   dataset <- datasetOption ?~> notFoundMessage(datasetName)
    isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString), None, None)(request)
    result <- isAccessibleResult.header.status match {
      case 200 => Fox.successful(Ok(Json.obj("id" -> dataset._id,
                                           "name" -> dataset.name,
                                           "organization" -> dataset._organization,
                                           "directoryName" -> dataset.directoryName)))
      case _ => Fox.failure(notFoundMessage(datasetName))
    }
  } yield result

Likely invalid or redundant comment.

@philippotto
Copy link
Member

Ok I gave this PR a whole new rework.

There are nearly no frontend changes left. I only kept the refactoring of the AsyncRedirect component to make it a functional hook-based component.

Excellent 👍 frontend lgtm to me. leaving final review to @fm3.

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

I would like to avoid the controller classes depending on each other. Maybe you could extract the relevant logic into a service?

I tired to do this and it works but I am not sure, whether the location where I extracted it to is good.

I could not find an AuthenticationService and the functionality did not match for going into the User/Dataset/OrganizationService. Thus, I created a AuthenticationService and moved it to app/security as I could not find a suitable place for this service. I am very open to suggestion here :)

Besides that, everything should be working smoothly :D

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
app/security/AuthenticationService.scala (2)

108-108: Handle compound annotations in canAccessAnnotation

The comment indicates that the current implementation does not support compound annotations:

// Note: this does not work for compound annotations.

Consider extending support for compound annotations or explicitly handling this case to prevent unexpected behavior for users who might work with compound annotations.


88-97: Refactor to reduce code duplication in pattern matching

The pattern matching logic in canAccessDatasetOrAnnotationOrWorkflow is similar to that in accessibleBySwitchingForSuperUser. To improve maintainability and reduce code duplication, consider abstracting the common pattern matching into a helper method.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1981fbf and 61c0a88.

📒 Files selected for processing (4)
  • CHANGELOG.unreleased.md (1 hunks)
  • app/controllers/AuthenticationController.scala (3 hunks)
  • app/controllers/DatasetController.scala (3 hunks)
  • app/security/AuthenticationService.scala (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.unreleased.md
  • app/controllers/DatasetController.scala
🔇 Additional comments (3)
app/security/AuthenticationService.scala (1)

44-44: 🛠️ Refactor suggestion

Re-evaluate the organization comparison logic

The condition:

_ <- bool2Fox(selectedOrganization._id != user._organization) // User is already in correct orga, but still could not see dataset. Assume this had a reason.

prevents switching organizations if the selected organization is the same as the user's current organization. However, there may be valid scenarios where a user lacks access to a dataset within their current organization due to permission restrictions. Consider revising this logic to allow for permission checks even when the organization is the same, or provide a more specific error message to guide the user.

app/controllers/AuthenticationController.scala (2)

40-40: Dependency injection of AuthenticationService is correctly implemented

The AuthenticationService is properly injected into the AuthenticationController, replacing the annotationProvider. This update centralizes authentication logic and promotes better code organization.


222-225: Parameters are correctly aligned in the method call

The parameters passed to getOrganizationToSwitchTo are in the correct order, matching the method signature in AuthenticationService. This ensures that the correct identifiers are used during organization retrieval.

private def canAccessAnnotation(user: User, ctx: DBAccessContext, annotationId: String): Fox[Boolean] = {
val foundFox = for {
annotationIdParsed <- ObjectId.fromString(annotationId)
annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid using GlobalAccessContext to ensure proper access control

In the canAccessAnnotation method, using GlobalAccessContext bypasses authorization checks:

annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext) // Note: this does not work for compound annotations.

This may lead to security issues by allowing access to annotations without proper permissions. Consider using the provided ctx (the authorized access context) to ensure that access control is correctly enforced.

Apply this diff to fix the issue:

-          annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext) // Note: this does not work for compound annotations.
+          annotation <- annotationDAO.findOne(annotationIdParsed)(ctx) // Note: this does not work for compound annotations.

Copy link
Member

@fm3 fm3 left a comment

Choose a reason for hiding this comment

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

Nice! Since the service does not currently contain any other authentication logic, how about we name it AccessibleBySwitchingService for the time being? if/when we move more logic there we cann call it AuthenticationService again. Also, I think it does not need to extend SqlEscaping as it does not interact with postgres directly. security package is fine by me.

@@ -317,7 +318,7 @@ class DatasetController @Inject()(userService: UserService,
def update(datasetId: String): Action[JsValue] =
sil.SecuredAction.async(parse.json) { implicit request =>
withJsonBodyUsing(datasetPublicReads) {
case (description, datasetName, legacyDatasetDisplayName, sortingKey, isPublic, tags, metadata, folderId) => {
case (description, datasetName, legacyDatasetDisplayName, sortingKey, isPublic, tags, metadata, folderId) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

My IDE complained about redundant brackets. Thus, I removed them

Copy link
Member

@fm3 fm3 left a comment

Choose a reason for hiding this comment

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

LGTM! Did not re-test now, I assume you did?

@MichaelBuessemeyer
Copy link
Contributor Author

Nice! Since the service does not currently contain any other authentication logic, how about we name it AccessibleBySwitchingService for the time being? if/when we move more logic there we cann call it AuthenticationService again. Also, I think it does not need to extend SqlEscaping as it does not interact with postgres directly. security package is fine by me.

Thanks and all done :)

@MichaelBuessemeyer
Copy link
Contributor Author

LGTM! Did not re-test now, I assume you did?

On it :)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
app/security/AccessibleBySwitchingService.scala (3)

43-43: Clarify the comment to match the condition logic

The comment may be confusing as it suggests the user is already in the correct organization, but the condition checks if the selected organization is different from the user's current organization. Consider rephrasing the comment for clarity.

Apply this diff to improve the comment:

-  _ <- bool2Fox(selectedOrganization._id != user._organization) // User is already in correct orga, but still could not see dataset. Assume this had a reason.
+  _ <- bool2Fox(selectedOrganization._id != user._organization) // User is not in the correct organization, proceed to switch organization.

59-59: Consider handling compound annotations

Currently, the methods do not support compound annotations, as noted in the code comments. To improve functionality and ensure full compatibility, consider adding support for compound annotations.

Also applies to: 106-106


1-122: Add unit tests for AccessibleBySwitchingService

Consider adding unit tests to cover different scenarios and ensure the correctness of the access control logic implemented in this service.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 61c0a88 and 08c0de7.

📒 Files selected for processing (3)
  • app/controllers/AuthenticationController.scala (3 hunks)
  • app/controllers/DatasetController.scala (4 hunks)
  • app/security/AccessibleBySwitchingService.scala (1 hunks)
👮 Files not reviewed due to content moderation or server errors (2)
  • app/controllers/DatasetController.scala
  • app/controllers/AuthenticationController.scala

private def canAccessAnnotation(user: User, ctx: DBAccessContext, annotationId: String): Fox[Boolean] = {
val foundFox = for {
annotationIdParsed <- ObjectId.fromString(annotationId)
annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use ctx instead of GlobalAccessContext when fetching annotation

To ensure proper access control based on the user's permissions, fetch the annotation using the authorized context ctx rather than GlobalAccessContext.

Apply this diff to fix the issue:

-        annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext)
+        annotation <- annotationDAO.findOne(annotationIdParsed)(ctx)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext)
annotation <- annotationDAO.findOne(annotationIdParsed)(ctx)

@MichaelBuessemeyer
Copy link
Contributor Author

LGTM! Did not re-test now, I assume you did?

On it :)

Done and worked.

@MichaelBuessemeyer MichaelBuessemeyer merged commit 002c915 into master Dec 11, 2024
3 checks passed
@MichaelBuessemeyer MichaelBuessemeyer deleted the add-switch-orga-for-legacy-links branch December 11, 2024 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Legacy link resolution should only use directoryName not the name
3 participants