Skip to content
Open

Dev #15

Show file tree
Hide file tree
Changes from 7 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.
181 changes: 177 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,174 @@ 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
Copy Markdown

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 query = "INSERT INTO account (first_name, last_name, ssn, password) VALUES (?, ?, ?, ?)";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, first);
stmt.setString(2, last);
stmt.setString(3, ssn);
stmt.setString(4, pass);
stmt.executeUpdate();
System.out.println("Account created.");
}
}

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
Copy Markdown

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 thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +190 to +201
Copy link
Copy Markdown

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 +220,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