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

Feature/ssh/show ssh fingerprints 2 #9958

Closed
wants to merge 7 commits into from

Conversation

SimonEntholzer
Copy link
Contributor

@SimonEntholzer SimonEntholzer commented Dec 6, 2024

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding and design guidelines.
  • Following the theming guidelines, I specified colors only in the theming variable files and checked that the changes look consistent in both the light and the dark theme.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I added authorities to all new routes and checked the course groups for displaying navigation elements (links, buttons).
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Changes affecting Programming Exercises

  • High priority: I tested all changes and their related features with all corresponding user types on a test server configured with the integrated lifecycle setup (LocalVC and LocalCI).
  • I tested all changes and their related features with all corresponding user types on a test server configured with Gitlab and Jenkins.

Motivation and Context

Description

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 2 Students
  • 1 Programming Exercise with Complaints enabled
  1. Log in to Artemis
  2. Navigate to Course Administration
  3. ...

Exam Mode Testing

Prerequisites:

  • 1 Instructor
  • 2 Students
  • 1 Exam with a Programming Exercise
  1. Log in to Artemis
  2. Participate in the exam as a student
  3. Make sure that the UI of the programming exercise in the exam mode stays unchanged. You can use the exam mode documentation as reference.
  4. ...

Testserver States

Note

These badges show the state of the test servers.
Green = Currently available, Red = Currently locked
Click on the badges to get to the test servers.







Review Progress

Performance Review

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.
  • I (as a reviewer) confirm that the server changes (in particular related to database calls) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Exam Mode Test

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Test Coverage

Screenshots

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new method for generating SHA-256 fingerprints for SSH keys.
    • Added a service to provide SSH fingerprints for the local virtual classroom context.
    • Implemented a REST controller to manage and retrieve SSH fingerprints.
    • Created a user settings component for displaying SSH fingerprints with responsive design.
  • Documentation

    • Enhanced user interface with new labels and explanations for SSH fingerprints in multiple languages.
  • Tests

    • Added integration tests for SSH fingerprints functionality.
    • Developed unit tests for the new SSH user settings fingerprints component and service.

@SimonEntholzer SimonEntholzer requested a review from a team as a code owner December 6, 2024 11:38
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) programming Pull requests that affect the corresponding module labels Dec 6, 2024
Copy link

coderabbitai bot commented Dec 6, 2024

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 pmd (7.7.0)
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/HashUtils.java

The following rules are missing or misspelled in your ruleset file category/vm/bestpractices.xml: BooleanInstantiation, DontImportJavaLang, DuplicateImports, EmptyFinallyBlock, EmptyIfStmt, EmptyInitializer, EmptyStatementBlock, EmptyStatementNotInLoop, EmptySwitchStatements, EmptySynchronizedBlock, EmptyTryBlock, EmptyWhileStmt, ExcessiveClassLength, ExcessiveMethodLength, ImportFromSamePackage, MissingBreakInSwitch, SimplifyBooleanAssertion. Please check your ruleset configuration.

src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/SshFingerprintsProviderService.java

The following rules are missing or misspelled in your ruleset file category/vm/bestpractices.xml: BooleanInstantiation, DontImportJavaLang, DuplicateImports, EmptyFinallyBlock, EmptyIfStmt, EmptyInitializer, EmptyStatementBlock, EmptyStatementNotInLoop, EmptySwitchStatements, EmptySynchronizedBlock, EmptyTryBlock, EmptyWhileStmt, ExcessiveClassLength, ExcessiveMethodLength, ImportFromSamePackage, MissingBreakInSwitch, SimplifyBooleanAssertion. Please check your ruleset configuration.

src/main/java/de/tum/cit/aet/artemis/programming/web/localvc/ssh/SshFingerprintsProviderResource.java

The following rules are missing or misspelled in your ruleset file category/vm/bestpractices.xml: BooleanInstantiation, DontImportJavaLang, DuplicateImports, EmptyFinallyBlock, EmptyIfStmt, EmptyInitializer, EmptyStatementBlock, EmptyStatementNotInLoop, EmptySwitchStatements, EmptySynchronizedBlock, EmptyTryBlock, EmptyWhileStmt, ExcessiveClassLength, ExcessiveMethodLength, ImportFromSamePackage, MissingBreakInSwitch, SimplifyBooleanAssertion. Please check your ruleset configuration.

  • 1 others

