Skip to content
Open

Dev #15

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/339Lr3BJ)
### How the tests work (and Docker requirement)

This project ships with an end‑to‑end CLI integration test suite that uses Testcontainers to spin up a temporary MySQL database.
Expand Down
Empty file modified mvnw
100644 → 100755
Empty file.
190 changes: 186 additions & 4 deletions src/main/java/com/example/Main.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.example;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.*;
import java.util.Arrays;
import java.util.Scanner;

public class Main {

Expand All @@ -27,12 +26,183 @@ public void run() {
}

try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) {
Scanner scanner = new Scanner(System.in);
boolean loggedIn = false;
while (!loggedIn) {
System.out.print("Username: ");
String username = scanner.nextLine();
System.out.print("Password: ");
String password = scanner.nextLine();

if (isValidLogin(connection,username,password)) {
loggedIn = true;
System.out.println("Logged in as " + username);
} else {
System.out.println("Invalid username or password");
System.out.println("0) Exit");
System.out.println("Press any other key to try again");

String choice = scanner.nextLine();
if (choice.equals("0")) {
return;
}
}
}
while (true) {
printMenu();
String option = scanner.nextLine();

switch (option) {
case "1":
listMoonMissions(connection);
break;
case "2":
System.out.print("Enter mission_id: ");
String idStr = scanner.nextLine();
getMoonMissionById(connection, idStr);
break;
case "3":
System.out.print("Enter year: ");
String yearStr = scanner.nextLine();
countMissionsByYear(connection, yearStr);
break;
case "4":
createAccount(connection, scanner);
break;
case "5":
updateAccountPassword(connection, scanner);
break;
case "6":
deleteAccount(connection, scanner);
break;
case "0":
return;
default:
System.out.println("Invalid option, try again.");
}

}
} catch (SQLException e) {
throw new RuntimeException(e);
}
//Todo: Starting point for your code

}

private void printMenu() {
System.out.println("\n--- Moon Mission Menu ---");
System.out.println("1. List moon missions");
System.out.println("2. Get moon mission by id");
System.out.println("3. Count missions by year");
System.out.println("4. Create an account");
System.out.println("5. Update an account password");
System.out.println("6. Delete an account");
System.out.println("0. Exit");
System.out.print("Choice: ");
}

private void listMoonMissions(Connection conn) throws SQLException {
String query = "SELECT * FROM moon_mission";
try (PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {

while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}

private void getMoonMissionById(Connection conn, String id) throws SQLException {
String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String dateStr = rs.getString("launch_date");
String year = (dateStr != null && dateStr.length() >= 4) ? dateStr.substring(0, 4) : "N/A";

System.out.println("Mission: " + rs.getString("spacecraft") +
", Year: " + year);
} else {
System.out.println("Mission not found.");
}
}
}
}

private void countMissionsByYear(Connection conn, String year) throws SQLException {
String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, year);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt(1);
System.out.println("Missions in " + year + ": " + count);
}
}
}
}
Comment on lines +103 to 143
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make mission queries deterministic + avoid SELECT *.
SELECT * and missing ORDER BY can make outputs flaky (and unnecessarily reads columns).

-        String query = "SELECT * FROM moon_mission";
+        String query = "SELECT spacecraft FROM moon_mission ORDER BY mission_id";
@@
-        String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
+        String query = "SELECT spacecraft, YEAR(launch_date) AS launch_year FROM moon_mission WHERE mission_id = ?";
@@
-                    String dateStr = rs.getString("launch_date");
-                    String year = (dateStr != null && dateStr.length() >= 4) ? dateStr.substring(0, 4) : "N/A";
-
-                    System.out.println("Mission: " + rs.getString("spacecraft") +
-                            ", Year: " + year);
+                    String year = rs.getString("launch_year");
+                    System.out.println("Mission: " + rs.getString("spacecraft") + ", Year: " + (year == null ? "N/A" : year));
@@
-        String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
+        String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
-            stmt.setString(1, year);
+            stmt.setInt(1, Integer.parseInt(year.trim()));

(If the tests feed non-numeric years, wrap the parseInt with a friendly error message instead of throwing.)

📝 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
private void listMoonMissions(Connection conn) throws SQLException {
String query = "SELECT * FROM moon_mission";
try (PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}
private void getMoonMissionById(Connection conn, String id) throws SQLException {
String query = "SELECT * FROM moon_mission WHERE mission_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String dateStr = rs.getString("launch_date");
String year = (dateStr != null && dateStr.length() >= 4) ? dateStr.substring(0, 4) : "N/A";
System.out.println("Mission: " + rs.getString("spacecraft") +
", Year: " + year);
} else {
System.out.println("Mission not found.");
}
}
}
}
private void countMissionsByYear(Connection conn, String year) throws SQLException {
String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, year);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt(1);
System.out.println("Missions in " + year + ": " + count);
}
}
}
}
private void listMoonMissions(Connection conn) throws SQLException {
String query = "SELECT spacecraft FROM moon_mission ORDER BY mission_id";
try (PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}
private void getMoonMissionById(Connection conn, String id) throws SQLException {
String query = "SELECT spacecraft, YEAR(launch_date) AS launch_year FROM moon_mission WHERE mission_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
String year = rs.getString("launch_year");
System.out.println("Mission: " + rs.getString("spacecraft") + ", Year: " + (year == null ? "N/A" : year));
} else {
System.out.println("Mission not found.");
}
}
}
}
private void countMissionsByYear(Connection conn, String year) throws SQLException {
String query = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setInt(1, Integer.parseInt(year.trim()));
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
int count = rs.getInt(1);
System.out.println("Missions in " + year + ": " + count);
}
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 103 to 143, replace the
three uses of "SELECT *" with explicit column lists (e.g., mission_id,
spacecraft, launch_date) and change listMoonMissions to include a deterministic
ORDER BY (e.g., ORDER BY mission_id or ORDER BY launch_date) so results are
stable; update result-set access to use those explicit column names instead of
relying on *; for countMissionsByYear change the query to accept an integer year
(e.g., "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?") but
validate/parse the input year first with a try/catch that prints a friendly
error if non-numeric, then call stmt.setInt(1, parsedYear); ensure all
PreparedStatements/ResultSets still use try-with-resources.


private void createAccount(Connection conn, Scanner sc) throws SQLException {
System.out.print("First name: ");
String first = sc.nextLine();
System.out.print("Last name: ");
String last = sc.nextLine();
System.out.print("SSN: ");
String ssn = sc.nextLine();
System.out.print("Password: ");
String pass = sc.nextLine();

String generatedName = "";
if (first.length() >= 3 && last.length() >= 3) {
generatedName = first.substring(0, 3) + last.substring(0, 3);
} else {
generatedName = first + last;
}
Comment on lines +155 to +160
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Simplify name generation to match init.sql pattern.

The conditional logic creates inconsistent usernames for short names. When either name is shorter than 3 characters, it concatenates the full names, which differs from the init.sql pattern that always takes up to 3 characters.

Apply this diff to ensure consistent behavior:

-        String generatedName = "";
-        if (first.length() >= 3 && last.length() >= 3) {
-            generatedName = first.substring(0, 3) + last.substring(0, 3);
-        } else {
-            generatedName = first + last;
-        }
+        String generatedName = first.substring(0, Math.min(3, first.length())) + 
+                               last.substring(0, Math.min(3, last.length()));

This ensures names like "Al Smith" generate "AlSmi" instead of "AlSmith".

📝 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
String generatedName = "";
if (first.length() >= 3 && last.length() >= 3) {
generatedName = first.substring(0, 3) + last.substring(0, 3);
} else {
generatedName = first + last;
}
String generatedName = first.substring(0, Math.min(3, first.length())) +
last.substring(0, Math.min(3, last.length()));
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 155 to 160, the conditional
currently concatenates full names when either part is shorter than 3 chars,
which diverges from init.sql’s pattern; replace the if/else with logic that
always takes up to the first three characters of each name (e.g., use
substring(0, Math.min(3, first.length())) and substring(0, Math.min(3,
last.length()))), concatenate those two parts, and assign to generatedName
(ensure you trim inputs and handle null/empty safely if needed).


String query = "INSERT INTO account (name, first_name, last_name, ssn, password) VALUES (?, ?, ?, ?, ?)";

try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, generatedName); // Sätter det genererade namnet
stmt.setString(2, first);
stmt.setString(3, last);
stmt.setString(4, ssn);
stmt.setString(5, pass);
stmt.executeUpdate();
System.out.println("Account created. Your username is: " + generatedName);
}
}

