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

feat: auth #6

Merged
merged 11 commits into from
Sep 12, 2024
Merged

feat: auth #6

merged 11 commits into from
Sep 12, 2024

Conversation

Nictheboy
Copy link
Collaborator

@Nictheboy Nictheboy commented Aug 24, 2024

Summary by CodeRabbit

  • New Features

    • Added AuthorizationInterceptor for centralized request authorization handling.
    • Introduced WebMvcConfig to configure Spring MVC with the new interceptor.
    • Created a new AuthorizationService for managing JWT-based authorization.
    • Introduced CustomAuthLogics for flexible custom authorization logic management.
    • Added Guard, NoAuth, and ResourceId annotations for enhanced security controls.
    • Created tests for the AuthorizationInterceptor and AuthorizationService.
  • Bug Fixes

    • Enhanced error handling by restructuring error classes and simplifying references, maintaining functionality.
  • Documentation

    • Included a README for testing best practices with MockK.
  • Chores

    • Updated package structures for better organization and clarity.
    • Added scripts for managing Docker services and simplified setup instructions.

Copy link

coderabbitai bot commented Aug 24, 2024

Walkthrough

The changes introduce several enhancements to the project, including the addition of new dependencies for testing and security, the introduction of new classes for authorization and error handling, and a reorganization of existing classes into a more structured package hierarchy. A README file for testing practices has been added, along with various test classes to validate the functionality of newly introduced components such as the AuthorizationInterceptor.

Changes