Walkthrough

The changes in this pull request introduce new functionalities for managing SSH fingerprints within the Artemis application. A new HashUtils method for generating SHA-256 fingerprints is added, alongside a new service (SshFingerprintsProviderService) to provide SSH fingerprints. A REST controller (SshFingerprintsProviderResource) is created to handle requests for these fingerprints. Additionally, a new Angular component and service are introduced to display and fetch SSH fingerprints in user settings. The UI is updated with new HTML and CSS, and corresponding internationalization entries are added for user guidance.

Changes

File Change Summary
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/HashUtils.java - Method added: public static String getSha256Fingerprint(PublicKey key)
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/SshFingerprintsProviderService.java - Class added: SshFingerprintsProviderService
- Method added: public Map<String, String> getSshFingerPrints()
- Constructor added: public SshFingerprintsProviderService(SshServer sshServer)
src/main/java/de/tum/cit/aet/artemis/programming/web/localvc/ssh/SshFingerprintsProviderResource.java - Class added: public class SshFingerprintsProviderResource
- Method added: public ResponseEntity<Map<String, String>> getSshFingerprints()
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.html - New HTML component for displaying SSH fingerprints with responsive layout and navigation options.
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.scss - New CSS classes added: .column, .left, .right for layout management.
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.ts - New Angular component: SshUserSettingsFingerprintsComponent with dependency on SshUserSettingsFingerprintsService.
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.service.ts - Class added: SshUserSettingsFingerprintsService
- Method added: public async getSshFingerprints(): Promise<{ [key: string]: string }>
src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html - Modifications to the layout and structure, including a new link for navigating to the fingerprints page.
src/main/webapp/app/shared/user-settings/user-settings.module.ts - Component added: SshUserSettingsFingerprintsComponent to UserSettingsModule.
src/main/webapp/app/shared/user-settings/user-settings.route.ts - New route added: path: 'ssh/fingerprints', component: SshUserSettingsFingerprintsComponent.
src/main/webapp/i18n/de/userSettings.json - New keys added under sshSettingsPage: "fingerprints", "sshFingerprints", "fingerprintsExplanation", "fingerprintsLearnMore".
src/main/webapp/i18n/en/userSettings.json - New keys added under sshSettingsPage: "fingerprints", "sshFingerprints", "fingerprintsExplanation", "fingerprintsLearnMore".
src/test/java/de/tum/cit/aet/artemis/programming/icl/SshFingerprintsProviderIntegrationTest.java - Class added: SshFingerprintsProviderIntegrationTest with methods for testing SSH fingerprints API.
src/test/javascript/spec/component/account/ssh/ssh-user-settings-fingerprints.component.spec.ts - New test suite for SshUserSettingsFingerprintsComponent with a test case for displaying fingerprints.
src/test/javascript/spec/service/ssh-user-settings-fingerprints.service.spec.ts - New test suite for SshUserSettingsFingerprintsService focusing on HTTP request functionality.
src/test/javascript/spec/component/account/ssh/ssh-user-settings-key-details.component.spec.ts - Updated import paths for test dependencies.
src/test/javascript/spec/component/account/ssh/ssh-user-settings.component.spec.ts - Updated import paths for test dependencies.

Possibly related PRs

Suggested labels

tests, client, server, ready to merge, maintainer-approved

Suggested reviewers

  • Hialus
  • JohannesStoehr
  • sarpsahinalp
  • EneaGore
  • coolchock
  • pzdr7
  • BBesrour
  • DominikRemo
  • krusche

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai 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 or @coderabbitai title anywhere in the PR title to generate the title automatically.

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.

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 (12)
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/HashUtils.java (1)

Line range hint 8-8: Prevent instantiation of utility class

Add a private constructor to prevent instantiation of this utility class.

 public class HashUtils {
+    private HashUtils() {
+        throw new IllegalStateException("Utility class");
+    }

     public static String getSha512Fingerprint(PublicKey key) {
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.ts (1)

14-14: Consider initializing sshFingerprints as empty object

Initialize sshFingerprints as an empty object to avoid potential undefined checks in the template.

-    protected sshFingerprints?: { [key: string]: string };
+    protected sshFingerprints: { [key: string]: string } = {};
src/main/java/de/tum/cit/aet/artemis/programming/web/localvc/ssh/SshFingerprintsProviderResource.java (3)

19-21: Improve JavaDoc documentation

The current JavaDoc comment is incomplete. It should describe what this controller manages.

 /**
- * REST controller for managing.
+ * REST controller for managing SSH fingerprint operations.
  */

27-31: Add final modifier to service field

The service field should be marked as final since it's initialized through constructor injection and never changed.

-    SshFingerprintsProviderService sshFingerprintsProviderService;
+    private final SshFingerprintsProviderService sshFingerprintsProviderService;

33-43: Enhance endpoint documentation and consider caching

The endpoint documentation should include response status codes and consider caching for performance optimization.

     /**
      * GET /ssh-fingerprints
      *
      * @return the SSH fingerprints for the keys a user uses
+     * @response 200 Successfully retrieved SSH fingerprints
+     * @response 400 Failed to load SSH keys
      */
     @GetMapping(value = "ssh-fingerprints", produces = MediaType.APPLICATION_JSON_VALUE)
     @EnforceAtLeastStudent
     @FeatureToggle(Feature.Exports)
+    @ResponseStatus(HttpStatus.OK)
+    @Cacheable(value = "ssh-fingerprints", condition = "#result != null")
     public ResponseEntity<Map<String, String>> getSshFingerprints() {
src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/SshFingerprintsProviderService.java (1)

26-32: Consider adding monitoring metrics

For better operational visibility, consider adding metrics to track SSH key loading performance and errors.

+    private final MeterRegistry meterRegistry;
     private final SshServer sshServer;
 
-    public SshFingerprintsProviderService(SshServer sshServer) {
+    public SshFingerprintsProviderService(SshServer sshServer, MeterRegistry meterRegistry) {
         this.sshServer = sshServer;
+        this.meterRegistry = meterRegistry;
+        // Register metrics
+        meterRegistry.gauge("ssh.keys.total", fingerprints.size());
     }
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.html (3)

37-37: Fix inconsistent styling

Some rows use the 'small-text' class while others don't. Maintain consistent styling across all fingerprint rows.

-            <div class="row">
+            <div class="row small-text">

Also applies to: 48-48


3-67: Add loading and error states

The template should handle loading and error states to improve user experience.

 <div class="list-group d-block">
+    @if (loading) {
+        <div class="list-group-item">
+            <div class="d-flex justify-content-center">
+                <div class="spinner-border" role="status">
+                    <span class="visually-hidden">Loading...</span>
+                </div>
+            </div>
+        </div>
+    }
+    @if (error) {
+        <div class="list-group-item text-danger">
+            <span jhiTranslate="artemisApp.userSettings.sshSettingsPage.loadError"></span>
+        </div>
+    }
     <!-- Rest of the template -->

15-21: Improve accessibility

Add ARIA labels to improve accessibility for screen readers.

-                <div class="column left">
+                <div class="column left" role="rowheader" aria-label="SSH key type">
-                <div class="column right">
+                <div class="column right" role="cell" aria-label="SSH fingerprint value">

Also applies to: 27-32, 38-43, 49-54

src/test/java/de/tum/cit/aet/artemis/programming/icl/SshFingerprintsProviderIntegrationTest.java (1)

40-42: Consider future-proofing the RSA key size

While 2048 bits is currently secure, consider using 4096 bits for better future-proofing. This aligns with NIST recommendations for longer-term security.

-        keyPairGenerator.initialize(2048);
+        keyPairGenerator.initialize(4096);
src/main/webapp/app/shared/user-settings/user-settings.route.ts (1)

64-70: Consider using a more specific page title for fingerprints view

The current page title reuses the generic SSH settings title. Consider using a more specific title for better user navigation and accessibility.

-                    pageTitle: 'artemisApp.userSettings.categories.SSH_SETTINGS',
+                    pageTitle: 'artemisApp.userSettings.categories.SSH_FINGERPRINTS',
src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html (1)

Line range hint 30-36: Add OS-specific instructions for SSH key usage

Based on previous feedback, we should include instructions for different operating systems to improve user experience.

                 <div class="d-flex flex-wrap">
                     <p>
                         <span class="mt-4 font-medium" jhiTranslate="artemisApp.userSettings.sshSettingsPage.whatToUseSSHForInfo"> </span>
                         <jhi-documentation-link [documentationType]="documentationType" [displayString]="'artemisApp.userSettings.sshSettingsPage.learnMore'">
                         </jhi-documentation-link>
+                        <div class="mt-2">
+                            <h5 jhiTranslate="artemisApp.userSettings.sshSettingsPage.osInstructions.title"></h5>
+                            <ul>
+                                <li jhiTranslate="artemisApp.userSettings.sshSettingsPage.osInstructions.linux"></li>
+                                <li jhiTranslate="artemisApp.userSettings.sshSettingsPage.osInstructions.macos"></li>
+                                <li jhiTranslate="artemisApp.userSettings.sshSettingsPage.osInstructions.windows"></li>
+                            </ul>
+                        </div>
                     </p>
                 </div>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8036d43 and d35c826.

📒 Files selected for processing (17)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/HashUtils.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/SshFingerprintsProviderService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/programming/web/localvc/ssh/SshFingerprintsProviderResource.java (1 hunks)
  • src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.html (1 hunks)
  • src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.scss (1 hunks)
  • src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.ts (1 hunks)
  • src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.service.ts (1 hunks)
  • src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html (2 hunks)
  • src/main/webapp/app/shared/user-settings/user-settings.module.ts (2 hunks)
  • src/main/webapp/app/shared/user-settings/user-settings.route.ts (2 hunks)
  • src/main/webapp/i18n/de/userSettings.json (1 hunks)
  • src/main/webapp/i18n/en/userSettings.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/programming/icl/SshFingerprintsProviderIntegrationTest.java (1 hunks)
  • src/test/javascript/spec/component/account/ssh/ssh-user-settings-fingerprints.component.spec.ts (1 hunks)
  • src/test/javascript/spec/component/account/ssh/ssh-user-settings-key-details.component.spec.ts (1 hunks)
  • src/test/javascript/spec/component/account/ssh/ssh-user-settings.component.spec.ts (1 hunks)
  • src/test/javascript/spec/service/ssh-user-settings-fingerprints.service.spec.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/test/javascript/spec/component/account/ssh/ssh-user-settings.component.spec.ts
  • src/test/javascript/spec/component/account/ssh/ssh-user-settings-key-details.component.spec.ts
  • src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.scss
🧰 Additional context used
📓 Path-based instructions (13)
src/main/webapp/app/shared/user-settings/user-settings.module.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/user-settings/user-settings.route.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/HashUtils.java (1)

Pattern src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

src/main/webapp/i18n/de/userSettings.json (1)

Pattern src/main/webapp/i18n/de/**/*.json: German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/dein" and never with "sie/ihr".

src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/java/de/tum/cit/aet/artemis/programming/web/localvc/ssh/SshFingerprintsProviderResource.java (1)

Pattern src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

src/test/javascript/spec/component/account/ssh/ssh-user-settings-fingerprints.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/test/java/de/tum/cit/aet/artemis/programming/icl/SshFingerprintsProviderIntegrationTest.java (1)

Pattern src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

src/test/javascript/spec/service/ssh-user-settings-fingerprints.service.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/ssh/SshFingerprintsProviderService.java (1)

Pattern src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

📓 Learnings (1)
src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html (1)
Learnt from: Hialus
PR: ls1intum/Artemis#9454
File: src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html:114-119
Timestamp: 2024-11-12T12:51:58.050Z
Learning: In `src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html`, when providing commands for copying SSH public keys, always include instructions for macOS and Windows users in addition to Linux.
🔇 Additional comments (5)
src/main/webapp/app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.service.ts (1)

1-3: LGTM: Proper imports and modern DI pattern

The service follows Angular's modern dependency injection pattern using the inject function and has clean imports.

src/main/webapp/app/shared/user-settings/user-settings.module.ts (1)

16-16: LGTM! Module changes follow Angular guidelines

The import and declaration of SshUserSettingsFingerprintsComponent are properly implemented following Angular's style guide.

Also applies to: 26-26

src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html (1)

106-110: LGTM! Clean implementation of fingerprints link

The fingerprints link is well-positioned and follows the existing styling patterns.

src/main/webapp/i18n/en/userSettings.json (1)

58-61: LGTM! Clear and well-structured translations.

The new SSH fingerprint-related translations are clear, informative, and maintain consistency with the existing translations. The explanatory text effectively communicates the purpose and importance of SSH fingerprints to users.

src/main/webapp/i18n/de/userSettings.json (1)

58-61: LGTM! Well-translated with correct informal tone.

The German translations are accurate, maintain consistency with the English version, and correctly use the informal "du" form as required by the coding guidelines. The technical explanations are clear and natural-sounding in German.


@Injectable({ providedIn: 'root' })
export class SshUserSettingsFingerprintsService {
error?: string;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling for HTTP requests

The service currently has an error property but doesn't utilize it. Consider implementing proper error handling for the HTTP request.

 public async getSshFingerprints(): Promise<{ [key: string]: string }> {
-    return await firstValueFrom(this.http.get<{ [key: string]: string }>('api/ssh-fingerprints'));
+    try {
+        return await firstValueFrom(this.http.get<{ [key: string]: string }>('api/ssh-fingerprints'));
+    } catch (err) {
+        this.error = err instanceof Error ? err.message : 'Failed to fetch SSH fingerprints';
+        throw err;
+    }
 }

Also applies to: 11-13

Comment on lines +14 to +16
public static String getSha256Fingerprint(PublicKey key) {
return KeyUtils.getFingerPrint(BuiltinDigests.sha256.create(), key);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation and documentation

The method should include parameter validation and proper JavaDoc documentation.

+    /**
+     * Generates a SHA-256 fingerprint for the given public key.
+     *
+     * @param key the public key to generate the fingerprint for
+     * @return the SHA-256 fingerprint as a string
+     * @throws IllegalArgumentException if the key is null
+     */
     public static String getSha256Fingerprint(PublicKey key) {
+        if (key == null) {
+            throw new IllegalArgumentException("Public key cannot be null");
+        }
         return KeyUtils.getFingerPrint(BuiltinDigests.sha256.create(), key);
     }
📝 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
public static String getSha256Fingerprint(PublicKey key) {
return KeyUtils.getFingerPrint(BuiltinDigests.sha256.create(), key);
}
/**
* Generates a SHA-256 fingerprint for the given public key.
*
* @param key the public key to generate the fingerprint for
* @return the SHA-256 fingerprint as a string
* @throws IllegalArgumentException if the key is null
*/
public static String getSha256Fingerprint(PublicKey key) {
if (key == null) {
throw new IllegalArgumentException("Public key cannot be null");
}
return KeyUtils.getFingerPrint(BuiltinDigests.sha256.create(), key);
}

import { SshUserSettingsFingerprintsService } from 'app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.service';

@Component({
selector: 'jhi-account-information',
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update component selector to match component name

The selector jhi-account-information doesn't match the component's purpose. Consider updating it to jhi-ssh-user-settings-fingerprints for consistency.

-    selector: 'jhi-account-information',
+    selector: 'jhi-ssh-user-settings-fingerprints',
📝 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
selector: 'jhi-account-information',
selector: 'jhi-ssh-user-settings-fingerprints',

Comment on lines +21 to +23
async ngOnInit() {
this.sshFingerprints = await this.sshUserSettingsService.getSshFingerprints();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add loading state and error handling

The component should handle loading states and potential errors from the service.

+    protected loading = false;
+    protected error?: string;
+
     async ngOnInit() {
+        this.loading = true;
+        try {
             this.sshFingerprints = await this.sshUserSettingsService.getSshFingerprints();
+        } catch (err) {
+            this.error = this.sshUserSettingsService.error;
+        } finally {
+            this.loading = false;
+        }
     }
📝 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
async ngOnInit() {
this.sshFingerprints = await this.sshUserSettingsService.getSshFingerprints();
}
protected loading = false;
protected error?: string;
async ngOnInit() {
this.loading = true;
try {
this.sshFingerprints = await this.sshUserSettingsService.getSshFingerprints();
} catch (err) {
this.error = this.sshUserSettingsService.error;
} finally {
this.loading = false;
}
}

Comment on lines +25 to +28
it('should get SSH fingerprints', fakeAsync(() => {
sshFingerprintsService.getSshFingerprints();
httpMock.expectOne({ method: 'GET', url: getUserUrl });
}));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage with response handling and error scenarios

The current test only verifies that the HTTP call is made but doesn't test:

  1. Successful response handling
  2. Error scenarios
  3. Response type verification

Consider expanding the test suite with these scenarios:

 it('should get SSH fingerprints', fakeAsync(() => {
+    const mockResponse = {
+        'ssh-rsa': 'SHA256:abc123...',
+        'ssh-ed25519': 'SHA256:xyz789...'
+    };
     sshFingerprintsService.getSshFingerprints();
-    httpMock.expectOne({ method: 'GET', url: getUserUrl });
+    const req = httpMock.expectOne({ method: 'GET', url: getUserUrl });
+    expect(req.request.responseType).toBe('json');
+    req.flush(mockResponse);
 }));
+
+it('should handle error response', fakeAsync(() => {
+    sshFingerprintsService.getSshFingerprints().subscribe({
+        error: (error) => {
+            expect(error.status).toBe(404);
+        }
+    });
+    const req = httpMock.expectOne({ method: 'GET', url: getUserUrl });
+    req.flush('Not Found', { status: 404, statusText: 'Not Found' });
+}));

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +34 to +41
public Map<String, String> getSshFingerPrints() {
Map<String, String> fingerprints = new HashMap<>();
KeyPairProvider keyPairProvider = sshServer.getKeyPairProvider();
if (keyPairProvider != null) {
try {
keyPairProvider.loadKeys(null).iterator()
.forEachRemaining(keyPair -> fingerprints.put(keyPair.getPublic().getAlgorithm(), HashUtils.getSha256Fingerprint(keyPair.getPublic())));

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null checks and validate fingerprint format

The current implementation lacks proper null checks and fingerprint format validation.

     public Map<String, String> getSshFingerPrints() {
         Map<String, String> fingerprints = new HashMap<>();
         KeyPairProvider keyPairProvider = sshServer.getKeyPairProvider();
-        if (keyPairProvider != null) {
+        if (keyPairProvider == null) {
+            log.warn("No KeyPairProvider configured in SSH server");
+            return fingerprints;
+        }
+
+        try {
+            var keys = keyPairProvider.loadKeys(null);
+            if (keys == null) {
+                log.warn("No SSH keys loaded from provider");
+                return fingerprints;
+            }
+
+            keys.iterator().forEachRemaining(keyPair -> {
+                if (keyPair != null && keyPair.getPublic() != null) {
+                    var fingerprint = HashUtils.getSha256Fingerprint(keyPair.getPublic());
+                    if (fingerprint != null && fingerprint.startsWith("SHA256:")) {
+                        fingerprints.put(keyPair.getPublic().getAlgorithm(), fingerprint);
+                    } else {
+                        log.warn("Invalid fingerprint format for key algorithm: {}", 
+                            keyPair.getPublic().getAlgorithm());
+                    }
+                }
+            });
📝 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
public Map<String, String> getSshFingerPrints() {
Map<String, String> fingerprints = new HashMap<>();
KeyPairProvider keyPairProvider = sshServer.getKeyPairProvider();
if (keyPairProvider != null) {
try {
keyPairProvider.loadKeys(null).iterator()
.forEachRemaining(keyPair -> fingerprints.put(keyPair.getPublic().getAlgorithm(), HashUtils.getSha256Fingerprint(keyPair.getPublic())));
public Map<String, String> getSshFingerPrints() {
Map<String, String> fingerprints = new HashMap<>();
KeyPairProvider keyPairProvider = sshServer.getKeyPairProvider();
if (keyPairProvider == null) {
log.warn("No KeyPairProvider configured in SSH server");
return fingerprints;
}
try {
var keys = keyPairProvider.loadKeys(null);
if (keys == null) {
log.warn("No SSH keys loaded from provider");
return fingerprints;
}
keys.iterator().forEachRemaining(keyPair -> {
if (keyPair != null && keyPair.getPublic() != null) {
var fingerprint = HashUtils.getSha256Fingerprint(keyPair.getPublic());
if (fingerprint != null && fingerprint.startsWith("SHA256:")) {
fingerprints.put(keyPair.getPublic().getAlgorithm(), fingerprint);
} else {
log.warn("Invalid fingerprint format for key algorithm: {}",
keyPair.getPublic().getAlgorithm());
}
}
});

Comment on lines +38 to +43
it('should display fingerprints', async () => {
await comp.ngOnInit();
await fixture.whenStable();

expect(fingerPintsServiceMock.getSshFingerprints).toHaveBeenCalled();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Expand test coverage

The current test only verifies that getSshFingerprints is called. Consider adding tests for:

  • Error handling scenarios
  • Empty/null fingerprints
  • Correct rendering of fingerprint values
  • Loading state handling
it('should handle error when fetching fingerprints', async () => {
    fingerPintsServiceMock.getSshFingerprints.mockRejectedValue(new Error('Failed to fetch'));
    await comp.ngOnInit();
    await fixture.whenStable();
    // Verify error handling
});

it('should handle empty fingerprints', async () => {
    fingerPintsServiceMock.getSshFingerprints.mockResolvedValue({});
    await comp.ngOnInit();
    await fixture.whenStable();
    // Verify UI handles empty state
});

it('should display all fingerprint types', async () => {
    await comp.ngOnInit();
    await fixture.whenStable();
    fixture.detectChanges();
    // Verify each fingerprint type is displayed correctly
});

Comment on lines +11 to +13
const mockFingerprints: { [key: string]: string } = {
RSA: 'SHA512:abcde123',
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance mock data coverage

The mock fingerprints object only includes RSA type. Consider adding other supported types (EdDSA, ECDSA, EC) to ensure comprehensive testing.

 const mockFingerprints: { [key: string]: string } = {
     RSA: 'SHA512:abcde123',
+    EdDSA: 'SHA512:eddsatest',
+    ECDSA: 'SHA512:ecdsatest',
+    EC: 'SHA512:ectest',
 };
📝 Committable suggestion

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

Suggested change
const mockFingerprints: { [key: string]: string } = {
RSA: 'SHA512:abcde123',
};
const mockFingerprints: { [key: string]: string } = {
RSA: 'SHA512:abcde123',
EdDSA: 'SHA512:eddsatest',
ECDSA: 'SHA512:ecdsatest',
EC: 'SHA512:ectest',
};

Comment on lines +14 to +23
@if (sshFingerprints && sshFingerprints['RSA']) {
<div class="row small-text">
<div class="column left">
{{ 'RSA' }}
</div>
<div class="column right">
{{ sshFingerprints['RSA'] }}
</div>
</div>
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor repeated fingerprint display blocks

The template contains duplicate code blocks for different SSH key types. Consider extracting this into a reusable component or using @for with an array of key types.

-        @if (sshFingerprints && sshFingerprints['RSA']) {
-            <div class="row small-text">
-                <div class="column left">
-                    {{ 'RSA' }}
-                </div>
-                <div class="column right">
-                    {{ sshFingerprints['RSA'] }}
-                </div>
-            </div>
-        }
-        // ... similar blocks for other types
+        @for (type of ['RSA', 'EdDSA', 'ECDSA', 'EC'] as type) {
+            @if (sshFingerprints && sshFingerprints[type]) {
+                <div class="row small-text">
+                    <div class="column left">
+                        {{ type === 'EdDSA' ? 'ED25519' : type }}
+                    </div>
+                    <div class="column right">
+                        {{ sshFingerprints[type] }}
+                    </div>
+                </div>
+            }
+        }

Also applies to: 25-34, 36-45, 47-56

Comment on lines +49 to +76
@Nested
class SshFingerprintsProvider {

@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void shouldReturnFingerprints() throws Exception {
var response = request.get("/api/ssh-fingerprints", HttpStatus.OK, Map.class);
assertThat(response.get("RSA")).isNotNull();
assertThat(response.get("RSA")).isEqualTo(expectedFingerprint);
}

@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void shouldReturnNoFingerprintsWithoutKeyProviderSetup() throws Exception {
doReturn(null).when(sshServer).getKeyPairProvider();

var response = request.get("/api/ssh-fingerprints", HttpStatus.OK, Map.class);
assertThat(response.isEmpty()).isTrue();
}

@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void shouldReturnBadRequestWhenLoadKeysThrowsException() throws Exception {
doThrow(new IOException("Test exception")).when(keyPairProvider).loadKeys(null);

request.get("/api/ssh-fingerprints", HttpStatus.BAD_REQUEST, Map.class);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add test case for unauthorized access

The test suite covers successful retrieval, missing key provider, and exception scenarios. Consider adding a test case for unauthorized access to ensure proper security constraints.

Add this test method:

@Test
void shouldReturnUnauthorizedWithoutUser() throws Exception {
    request.get("/api/ssh-fingerprints", HttpStatus.UNAUTHORIZED, Map.class);
}

@SimonEntholzer SimonEntholzer deleted the feature/ssh/show-ssh-fingerprints-2 branch December 6, 2024 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client Pull requests that update TypeScript code. (Added Automatically!) programming Pull requests that affect the corresponding module server Pull requests that update Java code. (Added Automatically!) tests
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

1 participant