private void updateAccountPassword(Connection conn, Scanner sc) throws SQLException {
System.out.print("Enter user_id: ");
String id = sc.nextLine();
System.out.print("Enter new password: ");
String pass = sc.nextLine();

String query = "UPDATE account SET password = ? WHERE user_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, pass);
stmt.setString(2, id);
int rows = stmt.executeUpdate();
if (rows > 0) System.out.println("Password updated.");
else System.out.println("User not found.");
}
}
Comment on lines +175 to +189
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authorization check allows privilege escalation.

Any authenticated user can change anyone's password by providing any user_id. This is a severe security vulnerability.

At minimum, verify the logged-in user is modifying their own account:

     private void updateAccountPassword(Connection conn, Scanner sc) throws SQLException {
         System.out.print("Enter user_id: ");
         String id = sc.nextLine();
         System.out.print("Enter new password: ");
         String pass = sc.nextLine();
 
-        String query = "UPDATE account SET password = ? WHERE user_id = ?";
+        // TODO: Pass the authenticated username to this method and verify user_id matches
+        String query = "UPDATE account SET password = ? WHERE user_id = ? AND name = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
             stmt.setString(1, pass);
             stmt.setString(2, id);
+            stmt.setString(3, authenticatedUsername); // Verify ownership
             int rows = stmt.executeUpdate();

Alternatively, remove the user_id prompt entirely and derive it from the authenticated session.

Additionally, user_id is likely an integer—use setInt(2, Integer.parseInt(id)) instead of setString.

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

🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 175 to 189, the
updateAccountPassword method allows any authenticated user to change any account
by accepting a user_id input and using it directly; to fix this, remove or
ignore the user_id input and derive the target user_id from the authenticated
session (or at minimum validate the supplied id equals the currently
authenticated user's id) before performing the update, and change the
PreparedStatement parameter for user_id to use setInt with Integer.parseInt(id)
(with proper NumberFormatException handling) so the type matches the DB; also
add an authorization failure message and return early when the session user does
not match the target.

private void deleteAccount(Connection conn, Scanner sc) throws SQLException {
System.out.print("Enter user_id: ");
String id = sc.nextLine();

String query = "DELETE FROM account WHERE user_id = ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, id);
int rows = stmt.executeUpdate();
if (rows > 0) System.out.println("Account deleted.");
else System.out.println("User not found.");
}
}
Comment on lines +190 to +201
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authorization check and confirmation for destructive operation.

This method has two critical issues:

  1. Any authenticated user can delete any account by providing the user_id (privilege escalation).
  2. No confirmation prompt for this destructive operation.

Add authorization and confirmation:

     private void deleteAccount(Connection conn, Scanner sc) throws SQLException {
         System.out.print("Enter user_id: ");
         String id = sc.nextLine();
+        
+        System.out.print("Are you sure you want to delete this account? (yes/no): ");
+        String confirm = sc.nextLine();
+        if (!confirm.equalsIgnoreCase("yes")) {
+            System.out.println("Delete cancelled.");
+            return;
+        }
 
-        String query = "DELETE FROM account WHERE user_id = ?";
+        // TODO: Pass authenticated username and verify ownership
+        String query = "DELETE FROM account WHERE user_id = ? AND name = ?";
         try (PreparedStatement stmt = conn.prepareStatement(query)) {
-            stmt.setString(1, id);
+            stmt.setInt(1, Integer.parseInt(id));
+            stmt.setString(2, authenticatedUsername); // Verify ownership
             int rows = stmt.executeUpdate();

Consider whether account deletion should even be self-service, or if it should require admin privileges.

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





/**
* Determines if the application is running in development mode based on system properties,
* environment variables, or command-line arguments.
Expand All @@ -59,4 +229,16 @@ private static String resolveConfig(String propertyKey, String envKey) {
}
return (v == null || v.trim().isEmpty()) ? null : v.trim();
}
private boolean isValidLogin(Connection conn, String user, String pass) throws SQLException {
String query = "SELECT * FROM account WHERE name = ? AND password = ?";

try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, user);
stmt.setString(2, pass);

try (ResultSet rs = stmt.executeQuery()) {
return rs.next();
}
}
}
}
Loading