File Change Summary
pom.xml Added dependencies for springmockk, bcrypt, jersey-client, and java-jwt.
src/main/kotlin/org/rucca/cheese/auth/*.kt Introduced AuthorizationInterceptor, AuthorizationAspect, AuthorizationService, and related error handling classes.
src/main/kotlin/org/rucca/cheese/common/error/*.kt Moved error classes to org.rucca.cheese.common.error, simplified type references.
src/main/kotlin/org/rucca/cheese/common/config/*.kt Introduced WebMvcConfig and ApplicationConfig classes for configuration management.
src/test/README.md Added README for best practices in testing, recommending MockK.
src/test/kotlin/org/rucca/cheese/common/ConfigTest.kt Introduced SpaceTest class to test AuthorizationInterceptor functionality.
src/test/kotlin/org/rucca/cheese/auth/*.kt Added AuthorizationAspectTest, AuthorizationServiceTest, and UserCreatorServiceTest for comprehensive testing.
src/main/kotlin/org/rucca/cheese/space/*.kt Updated SpaceController and SpaceService to integrate new authorization features.
design/DB/CREATE.sql Modified database schema to create a new avatar table and update user_profile.
.github/workflows/build.yml Removed PostgreSQL and Elasticsearch service definitions; added a script to manage services.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant I as AuthorizationInterceptor
    participant S as SpaceService
    participant R as Response
    participant A as AuthorizationService

    C->>I: Sends request to /space
    I->>I: preHandle() method called
    I->>A: Checks authorization
    A->>S: Validates request
    S->>R: Returns response
    I->>C: Returns response
Loading

🐰 Hopping through the code with glee,
New tests and mocks for all to see!
Authorization checks in line,
Errors structured, all's divine.
With springmockk, we leap and bound,
In our code garden, joy is found! 🥕


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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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 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.

@Nictheboy Nictheboy marked this pull request as draft August 24, 2024 16:11
Copy link

@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, codebase verification and nitpick comments (2)
src/test/README.md (2)

4-4: Remove trailing punctuation in heading.

The heading should not have trailing punctuation.

Apply this diff to fix the heading:

-## Do not use `Mockito`. Use `MockK` instead.
+## Do not use `Mockito`. Use `MockK` instead
Tools
Markdownlint

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: Convert bare URL to Markdown link.

Bare URLs should be converted to Markdown links for better readability.

Apply this diff to fix the URL:

-See https://github.com/Ninja-Squad/springmockk for more information.
+See [springmockk](https://github.com/Ninja-Squad/springmockk) for more information.
Tools
Markdownlint

6-6: null
Bare URL used

(MD034, no-bare-urls)

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between cb7b38a and 87eac6a.

Files selected for processing (13)
  • pom.xml (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/AuthorizationInterceptor.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/configuration/WebMvcConfig.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/error/BadRequestError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/error/BaseError.kt (2 hunks)
  • src/main/kotlin/org/rucca/cheese/common/error/GlobalErrorHandler.kt (2 hunks)
  • src/main/kotlin/org/rucca/cheese/common/error/InternalServerError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/error/NotFoundError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/persistent/BaseEntity.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/space/SpaceEntity.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/space/SpaceService.kt (1 hunks)
  • src/test/README.md (1 hunks)
  • src/test/kotlin/org/rucca/cheese/common/ConfigTest.kt (1 hunks)
Files skipped from review due to trivial changes (7)
  • src/main/kotlin/org/rucca/cheese/common/error/BadRequestError.kt
  • src/main/kotlin/org/rucca/cheese/common/error/BaseError.kt
  • src/main/kotlin/org/rucca/cheese/common/error/GlobalErrorHandler.kt
  • src/main/kotlin/org/rucca/cheese/common/error/InternalServerError.kt
  • src/main/kotlin/org/rucca/cheese/common/persistent/BaseEntity.kt
  • src/main/kotlin/org/rucca/cheese/space/SpaceEntity.kt
  • src/main/kotlin/org/rucca/cheese/space/SpaceService.kt
Additional context used
Markdownlint
src/test/README.md

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: null
Bare URL used

(MD034, no-bare-urls)

Additional comments not posted (18)
src/test/README.md (3)

1-1: Title is clear and concise.

The title effectively communicates the content of the README file.


2-2: Introduction is clear.

The introductory sentence provides useful context for the reader.


5-5: Explanation is clear and informative.

The reason for using MockK over Mockito is well-explained.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationInterceptor.kt (5)

1-1: Package declaration is correct.

The package declaration follows the standard convention.


3-6: Import statements are correct.

The import statements are necessary for the functionality of the class.


8-8: @Component annotation is correctly used.

The @Component annotation indicates that the class is a Spring component.


9-9: Class declaration is correct.

The class declaration follows standard conventions.


10-12: preHandle method is correctly overridden.

The preHandle method is correctly overridden and currently allows all requests to proceed.

src/main/kotlin/org/rucca/cheese/common/error/NotFoundError.kt (3)

1-1: Package declaration is correct.

The package declaration reflects the new package structure.


3-4: Import statements are correct.

The import statements are necessary for the functionality of the class.


6-11: Class declaration and constructor are correct.

The class declaration and constructor follow standard conventions. The use of IdType without the fully qualified name improves readability.

src/main/kotlin/org/rucca/cheese/common/configuration/WebMvcConfig.kt (3)

1-8: LGTM! Package declaration and imports are appropriate.

The package declaration and imports are necessary for the functionality provided in the file.


10-11: LGTM! Class declaration and constructor are appropriate.

The class declaration and constructor follow best practices.


12-14: LGTM! Method implementation is appropriate.

The addInterceptors method correctly adds the AuthorizationInterceptor.

src/test/kotlin/org/rucca/cheese/common/ConfigTest.kt (3)

1-12: LGTM! Package declaration and imports are appropriate.

The package declaration and imports are necessary for the functionality provided in the file.


13-17: LGTM! Class declaration and constructor are appropriate.

The class declaration and constructor follow best practices.


19-23: LGTM! Method implementation is appropriate.

The testAddAuthorizationInterceptor method correctly tests the AuthorizationInterceptor.

pom.xml (1)

116-121: LGTM! Dependency addition is appropriate.

The added dependency for springmockk is necessary for testing and correctly added.

@Nictheboy Nictheboy force-pushed the feat-auth branch 2 times, most recently from 594b56c to 52b6153 Compare August 24, 2024 16:30
…onService.audit() before each controller method is called
@Nictheboy Nictheboy marked this pull request as ready for review September 12, 2024 07:24
Copy link

@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: 10

Outside diff range and nitpick comments (7)
src/main/kotlin/org/rucca/cheese/auth/exception/NoGuardOrNoAuthAnnotationException.kt (1)

1-9: LGTM with a minor suggestion!

The custom exception class is well-defined and follows the best practices for exception handling in Kotlin. The exception message is clear and informative, including relevant details like the controller and method names.

Consider using string templates for constructing the exception message, which is more idiomatic and efficient in Kotlin:

-        RuntimeException(
-                "Any method in a controller must have a @Guard or @NoAuth annotation. " +
-                        "However, method $methodName in controller $controllerName does not have any.")
+        RuntimeException(
+                "Any method in a controller must have a @Guard or @NoAuth annotation. " +
+                        "However, method $methodName in controller $controllerName does not have any.")
src/main/resources/application.properties (1)

12-12: Consider using environment variables or profiles for the legacy URL.

The addition of the application.legacy-url property is a good practice for externalizing configuration. However, hardcoding the URL as http://localhost:3000 may not be suitable for all environments.

Consider using environment variables or Spring profiles to manage the legacy URL for different environments (e.g., local, staging, production). This allows for easier configuration and deployment across various environments without modifying the codebase.

For example, you can define the legacy URL as an environment variable:

application.legacy-url=${LEGACY_URL:http://localhost:3000}

This way, the value can be overridden by setting the LEGACY_URL environment variable, and it defaults to http://localhost:3000 if the environment variable is not set.

README.md (1)

11-14: Streamlined setup process looks good! Consider rephrasing the Windows-specific instructions.

The changes to the setup process, which now involve running the doc/script/dependency-start.sh and doc/script/dependency-restart.sh scripts, greatly simplify the process for users. The instructions are clear and concise, making it easier to set up the necessary services.

To add variety to the writing and address the static analysis hint, consider rephrasing the sentence mentioning "You may need to remove sudo..." for Windows users. For example:

-You may need to remove ```sudo``` and run the commands one by one manually if you want to do it on Windows in Docker Desktop
+For Windows users running Docker Desktop, it might be necessary to omit ```sudo``` and execute the commands individually.
Tools
LanguageTool

[style] ~11-~11: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...esult in all sorts of errors. You also need to set up several services. We recommend u...

(REP_NEED_TO_VB)


[style] ~13-~13: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...Desktop to start the services. (You may need to remove sudo and run the commands ...

(REP_NEED_TO_VB)

design/DB/CREATE.sql (2)

69-71: The foreign key constraint is good, but consider using a more meaningful name.

Adding the foreign key constraint to link the user_profile table with the avatar table is crucial for maintaining referential integrity. However, the constraint name FKo5dcemd97atrmjapi9x4s1j32 seems to be auto-generated and may not be very meaningful.

Consider using a more descriptive and human-readable name for the constraint, such as fk_user_profile_avatar. This will make the schema more self-explanatory and easier to understand for other developers.

ALTER TABLE IF EXISTS public.user_profile
ADD CONSTRAINT fk_user_profile_avatar
FOREIGN KEY (avatar_id) REFERENCES avatar;

73-74: The foreign key constraint is good, but consider using a more meaningful name.

Adding the foreign key constraint to link the user_profile table with the user table is crucial for maintaining referential integrity. However, the constraint name FKqcd5nmg7d7ement27tt9sf3bi seems to be auto-generated and may not be very meaningful.

Consider using a more descriptive and human-readable name for the constraint, such as fk_user_profile_user. This will make the schema more self-explanatory and easier to understand for other developers.

ALTER TABLE IF EXISTS public.user_profile
ADD CONSTRAINT fk_user_profile_user
FOREIGN KEY (user_id) REFERENCES public."user";
src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt (2)

30-50: LGTM! Consider adding validation for the input parameters.

The createUser method is correctly implemented and follows the best practices for password hashing and data persistence. However, to ensure data integrity, consider adding validation for the input parameters, such as:

  • Checking for empty or null values.
  • Validating the email format.
  • Ensuring the username and nickname meet the required length and format.
  • Verifying that the username and email are unique.

52-70: LGTM! Consider adding more specific exception handling and logging.

The login method is correctly implemented and follows the best practices for making HTTP requests and handling responses. However, consider the following improvements:

  1. Add more specific exception handling and logging to help with debugging and troubleshooting. For example, you can catch specific exceptions like IOException or JSONException and log the error message along with the relevant request and response details.

  2. Address the static analysis hint by logging the original exception before throwing the RuntimeException. This will help preserve the original exception information for better error tracking.

Here's an example of how you can implement the suggested improvements:

try {
    return result.getJSONObject("data").getString("accessToken")
} catch (e: Exception) {
    logger.error("Failed to extract JWT token from response: $result", e)
    throw RuntimeException("Failed to login: $result", e)
}
Tools
detekt

[warning] 67-67: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 87eac6a and b112017.

Files selected for processing (40)
  • .github/workflows/build.yml (1 hunks)
  • README.md (1 hunks)
  • design/DB/CREATE.sql (3 hunks)
  • doc/script/dependency-restart.sh (1 hunks)
  • doc/script/dependency-start.sh (1 hunks)
  • pom.xml (1 hunks)
  • src/main/kotlin/org/rucca/cheese/BackendApplication.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/AuthorizationAspect.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/CustomAuthLogics.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/Definitions.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/OwnerIds.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/annotation/Guard.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/annotation/NoAuth.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/annotation/ResourceId.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/AuthenticationRequiredError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/InvalidTokenError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/PermissionDeniedError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/TokenExpiredError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/DuplicatedResourceIdAnnotationException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/NoGuardOrNoAuthAnnotationException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/ResourceIdTypeMismatchException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/UnsupportedRoleException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/config/ApplicationConfig.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/persistent/BaseEntity.kt (2 hunks)
  • src/main/kotlin/org/rucca/cheese/space/SpaceController.kt (2 hunks)
  • src/main/kotlin/org/rucca/cheese/space/SpaceService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/user/RoleBasedAuthLogicService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/user/RolePermissionService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/user/UserEntity.kt (3 hunks)
  • src/main/kotlin/org/rucca/cheese/user/UserProfileEntity.kt (2 hunks)
  • src/main/resources/application.properties (1 hunks)
  • src/test/README.md (1 hunks)
  • src/test/kotlin/org/rucca/cheese/api/SpaceTest.kt (2 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/AuthorizationAspectTest.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/AuthorizationServiceTest.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/ExampleController.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/ExampleRestController.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/UserCreatorServiceTest.kt (1 hunks)
Files skipped from review due to trivial changes (1)
  • src/main/kotlin/org/rucca/cheese/space/SpaceService.kt
Additional context used
detekt
src/main/kotlin/org/rucca/cheese/auth/annotation/NoAuth.kt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/auth/annotation/ResourceId.kt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/user/RoleBasedAuthLogicService.kt

[warning] 58-58: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt

[warning] 67-67: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt

[warning] 93-93: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 95-95: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Markdownlint
src/test/README.md

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: null
Bare URL used

(MD034, no-bare-urls)

LanguageTool
README.md

[style] ~11-~11: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...esult in all sorts of errors. You also need to set up several services. We recommend u...

(REP_NEED_TO_VB)


[style] ~13-~13: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...Desktop to start the services. (You may need to remove sudo and run the commands ...

(REP_NEED_TO_VB)

Additional comments not posted (68)
src/main/kotlin/org/rucca/cheese/auth/annotation/NoAuth.kt (1)

1-3: LGTM!

The NoAuth annotation class is correctly defined and follows the Kotlin syntax. The @Target and @MustBeDocumented annotations are used appropriately.

Regarding the static analysis warning about the empty default constructor, it can be safely ignored as annotation classes in Kotlin do not require a constructor.

Tools
detekt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/auth/annotation/ResourceId.kt (1)

1-3: LGTM!

The ResourceId annotation is correctly defined and targeted for value parameters. The @MustBeDocumented annotation ensures that the annotation is included in the Javadoc.

The warning about the empty default constructor can be safely ignored because annotations in Kotlin do not require an explicit constructor.

Tools
detekt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/auth/exception/UnsupportedRoleException.kt (1)

1-5: LGTM!

The custom exception class UnsupportedRoleException is well-defined and follows best practices:

  • The class name clearly conveys the purpose of the exception.
  • Extending RuntimeException is appropriate for an unchecked exception.
  • The constructor includes the unsupported role in the exception message using string interpolation.
  • The exception class is defined in a separate file and placed in an appropriate package hierarchy.

Great job!

src/main/kotlin/org/rucca/cheese/auth/annotation/Guard.kt (1)

1-8: LGTM!

The Guard annotation class is well-designed and follows best practices:

  • The class name and property names are clear and descriptive.
  • The annotation targets functions, which is appropriate for a guard annotation.
  • The @MustBeDocumented ensures that the annotation is included in the generated documentation.
  • The properties action and resourceType provide flexibility in defining different actions and resource types.

The annotation can be easily applied to functions that require authorization checks, promoting a clean and maintainable codebase.

src/main/kotlin/org/rucca/cheese/auth/error/InvalidTokenError.kt (1)

1-6: LGTM!

The InvalidTokenError class is correctly defined and follows the Kotlin syntax. The class extends BaseError, which is likely a custom error class defined in the org.rucca.cheese.common.error package. The class is defined in a package that follows a structured hierarchy, which is a good practice for organizing code. The class name and the error message are descriptive and convey the purpose of the error. The HttpStatus.UNAUTHORIZED is an appropriate status code for an invalid token error.

src/main/kotlin/org/rucca/cheese/auth/error/TokenExpiredError.kt (1)

1-6: LGTM!

The TokenExpiredError class is well-defined and follows the project's error handling pattern. The package structure is well-organized, and the error class is appropriately named and scoped to the authentication module. The HTTP status code and error message are suitable for the token expiration scenario.

src/test/README.md (1)

1-6: LGTM!

The README file provides clear and useful testing guidelines. The recommendation to use MockK over Mockito for better Kotlin support is valid and will help improve the quality and maintainability of the test suite.

Tools
Markdownlint

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: null
Bare URL used

(MD034, no-bare-urls)

src/main/kotlin/org/rucca/cheese/auth/exception/ResourceIdTypeMismatchException.kt (1)

1-9: LGTM!

The custom exception class ResourceIdTypeMismatchException is well-defined and follows the best practices for exception handling in Kotlin. The exception message is informative and includes relevant details about the context where the exception occurred. The exception class is defined in an appropriate package structure and extends RuntimeException, which is suitable for unchecked exceptions.

src/main/kotlin/org/rucca/cheese/common/config/ApplicationConfig.kt (2)

1-11: LGTM!

The ApplicationConfig class is correctly implemented as a Spring component with configuration properties binding. The use of lateinit for the properties is appropriate since they will be initialized by the Spring container.


9-10: Verify the configuration properties.

Ensure that the application.legacyUrl and application.jwtSecret properties are correctly defined in the application configuration file (e.g., application.properties or application.yml).

Run the following script to verify the configuration properties:

src/main/kotlin/org/rucca/cheese/auth/error/AuthenticationRequiredError.kt (1)

1-10: LGTM!

The AuthenticationRequiredError class is well-structured and follows best practices. It extends the BaseError class and provides a clear and concise error message for authentication required scenarios. The HTTP status code and error code are appropriate for this type of error.

The class encapsulates the necessary information and can be easily integrated with the existing error handling mechanism.

src/main/kotlin/org/rucca/cheese/auth/exception/DuplicatedResourceIdAnnotationException.kt (1)

1-9: LGTM!

The custom exception class DuplicatedResourceIdAnnotationException is well-defined and serves a specific purpose. The exception message is informative and includes relevant details such as the controller and method names for easier debugging. The exception class extends RuntimeException, which is appropriate for an unchecked exception. The package structure also follows the standard convention for exception classes.

src/main/kotlin/org/rucca/cheese/BackendApplication.kt (1)

3-3: LGTM!

The changes to enable configuration properties in the BackendApplication class are implemented correctly. The @EnableConfigurationProperties annotation is used appropriately with the ApplicationConfig class as the argument. This will allow the application to bind external configuration properties to the ApplicationConfig class, making it more flexible and maintainable.

The import statement for the ApplicationConfig class is also added correctly.

Great job!

Also applies to: 5-5, 8-8

src/test/kotlin/org/rucca/cheese/auth/ExampleController.kt (2)

10-11: LGTM!

The class is correctly defined as a Spring MVC controller using the @Controller annotation.


12-18: Provide more context for @Guard and @ResourceId, and replace the hardcoded response.

A few observations:

  1. The usage of @GetMapping and @RequestParam annotations looks good for defining the endpoint and extracting the id parameter.

  2. To better understand the usage of the @Guard and @ResourceId annotations, could you please provide more context about what these annotations do and how they are processed? This will help ensure they are being used correctly in this context.

  3. The method currently returns a hardcoded string. For a real implementation, this should be replaced with the actual business logic.

-    fun withId(
-            @RequestParam("id") @ResourceId id: IdType,
-    ): String {
-        return "example_1" 
-    }
+    fun withId(
+            @RequestParam("id") @ResourceId id: IdType,
+    ): String {
+        // TODO: Replace with actual implementation
+        return "example_1"
+    }
Verification successful

Annotations @Guard and @ResourceId are used correctly; replace hardcoded response.

The @Guard annotation is used for authorization by specifying an action and resource type, and the @ResourceId annotation marks parameters as resource identifiers. Both are used appropriately in the withId method. Consider replacing the hardcoded response with actual business logic for a real implementation.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of `@Guard` and `@ResourceId` annotations.

# Test: Search for the annotation definitions. Expect: Definitions that explain their purpose.
rg --type kotlin -A 10 $'@Target|@Retention' | rg $'Guard|ResourceId'

Length of output: 711

src/main/kotlin/org/rucca/cheese/auth/error/PermissionDeniedError.kt (1)

8-16: LGTM!

The PermissionDeniedError class is well-defined and follows a clear structure. The constructor parameters provide relevant information about the permission denied error, and the error message is informative, including the specific action, resource type, and resource ID. The use of HttpStatus.FORBIDDEN status code is appropriate for permission denied errors.

src/test/kotlin/org/rucca/cheese/auth/UserCreatorServiceTest.kt (1)

8-14: LGTM!

The UserCreatorServiceTest class is correctly set up as a Spring Boot integration test with the @SpringBootTest annotation. The UserCreatorService dependency is correctly autowired in the constructor, and the logger is initialized correctly.

src/main/kotlin/org/rucca/cheese/auth/Definitions.kt (1)

1-30: LGTM!

The code in this file is well-structured, follows best practices, and provides a clear definition of data classes related to authorization. The use of nullable types allows flexibility in defining the properties of the data classes. The package declaration and imports are correct and follow the standard conventions.

Great job!

.github/workflows/build.yml (1)

28-29: Ensure the dependency-start.sh script correctly sets up the required services.

I noticed that the PostgreSQL and Elasticsearch service definitions have been removed in favor of running a custom dependency-start.sh script. This change could impact the build environment and the reliability of tests that depend on these services.

To ensure a smooth transition, please provide more information about the dependency-start.sh script:

  • What services does it set up?
  • How does it handle the initialization and configuration of these services?
  • Are there any specific requirements or dependencies for running this script?

Additionally, I recommend thoroughly verifying the test results with this new setup to ensure that all tests pass and the build remains stable.

To verify the test results, you can run the following script:

Please let me know if you have any further questions or if there's anything else I can assist with regarding this change.

Verification successful

The dependency-start.sh script appears to correctly set up the required services.

The script sets up Docker containers for Elasticsearch and PostgreSQL, configures them with necessary environment variables, and initializes the database schema. This approach should maintain the build environment's stability and reliability.

  • Ensure that the script is executed correctly in the CI environment to verify its effectiveness.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all tests pass with the new dependency setup.

# Test: Run the tests and check the exit code. Expect: Exit code 0 (success).
mvn test
echo "Test exit code: $?"

# Test: Check for test failures in the output. Expect: No test failures.
mvn test | grep -i "test.*failed"

Length of output: 237


Script:

#!/bin/bash
# Locate the dependency-start.sh script and display its contents for review.
fd dependency-start.sh --exec cat

Length of output: 2188

src/test/kotlin/org/rucca/cheese/auth/AuthorizationServiceTest.kt (1)

1-34: LGTM!

The test class is well-structured and follows the standard practices for Spring Boot and JUnit 5 testing. It tests the core functionality of AuthorizationService to verify the authorization token. The use of constructor injection for dependencies and @SpringBootTest annotation for integration testing are good practices. The @TestInstance(TestInstance.Lifecycle.PER_CLASS) annotation is correctly used to create a single instance of the test class for all the tests, which is required for using @BeforeAll annotation.

src/main/kotlin/org/rucca/cheese/auth/OwnerIds.kt (3)

1-7: LGTM!

The package declaration, imports, and typealias are well-defined and follow best practices.


8-16: LGTM!

The OwnerIds class and its register method are well-implemented. The method performs the necessary check to prevent duplicate registrations and throws a clear exception if a duplicate registration is attempted.


18-30: LGTM!

The getOwnerIdGetter method is well-implemented. It retrieves the owner ID provider for a given resource type and resource ID, and returns an IdGetter object that caches the owner ID for efficient access. The method handles the case when no owner ID provider is found by returning null, allowing the caller to handle it appropriately.

src/main/kotlin/org/rucca/cheese/user/RolePermissionService.kt (1)

21-33: LGTM!

The function correctly creates an Authorization object for a standard user with the appropriate permissions.

src/main/kotlin/org/rucca/cheese/auth/CustomAuthLogics.kt (2)

6-14: LGTM!

The CustomAuthLogicHandler type alias provides a clear and concise way to define the signature of custom authorization logic handlers. The parameters cover the essential aspects needed for authorization decisions, and returning a Boolean allows the handler to indicate whether the authorization should be granted or denied.


16-39: LGTM!

The CustomAuthLogics class provides a simple and flexible way to manage and invoke custom authorization logic handlers. The register method ensures that a handler with the same name is not registered twice, and the invoke method retrieves the registered handler by name and invokes it with the provided parameters. The class encapsulates the storage and invocation of custom authorization logic handlers, promoting modularity and reusability.

src/main/kotlin/org/rucca/cheese/common/persistent/BaseEntity.kt (2)

15-15: Clarify the intended usage of the IdGetter type alias.

The addition of the IdGetter type alias enhances the code's expressiveness by providing a clear representation of functions that retrieve entity IDs. However, it's currently unused in the provided code. Could you please clarify how and where you intend to use this type alias?


1-1: Verify the impact of the package change on other files.

The package change improves the organization and clarity of the code. However, ensure that the import statements in other files that use this class have been updated accordingly.

Run the following script to verify the import statements:

Verification successful

Package change successfully integrated.

The package change for BaseEntity has been successfully updated across the codebase. The old import statement is no longer present, and the new import statement is correctly used in relevant files.

  • The old import statement import org.rucca.cheese.common.BaseEntity was not found.
  • The new import statement import org.rucca.cheese.common.persistent.BaseEntity is present in src/main/kotlin/org/rucca/cheese/space/SpaceEntity.kt.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the import statements for the `BaseEntity` class.

# Test: Search for the old import statement. Expect: No results.
rg --type kotlin $'import org\.rucca\.cheese\.common\.BaseEntity'

# Test: Search for the new import statement. Expect: Only valid usages.
rg --type kotlin -A 5 $'import org\.rucca\.cheese\.common\.persistent\.BaseEntity'

Length of output: 659

src/test/kotlin/org/rucca/cheese/auth/ExampleRestController.kt (3)

13-16: Verify if the function is intended to be publicly accessible without any authorization.

The function logic is correct. However, please confirm if this endpoint is intended to be publicly accessible without any authorization. If not, consider adding the appropriate authorization annotations.


41-47: LGTM!

The function is correctly implemented with the @Guard annotation and a @ResourceId parameter of the expected type.


49-53: LGTM!

The function is correctly implemented with the @NoAuth annotation, indicating that it does not require any authorization.

src/test/kotlin/org/rucca/cheese/api/SpaceTest.kt (4)

3-6: LGTM!

The new imports are necessary for the changes made in the test class.


15-31: LGTM!

The changes to the test class setup are necessary to efficiently create a user and obtain an authentication token before running the tests. The @BeforeAll method ensures that these steps are performed only once, which is more efficient than creating a new user for each test.


34-35: LGTM!

The update to the testGetSpaceAndNotFound test method is necessary to ensure that the request is properly authenticated when attempting to access a non-existent space. This change makes the test more accurate in reflecting the behavior of the API endpoint for authenticated users.


Line range hint 1-42: Overall, the changes made to the SpaceTest.kt file are well-structured and follow best practices for testing.

The updates to the test class setup and the testGetSpaceAndNotFound test method are necessary to ensure that the tests accurately reflect the behavior of the API endpoints for authenticated users. The file maintains a clear structure, with descriptive test method names and comprehensive assertions covering the important aspects of the API response.

Great job on these changes! 👍

README.md (1)

Line range hint 1-10:
No comments on the unchanged code segments.

Also applies to: 15-41

Tools
LanguageTool

[style] ~11-~11: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...esult in all sorts of errors. You also need to set up several services. We recommend u...

(REP_NEED_TO_VB)


[style] ~13-~13: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...Desktop to start the services. (You may need to remove sudo and run the commands ...

(REP_NEED_TO_VB)


[style] ~17-~17: This phrase is redundant (‘I’ stands for ‘interfaces’). Use simply “APIs”.
Context: ...shell or PowerShell. This will generate API interfaces from the OpenAPI specification in ```de...

(ACRONYM_TAUTOLOGY)

src/main/kotlin/org/rucca/cheese/space/SpaceController.kt (1)

Line range hint 19-23: LGTM!

The addition of the @Guard annotation to the getSpace function is a good security practice. It correctly enforces the requirement of the "query" permission for "space" before allowing access to the space data. The annotation parameters are properly specified, and the overall structure and logic of the function remain intact.

src/main/kotlin/org/rucca/cheese/user/RoleBasedAuthLogicService.kt (1)

16-35: LGTM!

The initialize function correctly registers the "role-based" custom authorization logic with the AuthorizationService. The logic is properly calling the audit function with the provided parameters.

src/main/kotlin/org/rucca/cheese/user/UserEntity.kt (4)

29-29: LGTM!

The import statement is necessary for defining the UserRepository interface.


54-54: LGTM!

The addition of the insertable = false attribute to the createdAt and updatedAt fields is appropriate as the values of these fields are likely automatically generated by the database.

Also applies to: 58-58


63-64: LGTM!

The addition of the UserRepository interface extending JpaRepository is a great enhancement as it provides CRUD operations for UserEntity, enabling easier data access and manipulation for user entities.


39-39: Verify the class usage.

The renaming of the User class to UserEntity clarifies the purpose of the class as an entity in the context of JPA. However, ensure that all references to the User class have been updated to match the new name.

Run the following script to verify the class usage:

Verification successful

Renaming Verification Successful

The renaming of the User class to UserEntity has been successfully propagated throughout the codebase. All references to the class have been updated accordingly, and no issues were found. The remaining references to User in test files pertain to method names or variables, not the class itself.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all references to the `User` class have been updated to `UserEntity`.

# Test: Search for the old class name. Expect: No occurrences.
rg --type kotlin $'User\b'

Length of output: 28


Script:

#!/bin/bash
# Description: Verify all references to the `UserEntity` class.

# Test: Search for the new class name. Expect: Occurrences in the codebase.
rg --type kotlin $'UserEntity\b' -A 3

Length of output: 39


Script:

#!/bin/bash
# Description: Verify all references to the `User` and `UserEntity` classes.

# Test: Search for both class names in the codebase.
rg --type kotlin 'User\b|UserEntity\b' -A 3

Length of output: 4885

design/DB/CREATE.sql (2)

24-26: LGTM!

The changes to the user_profile table look good. Adding the avatar_id and user_id columns with appropriate foreign key constraints establishes the necessary relationships between the tables. The UNIQUE constraint on user_id ensures data integrity by enforcing a one-to-one relationship between users and their profiles.


50-55: Looks good!

The creation of the avatar table is a nice addition to the schema. It provides a dedicated place to store user profile avatars. The auto-incrementing id column using a sequence is a good choice for generating unique identifiers for each avatar.

src/main/kotlin/org/rucca/cheese/user/UserProfileEntity.kt (3)

31-35: LGTM!

The Avatar entity class is correctly implemented with an auto-generated id field using a sequence.


54-69: LGTM!

The changes to the UserProfile class are correctly implemented:

  • The createdAt and updatedAt fields are now non-insertable, ensuring their values are automatically set by the database.
  • The one-to-one relationship with UserEntity and many-to-one relationship with Avatar are correctly implemented using the appropriate annotations and fetch types.

72-72: LGTM!

The UserProfileRepository interface is correctly implemented, extending JpaRepository for UserProfile, which will facilitate CRUD operations on user profiles.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationAspect.kt (1)

18-73: LGTM!

The AuthorizationAspect is well-implemented and follows best practices:

  • It focuses on a single responsibility of handling authorization checks.
  • It covers important scenarios such as missing annotations, duplicated @ResourceId annotations, and type mismatches.
  • It retrieves the authorization token from the request header using RequestContextHolder, which is a common practice in Spring applications.
  • It delegates the actual authorization audit to the AuthorizationService, promoting a clean separation of concerns.

The aspect is ready to be merged.

src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt (5)

72-74: LGTM!

The testUsername method is correctly implemented and generates a random test username as expected.


76-78: LGTM!

The testPassword method is correctly implemented and returns a hardcoded test password as expected.


80-82: LGTM!

The testEmail method is correctly implemented and generates a random test email as expected.


84-86: LGTM!

The testNickname method is correctly implemented and returns a hardcoded test nickname as expected.


92-94: LGTM!

The testIntro method is correctly implemented and returns a hardcoded test intro as expected.

src/test/kotlin/org/rucca/cheese/auth/AuthorizationAspectTest.kt (9)

41-44: LGTM!

The test method correctly verifies the handling of NoGuardOrNoAuthAnnotationException by GlobalErrorHandler when the required annotations are missing.


47-50: LGTM!

The test method correctly verifies the handling of DuplicatedResourceIdAnnotationException by GlobalErrorHandler when multiple @ResourceId annotations are present.


53-56: LGTM!

The test method correctly verifies the handling of ResourceIdTypeMismatchException by GlobalErrorHandler when the type of the @ResourceId parameter doesn't match the method parameter.


59-62: LGTM!

The test method correctly verifies that AuthorizationService.audit is called with null token when no authorization header is present in the request.


65-68: LGTM!

The test method correctly verifies that AuthorizationService.audit is called with the token from the "Authorization" header.


71-74: LGTM!

The test method correctly verifies that AuthorizationService.audit is called with the token from the "authorization" header, regardless of the header name's case.


77-80: LGTM!

The test method correctly verifies that AuthorizationService.audit is called with the token and the id parameter value when both are present in the request.


83-86: LGTM!

The test method correctly verifies that AuthorizationService.audit is not called when the @NoAuth annotation is present on the controller method.


89-92: LGTM!

The test method correctly verifies that AuthorizationService.audit is called with the token and the id parameter value when testing a controller that uses @RestController instead of @Controller.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt (2)

29-36: LGTM!

The function correctly delegates the authorization audit to the overloaded audit function after verifying the token.


38-78: LGTM!

The function correctly performs authorization audit based on the provided Authorization object. It thoroughly checks the permission rules to determine if the requested operation is permitted. The logic for checking the permission rules is comprehensive and covers all the necessary conditions. The error handling for denied operations is also appropriate.

pom.xml (4)

116-121: LGTM!

The addition of the springmockk dependency with version 4.0.2 and test scope is a great choice to enhance the project's unit testing capabilities. It provides a powerful mocking framework that integrates well with Spring Boot applications.


122-126: Verify the usage and configuration of bcrypt.

The addition of the bcrypt dependency is a good choice for secure password hashing. However, please ensure that the library is used correctly and that the hashing parameters (e.g., salt rounds) are appropriately configured to provide strong security.


127-131: LGTM!

The addition of the jersey-client dependency with version 3.1.8 is a good choice for integrating RESTful web services into the project. It provides a robust framework for building client-server interactions.


132-136: Verify the usage and security of JSON Web Tokens.

The addition of the java-jwt dependency is a good choice for implementing JSON Web Tokens (JWTs) in the project, likely for authentication and authorization purposes. However, please ensure that the library is used correctly and that the JWTs are properly secured (e.g., using strong signing algorithms, validating token expiration, etc.) to prevent vulnerabilities.

doc/script/dependency-start.sh Show resolved Hide resolved
Comment on lines +80 to +111
fun verify(token: String?): Authorization {
var tokenWithoutBearer = token
if (token.isNullOrEmpty()) throw AuthenticationRequiredError()
if (token.indexOf("Bearer ") == 0) tokenWithoutBearer = token.substring(7)
else if (token.indexOf("bearer ") == 0) tokenWithoutBearer = token.substring(7)

val payload: TokenPayload
try {
payload =
objectMapper.readValue(
verifier.verify(tokenWithoutBearer).getClaim("payload")?.toString()
?: throw InvalidTokenError(),
TokenPayload::class.java)
} catch (e: TokenExpiredException) {
throw TokenExpiredError()
} catch (e: JWTVerificationException) {
throw InvalidTokenError()
} catch (e: JacksonException) {
throw RuntimeException(
"The token is valid, but the payload of the token is not a TokenPayload object." +
" This is ether a bug or a malicious attack.",
e)
}

if (payload.validUntil < System.currentTimeMillis()) throw TokenExpiredError()
if (payload.signedAt > System.currentTimeMillis())
throw RuntimeException(
"The token is valid, but it was signed in the future." +
" This is a timezone bug or a malicious attack.")

return payload.authorization
}
Copy link

Choose a reason for hiding this comment

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

Log the original exception before throwing custom exceptions.

The function implementation for token verification looks good overall. It properly verifies the token and handles various error scenarios.

However, as pointed out by the static analysis hints, the caught exceptions are swallowed without logging or propagating the original exception information. This can make it difficult to diagnose issues if an unexpected exception occurs.

To preserve the exception information for better diagnostics, consider logging the original exception before throwing the custom exceptions. For example:

} catch (e: TokenExpiredException) {
    logger.error("Token expired", e)
    throw TokenExpiredError()
} catch (e: JWTVerificationException) {
    logger.error("Invalid token", e)
    throw InvalidTokenError()
} catch (e: JacksonException) {
    logger.error("Invalid token payload", e)
    throw RuntimeException(...)
}
Tools
detekt

[warning] 93-93: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 95-95: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Copy link

@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: 11

Outside diff range and nitpick comments (6)
src/main/kotlin/org/rucca/cheese/auth/annotation/NoAuth.kt (1)

1-3: LGTM! Consider removing the empty default constructor.

The NoAuth annotation class is correctly defined and follows the Kotlin syntax. The @Target and @MustBeDocumented annotations are used appropriately.

As suggested by the static analysis hint, the empty default constructor can be safely removed. Apply this diff to remove it:

-@Target(AnnotationTarget.FUNCTION) @MustBeDocumented annotation class NoAuth()
+@Target(AnnotationTarget.FUNCTION) @MustBeDocumented annotation class NoAuth
Tools
detekt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/test/README.md (1)

4-6: Address Markdownlint hints.

Please consider addressing the following Markdownlint hints to improve the formatting of the section:

  • Remove the trailing punctuation (.) from the heading at line 4.
  • Wrap the bare URL at line 6 with angle brackets (<>).

Apply this diff to address the Markdownlint hints:

-## Do not use `Mockito`. Use `MockK` instead.
+## Do not use `Mockito`. Use `MockK` instead
 `Mockito` has poor support for Kotlin, so it is better to use `MockK` instead.
-See https://github.com/Ninja-Squad/springmockk for more information.
+See <https://github.com/Ninja-Squad/springmockk> for more information.
Tools
Markdownlint

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: null
Bare URL used

(MD034, no-bare-urls)

src/test/kotlin/org/rucca/cheese/auth/AuthorizationServiceTest.kt (1)

29-33: Consider adding more test scenarios.

The current test class only covers the happy path scenario for the verify() method. Consider adding more test methods to cover other scenarios, such as:

  • Testing with an invalid token
  • Testing with an expired token
  • Testing with a token that belongs to a different user
  • Testing with a token that has been revoked

This will help ensure that the verify() method behaves correctly in all possible scenarios.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationAspect.kt (1)

23-26: Consider including other controller annotations in the pointcut expression.

The pointcut expression correctly matches methods in classes annotated with @RestController or @Controller. However, it may miss other controller annotations like @GraphQLController.

Consider updating the pointcut expression to include other relevant controller annotations used in the project:

-"@within(org.springframework.web.bind.annotation.RestController) || " +
-        "@within(org.springframework.stereotype.Controller)")
+"@within(org.springframework.web.bind.annotation.RestController) || " +
+        "@within(org.springframework.stereotype.Controller) || " +
+        "@within(org.springframework.graphql.data.method.annotation.Controller)")
src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt (1)

30-50: Validate input parameters in the createUser method.

The createUser method correctly creates a new user and user profile, and uses secure password hashing with BCrypt. However, consider adding validation for the input parameters to ensure data integrity and prevent invalid data from being persisted.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt (1)

1-112: Consider adding more detailed documentation for the class and its methods.

While the class and its methods are well-structured and named, it would be beneficial to add more detailed documentation explaining the purpose, parameters, and return values of each method. This will improve the overall maintainability and understandability of the code.

Tools
detekt

[warning] 93-93: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 95-95: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 87eac6a and b112017.

Files selected for processing (40)
  • .github/workflows/build.yml (1 hunks)
  • README.md (1 hunks)
  • design/DB/CREATE.sql (3 hunks)
  • doc/script/dependency-restart.sh (1 hunks)
  • doc/script/dependency-start.sh (1 hunks)
  • pom.xml (1 hunks)
  • src/main/kotlin/org/rucca/cheese/BackendApplication.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/AuthorizationAspect.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/CustomAuthLogics.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/Definitions.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/OwnerIds.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/annotation/Guard.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/annotation/NoAuth.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/annotation/ResourceId.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/AuthenticationRequiredError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/InvalidTokenError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/PermissionDeniedError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/error/TokenExpiredError.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/DuplicatedResourceIdAnnotationException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/NoGuardOrNoAuthAnnotationException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/ResourceIdTypeMismatchException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/auth/exception/UnsupportedRoleException.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/config/ApplicationConfig.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/common/persistent/BaseEntity.kt (2 hunks)
  • src/main/kotlin/org/rucca/cheese/space/SpaceController.kt (2 hunks)
  • src/main/kotlin/org/rucca/cheese/space/SpaceService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/user/RoleBasedAuthLogicService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/user/RolePermissionService.kt (1 hunks)
  • src/main/kotlin/org/rucca/cheese/user/UserEntity.kt (3 hunks)
  • src/main/kotlin/org/rucca/cheese/user/UserProfileEntity.kt (2 hunks)
  • src/main/resources/application.properties (1 hunks)
  • src/test/README.md (1 hunks)
  • src/test/kotlin/org/rucca/cheese/api/SpaceTest.kt (2 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/AuthorizationAspectTest.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/AuthorizationServiceTest.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/ExampleController.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/ExampleRestController.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt (1 hunks)
  • src/test/kotlin/org/rucca/cheese/auth/UserCreatorServiceTest.kt (1 hunks)
Files skipped from review due to trivial changes (1)
  • src/main/kotlin/org/rucca/cheese/space/SpaceService.kt
Additional context used
detekt
src/main/kotlin/org/rucca/cheese/auth/annotation/NoAuth.kt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/auth/annotation/ResourceId.kt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/user/RoleBasedAuthLogicService.kt

[warning] 58-58: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt

[warning] 67-67: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt

[warning] 93-93: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 95-95: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Markdownlint
src/test/README.md

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: null
Bare URL used

(MD034, no-bare-urls)

LanguageTool
README.md

[style] ~11-~11: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...esult in all sorts of errors. You also need to set up several services. We recommend u...

(REP_NEED_TO_VB)


[style] ~13-~13: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...Desktop to start the services. (You may need to remove sudo and run the commands ...

(REP_NEED_TO_VB)

Additional comments not posted (72)
src/main/kotlin/org/rucca/cheese/auth/annotation/ResourceId.kt (2)

1-3: LGTM!

The ResourceId annotation is correctly defined and targeted for value parameters. The @MustBeDocumented annotation ensures that the ResourceId annotation is included in the Javadoc.

Tools
detekt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)


3-3: Ignore the static analysis warning.

The static analysis warning about an empty default constructor is a false positive because annotations in Kotlin do not require a constructor.

Tools
detekt

[warning] 3-3: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

src/main/kotlin/org/rucca/cheese/auth/exception/UnsupportedRoleException.kt (1)

1-5: LGTM!

The UnsupportedRoleException class is well-defined and follows best practices:

  • The package declaration and directory structure are consistent.
  • The class name clearly conveys the purpose of the exception.
  • Extending RuntimeException is appropriate for an unchecked exception.
  • The constructor takes the unsupported role as a parameter and includes it in the exception message.

The exception can be effectively used to handle cases where an unsupported role is encountered during authorization.

src/main/kotlin/org/rucca/cheese/auth/annotation/Guard.kt (1)

1-8: LGTM!

The Guard annotation class is well-defined and follows good practices:

  • The class name and property names are clear and descriptive.
  • The annotation is appropriately targeted for functions.
  • The @MustBeDocumented annotation ensures that the annotation will be included in the generated documentation.
  • The action and resourceType properties provide flexibility in defining the authorization requirements for functions.

The annotation can be effectively used to specify the required authorization for executing functions, and an authorization mechanism can utilize the action and resourceType properties to determine if the user has the necessary permissions.

src/main/kotlin/org/rucca/cheese/auth/error/InvalidTokenError.kt (1)

1-6: LGTM!

The InvalidTokenError class is well-defined and follows the project's error handling pattern. The package structure, error message, and associated HTTP status code are appropriate for representing an invalid token scenario.

src/main/kotlin/org/rucca/cheese/auth/error/TokenExpiredError.kt (1)

1-6: LGTM!

The TokenExpiredError class is correctly defined and follows the best practices:

  • The class extends BaseError which is likely a custom error class defined in the project.
  • The class is defined in a package that follows a structured hierarchy.
  • The class constructor is using HttpStatus.UNAUTHORIZED which is the appropriate HTTP status code for an expired token.
  • The class constructor is using a clear and concise error message.
src/test/README.md (2)

1-3: LGTM!

The introduction section is clear and concise.


4-6: LGTM!

The recommendation to use MockK instead of Mockito for mocking in Kotlin is valid and useful for developers. The link to the springmockk library is also helpful for more information.

Tools
Markdownlint

4-4: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


6-6: null
Bare URL used

(MD034, no-bare-urls)

src/main/kotlin/org/rucca/cheese/auth/exception/ResourceIdTypeMismatchException.kt (1)

1-9: LGTM!

The custom exception class ResourceIdTypeMismatchException is well-defined and follows best practices:

  • The exception class is defined in a separate file and package, promoting modularity and encapsulation.
  • The exception class extends RuntimeException, which is appropriate for unchecked exceptions.
  • The exception message is informative and includes relevant details about the context where the exception occurred.
  • The naming convention for the exception class is followed.

Great job!

src/main/kotlin/org/rucca/cheese/common/config/ApplicationConfig.kt (1)

1-11: LGTM! Verify the configuration binding and consider handling missing values.

The ApplicationConfig class is properly annotated with @Component and @ConfigurationProperties, allowing it to be automatically detected by Spring and have its properties bound to the corresponding configuration values.

To verify the configuration binding, ensure that the application.properties or application.yml file contains the following properties:

application.legacy-url=<value>
application.jwt-secret=<value>

Replace <value> with the actual values for legacyUrl and jwtSecret.

To handle scenarios where the legacyUrl or jwtSecret properties might be missing from the configuration file, consider one of the following approaches:

  1. Provide default values for the properties:
lateinit var legacyUrl: String = "default-url"
lateinit var jwtSecret: String = "default-secret"
  1. Make the properties nullable and handle null values appropriately:
var legacyUrl: String? = null
var jwtSecret: String? = null

Choose the approach that best fits your application's requirements and handle the missing values accordingly in the code that uses these properties.

src/main/kotlin/org/rucca/cheese/auth/error/AuthenticationRequiredError.kt (1)

1-10: LGTM!

The AuthenticationRequiredError class is well-defined, extending BaseError and using HttpStatus.UNAUTHORIZED appropriately. The package structure and naming are also logical and clear. Good job!

src/main/kotlin/org/rucca/cheese/auth/exception/NoGuardOrNoAuthAnnotationException.kt (1)

1-9: LGTM!

The custom exception class is well-defined and follows best practices:

  • The package name and class name are appropriate and descriptive.
  • Extending RuntimeException is suitable for an unchecked exception.
  • The constructor parameters provide relevant context for the exception.
  • The error message is clear, informative, and uses string interpolation effectively.

Great job!

src/main/kotlin/org/rucca/cheese/auth/exception/DuplicatedResourceIdAnnotationException.kt (1)

1-9: LGTM!

The DuplicatedResourceIdAnnotationException class is well-defined and serves a specific purpose. The exception message provides useful information for debugging, and the class follows the naming convention for exceptions. The class is also placed in the appropriate package.

src/main/kotlin/org/rucca/cheese/BackendApplication.kt (1)

3-3: LGTM!

The addition of the @EnableConfigurationProperties annotation with ApplicationConfig class as the argument is a good practice. It allows the application to bind external configuration properties to the ApplicationConfig class, making the application more flexible and maintainable by supporting dynamic configuration based on different environments or deployment scenarios.

The import statement for the ApplicationConfig class is also correctly added.

Also applies to: 5-5, 8-8

src/main/resources/application.properties (1)

12-12: LGTM, but update the value in production environments.

The addition of the application.legacy-url property is a good way to externalize the configuration for the legacy service endpoint. However, please ensure that the actual legacy service URL is configured in production environments, as the current value http://localhost:3000 is likely a placeholder for development or testing purposes.

src/test/kotlin/org/rucca/cheese/auth/ExampleController.kt (1)

10-19: Clarify the purpose and usage of custom annotations.

The @Guard and @ResourceId annotations seem to be custom annotations used for authorization and resource identification respectively. However, their purpose and usage are not clear from the provided code context.

Could you please provide more information on how these annotations are intended to be used and how they integrate with the overall authorization mechanism?

Additionally, consider using the id parameter in the endpoint implementation, as it is currently unused and the method returns a hardcoded string.

Also, since this class is located under the test directory, please confirm if it is intended to be a test class or a sample implementation.

src/main/kotlin/org/rucca/cheese/auth/error/PermissionDeniedError.kt (1)

1-16: LGTM!

The PermissionDeniedError class is well-structured and follows best practices:

  • It is defined in an appropriate package hierarchy.
  • It extends the BaseError class, which likely provides common error handling functionality.
  • The constructor parameters are relevant and provide necessary information about the unauthorized action, resource type, and resource ID.
  • The error message is descriptive and includes the provided information.
  • The HTTP status is set to FORBIDDEN, which is suitable for permission denied errors.

Overall, the code is clean, readable, and follows a consistent structure. Great job!

src/test/kotlin/org/rucca/cheese/auth/UserCreatorServiceTest.kt (1)

8-14: LGTM!

The test class is correctly set up as a Spring Boot integration test, with UserCreatorService autowired in the constructor and a logger initialized.

src/main/kotlin/org/rucca/cheese/auth/Definitions.kt (5)

5-5: LGTM!

Using a type alias for String is a good practice to provide a more descriptive and domain-specific name for the type. It can help improve code readability and maintainability by making the intent of the code more clear.


7-12: LGTM!

The data class AuthorizedResource is well-defined and represents a resource that can be authorized. The properties provide relevant information about the resource, such as the owner, types, IDs, and additional data. Making all properties nullable with default values provides flexibility in creating instances of the data class.


14-19: LGTM!

The data class Permission is well-defined and represents a permission that grants access to perform certain actions on a resource. The properties provide relevant information about the permission, such as the authorized actions, the resource on which the actions can be performed, and any additional custom logic associated with the permission.


21-24: LGTM!

The data class Authorization is well-defined and represents an authorization granted to a user. The userId property identifies the user, and the permissions property specifies the list of permissions granted to the user.


26-30: LGTM!

The data class TokenPayload is well-defined and represents the payload of an authorization token. The properties provide relevant information about the token, such as the associated Authorization object, the timestamp when the token was signed, and the timestamp until which the token is valid.

.github/workflows/build.yml (1)

28-29: Verify the correctness and completeness of the service setup in the external script.

The change introduces a new step that runs an external script (doc/script/dependency-start.sh) to set up the necessary services. While this change potentially streamlines the workflow, it also shifts the responsibility of service management to the external script.

Please ensure that the script correctly and completely sets up the required services (PostgreSQL and Elasticsearch) that were previously defined in the workflow file. Consider adding comments or documentation to clarify the purpose and functionality of the script.

To verify the correctness and completeness of the service setup, please provide the contents of the doc/script/dependency-start.sh script for review. You can use the following command to retrieve the script:

If you need any assistance in reviewing the script or have any questions, please let me know. I'd be happy to help!

Verification successful

Verification Successful: The external script correctly sets up the required services.

The doc/script/dependency-start.sh script successfully sets up the necessary services (Elasticsearch and PostgreSQL) and includes additional setup for the cheese-backend-dev service. The script includes health checks and configuration adjustments, ensuring the services are correctly initialized and running. No further action is needed.

  • The script correctly replaces the removed PostgreSQL and Elasticsearch service definitions from the workflow file.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Retrieve the `doc/script/dependency-start.sh` script for review.

cat doc/script/dependency-start.sh

Length of output: 2189

src/test/kotlin/org/rucca/cheese/auth/AuthorizationServiceTest.kt (1)

1-34: LGTM!

The test class follows the standard structure and naming conventions for a JUnit 5 test class in Kotlin. The use of @SpringBootTest and @TestInstance(TestInstance.Lifecycle.PER_CLASS) is appropriate for the test class. The prepare() method sets up the necessary test data before running the tests, and the testVerify() method tests the verify() method of AuthorizationService as expected.

src/main/kotlin/org/rucca/cheese/auth/OwnerIds.kt (1)

8-31: LGTM!

The OwnerIds class is well-designed and follows best practices:

  • It adheres to the Single Responsibility Principle by focusing on managing owner ID providers.
  • It properly handles duplicate registrations and throws a RuntimeException to prevent overwriting existing providers.
  • It lazily evaluates the owner ID using the registered provider, which is a good optimization.
  • It uses appropriate data structures and type aliases to improve code readability and maintainability.
  • It is properly encapsulated by keeping the ownerIdProviders map private and providing public functions to interact with it.

Great job!

src/main/kotlin/org/rucca/cheese/user/RolePermissionService.kt (1)

21-33: LGTM!

The method correctly constructs the Authorization object for a standard user with the appropriate permission to query spaces.

src/main/kotlin/org/rucca/cheese/auth/CustomAuthLogics.kt (1)

6-14: LGTM!

The CustomAuthLogicHandler type alias is well-defined and provides a clear contract for custom authorization logic handlers. The parameter names are descriptive and the Boolean return type is appropriate. Using a type alias here promotes code reuse and improves readability.

src/main/kotlin/org/rucca/cheese/common/persistent/BaseEntity.kt (2)

15-15: Provide more context on the usage of IdGetter.

The addition of the IdGetter type alias is a good idea to enhance code readability and maintainability. However, it would be helpful to provide more context on how this type alias will be utilized in the codebase. Could you please share some examples or point me to the relevant code where IdGetter is being used?


1-1: Verify the impact of the package change.

The package reorganization improves the structure and clarity of the code. However, ensure that the import statements in other files that use the BaseEntity class have been updated to reflect the new package.

Run the following script to verify the import statements:

Verification successful

Package change verification successful.

The package reorganization for BaseEntity has been successfully updated in the codebase. The old import statement is no longer present, and the new import statement is correctly used in at least one file. No further action is required.

  • Verified in src/main/kotlin/org/rucca/cheese/space/SpaceEntity.kt.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the import statements for the `BaseEntity` class.

# Test: Search for the old import statement. Expect: No occurrences.
rg --type kotlin $'import org\.rucca\.cheese\.common\.BaseEntity'

# Test: Search for the new import statement. Expect: Only occurrences of the new import statement.
rg --type kotlin $'import org\.rucca\.cheese\.common\.persistent\.BaseEntity'

Length of output: 252

src/test/kotlin/org/rucca/cheese/auth/ExampleRestController.kt (5)

13-16: Verify if the function is intended to be publicly accessible.

The function logic is correct. However, please confirm if this endpoint is intended to be publicly accessible without any authorization checks. If not, consider adding the appropriate authorization annotations.


18-25: Verify the purpose of having two resource IDs.

The function logic is correct. However, please confirm the purpose of having two resource IDs as parameters. If only one resource ID is needed, consider removing the extra parameter to avoid confusion or misuse of the endpoint.


35-39: Verify how the authorization will work without a resource ID.

The function logic is correct. However, please confirm how the authorization logic will work without any resource ID parameter. If a resource ID is needed for proper authorization, consider adding it as a parameter.


41-47: LGTM!

The function logic is correct, and the usage of the @ResourceId annotation with the IdType type is accurate.


49-53: LGTM!

The function logic is correct, and the usage of the @NoAuth annotation to bypass authorization is appropriate.

src/test/kotlin/org/rucca/cheese/api/SpaceTest.kt (5)

3-6: LGTM!

The new imports are necessary for the changes made in the test class and look good.


15-22: LGTM!

The changes to the SpaceTest class look good:

  • The @TestInstance annotation is necessary to allow the @BeforeAll annotated method to be non-static.
  • The UserCreatorService dependency injection is necessary to create a user and obtain an authentication token before running the tests.

23-25: LGTM!

The new member variables user and token look good:

  • They are necessary to store the user and token information for use in the test methods.
  • The lateinit annotation is appropriate as the variables will be initialized in the @BeforeAll annotated method.

26-30: LGTM!

The new prepare method looks good:

  • It is necessary to set up the user authentication before running the tests.
  • The @BeforeAll annotation is appropriate to run the method once before all test methods.

34-35: LGTM!

The changes to the testGetSpaceAndNotFound test method look good:

  • Including the "Authorization" header with a bearer token is necessary to test the space API with user authentication.
  • The token is correctly obtained from the prepare method and used in the request header.
README.md (1)

11-14: LGTM!

The streamlined instructions for setting up necessary services using Docker and scripts improve the user experience and make the process more straightforward. The guidance for Docker Desktop on Windows is a helpful addition for users on that platform.

Tools
LanguageTool

[style] ~11-~11: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...esult in all sorts of errors. You also need to set up several services. We recommend u...

(REP_NEED_TO_VB)


[style] ~13-~13: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...Desktop to start the services. (You may need to remove sudo and run the commands ...

(REP_NEED_TO_VB)

src/main/kotlin/org/rucca/cheese/space/SpaceController.kt (1)

19-19: The addition of the @Guard annotation is a positive change, but more information is needed to assess its effectiveness.

The @Guard annotation added to the getSpace method is a good step towards enhancing the security of the API by restricting access to the method based on certain conditions. However, without more context about how the annotation is implemented and what the "query" and "space" parameters represent, it's difficult to fully assess the effectiveness of the access control logic.

Could you please provide more information about the @Guard annotation, such as its implementation details and how it integrates with the existing authentication and authorization mechanisms in the project? This will help in better understanding the impact of this change on the project's security.

src/main/kotlin/org/rucca/cheese/user/RoleBasedAuthLogicService.kt (1)

16-35: LGTM!

The initialize method correctly registers the custom authorization logic with the AuthorizationService using a lambda function. The lambda function passes all the required parameters to the audit method.

src/main/kotlin/org/rucca/cheese/user/UserEntity.kt (5)

29-29: LGTM!

The import statement is necessary and correctly added.


39-39: LGTM!

The class renaming enhances clarity and is consistent with the file name change.


54-54: LGTM!

The insertable = false attribute is correctly added to allow for automatic timestamping by the database.


58-58: LGTM!

The insertable = false attribute is correctly added to allow for automatic timestamping by the database.


64-64: LGTM!

The UserRepository interface is correctly defined and enhances the data access layer by providing a repository pattern for managing user entities.

design/DB/CREATE.sql (4)

24-26: LGTM!

The changes to the user_profile table look good:

  • The avatar_id foreign key establishes a relationship with the avatar table.
  • The UNIQUE constraint on user_id ensures that each user profile is associated with a distinct user.
  • The NOT NULL constraints enforce data integrity.

50-55: Looks good!

The avatar table is properly defined with an auto-incrementing primary key using the avatar_id_seq sequence. The table and column names follow naming conventions.


70-71: LGTM!

The foreign key constraint is correctly defined, establishing a relationship between the user_profile and avatar tables.


73-74: Looks good!

The foreign key constraint is correctly defined, establishing a relationship between the user_profile and user tables.

src/main/kotlin/org/rucca/cheese/user/UserProfileEntity.kt (3)

31-35: LGTM!

The Avatar class is correctly implemented as a JPA entity, with the id field properly annotated for auto-generation using a sequence.


54-54: LGTM!

The changes to the UserProfile class are correctly implemented:

  • The one-to-one relationship with UserEntity and many-to-one relationship with Avatar are properly annotated using JPA.
  • Updating the createdAt and updatedAt fields to be non-insertable ensures their values are set by the database upon record creation and update.

Also applies to: 58-58, 63-69


72-72: LGTM!

The UserProfileRepository interface is correctly declared, extending JpaRepository for UserProfile. This will provide CRUD operations for user profiles out of the box.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationAspect.kt (2)

1-17: LGTM!

The package declaration and import statements are correct and relevant.


18-22: LGTM!

The AuthorizationAspect class is correctly defined as a Spring aspect and bean. The constructor injection of AuthorizationService is also properly done.

src/test/kotlin/org/rucca/cheese/auth/UserCreatorService.kt (1)

72-94: LGTM!

The test data generation methods provide utility methods to generate unique test data for various fields. The use of random number generation ensures the uniqueness of the generated test data.

src/test/kotlin/org/rucca/cheese/auth/AuthorizationAspectTest.kt (9)

40-44: LGTM!

The test method is correctly testing the scenario where the endpoint does not have the @Guard or @NoAuth annotation. It verifies that the expected exception is thrown and handled by the GlobalErrorHandler.


46-50: LGTM!

The test method is correctly testing the scenario where the endpoint has multiple @ResourceId annotations. It verifies that the expected exception is thrown and handled by the GlobalErrorHandler.


52-56: LGTM!

The test method is correctly testing the scenario where the type of the @ResourceId annotation does not match the type of the parameter. It verifies that the expected exception is thrown and handled by the GlobalErrorHandler.


58-62: LGTM!

The test method is correctly testing the scenario where the endpoint does not have a resource ID and the request does not have a token. It verifies that the AuthorizationService.audit method is called with the expected arguments.


64-68: LGTM!

The test method is correctly testing the scenario where the endpoint does not have a resource ID and the request has a token in the Authorization header. It verifies that the AuthorizationService.audit method is called with the expected arguments.


70-74: LGTM!

The test method is correctly testing the scenario where the endpoint does not have a resource ID and the request has a token in the authorization header. It verifies that the AuthorizationService.audit method is called with the expected arguments.


76-80: LGTM!

The test method is correctly testing the scenario where the endpoint has a resource ID and the request has a token in the authorization header. It verifies that the AuthorizationService.audit method is called with the expected arguments.


82-86: LGTM!

The test method is correctly testing the scenario where the endpoint has the @NoAuth annotation. It verifies that the AuthorizationService.audit method is not called.


88-92: LGTM!

The test method is correctly testing the scenario where the endpoint is a Spring MVC controller and has a resource ID and the request has a token in the authorization header. It verifies that the AuthorizationService.audit method is called with the expected arguments.

src/main/kotlin/org/rucca/cheese/auth/AuthorizationService.kt (1)

1-112: Overall, the AuthorizationService class is well-structured and provides the necessary functionality for authorization.

  • The class has a clear purpose and is well-organized.
  • The audit method performs a series of checks to determine if an operation is permitted based on the provided authorization and resource details.
  • The verify method handles the verification of JWT tokens and returns the corresponding Authorization object.
  • The class uses appropriate dependencies and follows good practices such as using constants and logging.
Tools
detekt

[warning] 93-93: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 95-95: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

pom.xml (4)

116-121: LGTM!

Adding the springmockk dependency is a good practice for improving the testing of Spring components. The version 4.0.2 is a stable release, and the test scope is appropriate as this library is only needed for testing purposes.


122-126: LGTM!

Adding the bcrypt dependency is a good choice for secure password hashing. The version 0.10.2 is a stable release, and the dependency should be used in the main application code for password hashing.


127-131: LGTM!

Adding the jersey-client dependency suggests that the application will interact with external RESTful services. The version 3.1.8 is a recent stable release, and the dependency should be used in the main application code for making HTTP requests.


132-136: LGTM!

Adding the java-jwt dependency indicates that the application will use JSON Web Tokens (JWTs) for authentication and authorization. The version 4.4.0 is a stable release, and the dependency should be used in the main application code for generating and validating JWTs.

Comment on lines +1 to +3
#!/bin/sh
sudo systemctl start docker
sudo docker restart elasticsearch postgres cheese_legacy
Copy link

Choose a reason for hiding this comment

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

Improve the script's security, error handling, logging, and usability.

The script has the following issues:

  1. Using sudo in scripts can be a security risk as it grants elevated privileges. It's better to use a dedicated user with limited permissions to run docker commands.
  2. The script is not checking for errors after running each command. If any command fails, the script will continue executing, which can lead to unexpected behavior.
  3. The script is not logging any information about the restart process, which can make it difficult to debug issues.
  4. The script is not checking if the docker daemon or the containers are already running before restarting them. Restarting a running container can cause downtime.
  5. The script is not providing any usage instructions or help text.

Consider applying the following changes to improve the script:

  1. Create a dedicated user with limited permissions to run docker commands and use that user instead of sudo.
  2. Add error handling after each command to check if it succeeded. If a command fails, log an error message and exit the script with a non-zero status code.
  3. Add logging statements to record the start and end time of the script, and the status of each command.
  4. Check if the docker daemon and the containers are already running before restarting them. If they are running, log a message indicating that they are already running and skip the restart.
  5. Add a help text at the beginning of the script to provide usage instructions and explain what the script does.

Here's an example of how the script could be improved:

#!/bin/sh

set -e

log() {
  echo "[$(date)] $1"
}

usage() {
  echo "Usage: $0"
  echo "Restarts the docker daemon and the specified docker containers."
  echo "Requires docker to be installed and the current user to have permissions to run docker commands."
}

if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
  usage
  exit 0
fi

log "Checking if docker is running..."
if ! docker info > /dev/null 2>&1; then
  log "Docker is not running, starting it now..."
  sudo systemctl start docker
else
  log "Docker is already running, skipping start."
fi

for container in elasticsearch postgres cheese_legacy; do
  log "Checking if $container is running..."
  if ! docker inspect --format="{{.State.Running}}" $container 2>/dev/null | grep -q true; then
    log "$container is not running, restarting it now..."
    docker restart $container
  else
    log "$container is already running, skipping restart."
  fi
done

log "Done."

This script includes the following improvements:

  1. It uses set -e to exit immediately if any command fails.
  2. It defines a log function to log messages with a timestamp.
  3. It defines a usage function to display help text when the script is called with -h or --help.
  4. It checks if the docker daemon is running before starting it, and skips the start if it's already running.
  5. It checks if each container is running before restarting it, and skips the restart if it's already running.
  6. It logs messages indicating the status of each command.

Note that this script assumes that the current user has permissions to run docker commands without sudo. If that's not the case, you'll need to create a dedicated user with limited permissions to run docker commands and use that user instead.

@@ -8,3 +8,6 @@ spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate

application.legacy-url=http://localhost:3000
application.jwt-secret=test-secret
Copy link

Choose a reason for hiding this comment

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

Use a strong, randomly generated secret key in production environments.

The application.jwt-secret property is used to configure the secret key for JWT signing or verification, which is critical for securing API endpoints and managing user authentication. However, the current value test-secret is a weak secret and should never be used in production environments.

It's crucial to use a strong, randomly generated secret key in production environments to ensure the security of JWTs. Please update the value to a strong, randomly generated secret key before deploying to production.

Do you want me to generate a strong secret key or open a GitHub issue to track this task?

Comment on lines +16 to +21
@Test
fun createUserAndLogin() {
val response = userCreatorService.createUser()
val token = userCreatorService.login(response.username, response.password)
logger.info("Token: $token")
}
Copy link

Choose a reason for hiding this comment

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

Add assertions and remove sensitive information logging.

The test method is missing assertions to validate the behavior of UserCreatorService. Add assertions to check:

  • The response from createUser() contains the expected data.
  • The token from login() is not null or empty.

Also, remove the logging of the token at line 20 as it could be a security risk to log sensitive information.

Comment on lines +11 to +19
fun getAuthorizationForUserWithRole(
userId: IdType,
role: String,
): Authorization {
return when (role) {
"standard-user" -> getAuthorizationForStandardUser(userId)
else -> throw IllegalArgumentException("Role '$role' is not supported")
}
}
Copy link

Choose a reason for hiding this comment

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

Mask the role name in the exception message.

To avoid leaking potentially sensitive information about valid roles, consider masking the role name in the exception message.

Apply this diff to mask the role name:

-            else -> throw IllegalArgumentException("Role '$role' is not supported")
+            else -> throw IllegalArgumentException("Unsupported role")
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
fun getAuthorizationForUserWithRole(
userId: IdType,
role: String,
): Authorization {
return when (role) {
"standard-user" -> getAuthorizationForStandardUser(userId)
else -> throw IllegalArgumentException("Role '$role' is not supported")
}
}
fun getAuthorizationForUserWithRole(
userId: IdType,
role: String,
): Authorization {
return when (role) {
"standard-user" -> getAuthorizationForStandardUser(userId)
else -> throw IllegalArgumentException("Unsupported role")
}
}

Comment on lines +16 to +39
class CustomAuthLogics {
private val logics: MutableMap<String, CustomAuthLogicHandler> = mutableMapOf()

fun register(
name: String,
handler: CustomAuthLogicHandler,
) {
if (logics.containsKey(name)) throw RuntimeException("Custom auth logic '$name' already exists.")
logics[name] = handler
}

fun invoke(
name: String,
userId: IdType,
action: AuthorizedAction,
resourceType: String,
resourceId: IdType?,
resourceOwnerIdGetter: IdGetter?,
customLogicData: Any?,
): Boolean {
val handler = logics[name] ?: throw RuntimeException("Custom auth logic '$name' not found.")
return handler(userId, action, resourceType, resourceId, resourceOwnerIdGetter, customLogicData)
}
}
Copy link

Choose a reason for hiding this comment

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

Consider making the class a singleton.

The CustomAuthLogics class provides a clean and effective way to manage custom authorization logic handlers. The register and invoke methods are implemented correctly, with appropriate error handling.

To ensure that only one instance of CustomAuthLogics is used throughout the application, consider making it a singleton. This can be achieved by making the constructor private and providing a static getInstance method that returns the single instance of the class.

Here's how you can refactor the class to make it a singleton:

class CustomAuthLogics private constructor() {
    private val logics: MutableMap<String, CustomAuthLogicHandler> = mutableMapOf()

    companion object {
        private val instance = CustomAuthLogics()

        fun getInstance(): CustomAuthLogics {
            return instance
        }
    }

    // rest of the class remains the same
}

With this change, you can obtain the single instance of CustomAuthLogics by calling CustomAuthLogics.getInstance() instead of creating a new instance each time.

Comment on lines +37 to +61
fun audit(
userId: IdType,
action: AuthorizedAction,
resourceType: String,
resourceId: IdType?,
resourceOwnerIdGetter: IdGetter?,
customLogicData: Any?,
): Boolean {
val role =
(customLogicData as? Map<*, *>)?.get("role") as? String
?: throw RuntimeException(
"Role not found in customLogicData. This is ether a bug or a malicious attack.")
val authorization = rolePermissionService.getAuthorizationForUserWithRole(userId, role)
try {
authorizationService.audit(
authorization,
action,
resourceType,
resourceId,
)
return true
} catch (e: PermissionDeniedError) {
return false
}
}
Copy link

Choose a reason for hiding this comment

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

Use a safer approach for role extraction and log swallowed exceptions.

  1. The extraction of the user's role from the customLogicData parameter is error-prone and can lead to a RuntimeException if the role is not found. Consider using a safer approach, such as:
val role = customLogicData?.let { it as? Map<*, *> }?.get("role") as? String
if (role == null) {
    // Log the error and return false or throw a more specific exception
    return false
}

This way, if the role is not found, you can log the error and return false or throw a more specific exception instead of crashing the application.

  1. The caught PermissionDeniedError exception is swallowed, potentially losing the original exception. It's important to log the exception before returning false to help diagnose issues. Update the catch block to log the exception:
} catch (e: PermissionDeniedError) {
    log.error("Permission denied for user $userId", e)
    return false
}
Tools
detekt

[warning] 58-58: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Comment on lines +1 to +64
#!/bin/sh
sudo systemctl start docker.service

sudo docker network create cheese_network

sudo docker run -d \
--name elasticsearch \
--network cheese_network \
-e discovery.type=single-node \
-e xpack.security.enabled=true \
-e ELASTIC_USERNAME=elastic \
-e ELASTIC_PASSWORD=elastic \
--health-cmd="curl http://localhost:9200/_cluster/health" \
--health-interval=10s \
--health-timeout=5s \
--health-retries=10 \
-p 9200:9200 \
docker.elastic.co/elasticsearch/elasticsearch:8.12.1

sudo docker run -d \
--name postgres \
--network cheese_network \
-e POSTGRES_PASSWORD=postgres \
--health-cmd="pg_isready" \
--health-interval=10s \
--health-timeout=5s \
--health-retries=5 \
-p 5432:5432 \
postgres
echo "Wait for 5 seconds please..."
sleep 5
sudo docker exec -i postgres bash << EOF
sed -i -e 's/max_connections = 100/max_connections = 1000/' /var/lib/postgresql/data/postgresql.conf
sed -i -e 's/shared_buffers = 128MB/shared_buffers = 2GB/' /var/lib/postgresql/data/postgresql.conf
EOF
sudo docker restart --time 0 postgres

sudo docker pull ghcr.io/sageseekersociety/cheese-backend-dev:dev # Update local image
sudo docker run -d \
--name cheese_legacy \
--network cheese_network \
-p 3000:3000 \
-e PORT=3000 \
-e JWT_SECRET="test-secret" \
-e PRISMA_DATABASE_URL="postgresql://postgres:postgres@postgres:5432/postgres?schema=public&connection_limit=16" \
-e ELASTICSEARCH_NODE=http://elasticsearch:9200/ \
-e ELASTICSEARCH_AUTH_USERNAME=elastic \
-e ELASTICSEARCH_AUTH_PASSWORD=elastic \
-e FILE_UPLOAD_PATH=/app/uploads \
-e DEFAULT_AVATAR_NAME=default.jpg \
-e EMAIL_SMTP_HOST=smtp.example.com \
-e EMAIL_SMTP_PORT=587 \
-e EMAIL_SMTP_SSL_ENABLE=true \
-e [email protected] \
-e EMAIL_SMTP_PASSWORD=a_super_strong_password \
-e EMAIL_DEFAULT_FROM="No Reply <[email protected]>" \
ghcr.io/sageseekersociety/cheese-backend-dev:dev \
bash -c '
if [ ! -f "FLAG_INIT" ]; then
touch FLAG_INIT
pnpm prisma db push
fi
pnpm start
'
Copy link

Choose a reason for hiding this comment

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

Overall, the script is well-structured and follows a logical sequence of steps to set up the required dependencies using Docker. Here are a few suggestions for improvement:

  1. Consider using a custom PostgreSQL configuration file or environment variables to set the max_connections and shared_buffers parameters, instead of modifying the configuration directly inside the container. This will make the setup more maintainable and easier to update in the future.

  2. The flag file used to conditionally run database migrations is created inside the container, which means it will be lost if the container is removed or recreated. Consider using a volume mount or a separate script to manage the database migrations, so that the flag file persists across container restarts.

  3. The script uses hardcoded values for the Docker image tags and environment variables. Consider using variables or a configuration file to make the script more maintainable and flexible. This will allow you to easily update the versions or configurations without modifying the script itself.

Comment on lines +23 to +72
@Before(
"@within(org.springframework.web.bind.annotation.RestController) || " +
"@within(org.springframework.stereotype.Controller)")
fun checkAuthorization(joinPoint: JoinPoint) {
val method = (joinPoint.signature as MethodSignature).method
val guardAnnotation: Guard? = method.getAnnotation<Guard>(Guard::class.java)
val noAuthAnnotation: NoAuth? = method.getAnnotation<NoAuth>(NoAuth::class.java)
if (noAuthAnnotation == null) {
if (guardAnnotation == null)
throw NoGuardOrNoAuthAnnotationException(
joinPoint.target.javaClass.name,
joinPoint.signature.name,
)

var resourceId: IdType? = null
val parameterAnnotations = method.parameterAnnotations
for (i in parameterAnnotations.indices) {
for (annotation in parameterAnnotations[i]) {
if (annotation is ResourceId) {
if (resourceId != null) {
throw DuplicatedResourceIdAnnotationException(
joinPoint.target.javaClass.name,
joinPoint.signature.name,
)
}
val arg = joinPoint.args[i]
if (arg !is IdType) {
throw ResourceIdTypeMismatchException(
joinPoint.target.javaClass.name,
joinPoint.signature.name,
)
}
resourceId = arg
}
}
}

val token: String? =
(RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes)
.request
.getHeader("Authorization")

authorizationService.audit(
token,
guardAnnotation.action,
guardAnnotation.resourceType,
resourceId,
)
}
}
Copy link

Choose a reason for hiding this comment

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

Consider refactoring the checkAuthorization method for better readability and maintainability.

The checkAuthorization method correctly implements the authorization logic. However, consider the following suggestions to improve the code:

  1. Break down the method into smaller methods to enhance readability and maintainability. For example, you can extract the logic for checking the @ResourceId annotation into a separate method.
  2. Make the exception messages more descriptive to aid in debugging. Include details like the expected annotation or type in the message.
  3. Clarify if the AuthorizationService.audit method handles null values for resourceId. If not, consider adding a null check before calling the method.

Comment on lines +53 to +70
fun login(username: String, password: String): String {
val client = ClientBuilder.newClient()
val target = client.target(applicationConfig.legacyUrl).path("/users/auth/login")
val request =
"""
{
"username": "$username",
"password": "$password"
}
"""
val response = target.request().post(Entity.entity(request, MediaType.APPLICATION_JSON))
val result = JSONObject(response.readEntity(String::class.java))
try {
return result.getJSONObject("data").getString("accessToken")
} catch (e: Exception) {
throw RuntimeException("Failed to login: $result")
}
}
Copy link

Choose a reason for hiding this comment

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

Handle the swallowed exception in the login method.

The login method makes a REST API call to the legacy system for user login and handles exceptions by throwing a RuntimeException with the error details. However, the caught exception is swallowed, which could lead to loss of the original exception.

To handle the swallowed exception, apply this diff:

         try {
             return result.getJSONObject("data").getString("accessToken")
         } catch (e: Exception) {
-            throw RuntimeException("Failed to login: $result")
+            throw RuntimeException("Failed to login: $result", e)
         }

This will include the original exception as the cause of the thrown RuntimeException, preserving the stack trace and original exception details.

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
fun login(username: String, password: String): String {
val client = ClientBuilder.newClient()
val target = client.target(applicationConfig.legacyUrl).path("/users/auth/login")
val request =
"""
{
"username": "$username",
"password": "$password"
}
"""
val response = target.request().post(Entity.entity(request, MediaType.APPLICATION_JSON))
val result = JSONObject(response.readEntity(String::class.java))
try {
return result.getJSONObject("data").getString("accessToken")
} catch (e: Exception) {
throw RuntimeException("Failed to login: $result")
}
}
fun login(username: String, password: String): String {
val client = ClientBuilder.newClient()
val target = client.target(applicationConfig.legacyUrl).path("/users/auth/login")
val request =
"""
{
"username": "$username",
"password": "$password"
}
"""
val response = target.request().post(Entity.entity(request, MediaType.APPLICATION_JSON))
val result = JSONObject(response.readEntity(String::class.java))
try {
return result.getJSONObject("data").getString("accessToken")
} catch (e: Exception) {
throw RuntimeException("Failed to login: $result", e)
}
}
Tools
detekt

[warning] 67-67: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Comment on lines +93 to +97
} catch (e: TokenExpiredException) {
throw TokenExpiredError()
} catch (e: JWTVerificationException) {
throw InvalidTokenError()
} catch (e: JacksonException) {
Copy link

Choose a reason for hiding this comment

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

Address the swallowed exceptions to avoid losing the original exceptions.

The static analysis tool suggests that the caught exceptions JWTVerificationException and JacksonException are swallowed, which could lead to loss of the original exceptions.

Consider logging the caught exceptions or re-throwing them as custom exceptions to preserve the original exception details. For example:

} catch (e: JWTVerificationException) {
    logger.error("Invalid token", e)
    throw InvalidTokenError(e)
} catch (e: JacksonException) {
    logger.error("Error parsing token payload", e)
    throw RuntimeException("The token is valid, but the payload of the token is not a TokenPayload object. This is either a bug or a malicious attack.", e)
}

Also applies to: 95-97

Tools
detekt

[warning] 93-93: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 95-95: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

@Nictheboy Nictheboy added this pull request to the merge queue Sep 12, 2024
Merged via the queue into dev with commit 804cab0 Sep 12, 2024
4 checks passed
@Nictheboy Nictheboy deleted the feat-auth branch September 12, 2024 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants