Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Course Management System

Java Maven MySQL Tests Version

A role-based desktop course-management application for schools, colleges and training institutes. Built with Java 21, Swing and MySQL, it provides three separate portals — Administrator, Instructor and Student — over a shared set of academic records.

Version 2.0 is a security-driven rewrite. Authentication is hash-based, every query is parameterised, portals are role-scoped, and credentials live outside the repository. See What changed in 2.0.


Table of Contents


What changed in 2.0

The 1.x codebase was a functional coursework project with a set of vulnerabilities that made it unsafe for real student data. Each has been addressed:

Area 1.x 2.0
Login Username and password concatenated into SQL — ' OR '1'='1 signed you in as administrator Parameterised lookup; the password never reaches the SQL at all
Queries ~30 concatenated statements; a crafted delete value could wipe a table Every statement is a PreparedStatement with bound parameters
Passwords Stored and compared as plaintext PBKDF2-HMAC-SHA256, 210 000 iterations, random 16-byte salt per password
Authorization A successful login just opened a window; any portal could be constructed directly Session + Role guards enforced in the portal base constructor
Data scoping Students saw every student via SELECT * FROM student_info Results and modules are scoped to the signed-in user's own record
Credentials root with an empty password hardcoded in source External config via file, system properties or environment variables
Default logins Admin/Admin, Teacher/Teacher, student/student in source comments Nothing is seeded; the first administrator is created interactively
Password entry JTextField — typed in cleartext on screen JPasswordField, held as char[] and zeroed after use
Brute force Unlimited attempts Configurable lockout (default 5 attempts / 15 minutes)
JDBC driver Connector/J 8.0.27, affected by CVE-2022-21363 Connector/J 9.7.0, resolved by Maven
Connections New connection per action, never closed HikariCP pool, try-with-resources everywhere
Transport Unencrypted sslMode=REQUIRED, public-key retrieval disabled
Errors printStackTrace() leaking schema details Logged via SLF4J/Logback; users see plain messages
Layout Fixed pixel setBounds, broken on high-DPI Layout managers that scale with the system font
Table limits Fixed String[20][n] arrays, capped at 20 rows SimpleTableModel over an unbounded list, with sorting and search
Marks Five subjects out of a hardcoded 500 Unbounded subjects, per-subject maximums, configurable pass percentage
Transactions Two independent writes; failure left orphaned rows Exam and its marks written in one transaction

Three functional bugs were fixed along the way: Add Module always failed (a positional INSERT supplied 4 values to a 5-column table), Add Instructor threw a NullPointerException after a successful insert, and marks entry silently discarded the semester field.


Features

Administrator

Full CRUD across students, instructors, courses, modules, enrollments and results. Optionally provisions portal logins for students and instructors, linking each account to its person record.

Instructor

Sees the modules they teach and the students on their assigned course; records and reviews marks. Scoped to the instructor record linked to their account.

Student

Sees their own details, the modules on their course, their instructors' contact details, and their own results with a per-subject breakdown, totals and pass/fail outcome.


Architecture

Four layers, each depending only on the one beneath it:

┌─────────────────────────────────────────────────────────┐
│  ui/          Swing screens                             │
│               MainFrame → LoginDialog → PortalFrame     │
│               Role guard enforced in the base ctor      │
├─────────────────────────────────────────────────────────┤
│  security/    AuthService · PasswordHasher · Session    │
│  util/        Validation                                │
├─────────────────────────────────────────────────────────┤
│  repository/  PreparedStatement data access             │
│  model/       Immutable records                         │
├─────────────────────────────────────────────────────────┤
│  db/          Database (HikariCP pool + migration)      │
│  config/      AppConfig (layered configuration)         │
└─────────────────────────────────────────────────────────┘
                          │
                    MySQL 8.0 over TLS

AppContext wires the graph once at startup and hands collaborators to the UI. Screens never construct their own connection, which is precisely what let 1.x screens bypass authentication.

Authentication flow. LoginDialog collects a char[] password → AuthService looks the user up by (username, role) with bound parameters → verifies the PBKDF2 hash in Java → on success opens a Session; on failure increments a counter that locks the account after N attempts. Unknown usernames still perform a dummy hash comparison so response time does not reveal whether an account exists.


Tech Stack

Layer Technology
Language Java 21 (LTS)
GUI Java Swing
Database MySQL 8.0+
Driver MySQL Connector/J 9.7.0
Pooling HikariCP 7.1.0
Logging SLF4J 2.0.17 + Logback 1.5.18
Testing JUnit 5.11.4
Build Maven (wrapper included)

No JARs are vendored in the repository; Maven resolves everything, so a driver upgrade is a one-line change in pom.xml.


Project Structure

Course-Management-System/
├── mvnw, mvnw.cmd, .mvn/          # Maven wrapper — no local Maven needed
├── pom.xml
└── src/
    ├── main/
    │   ├── java/com/cms/
    │   │   ├── CourseManagementApp.java   # entry point
    │   │   ├── AppContext.java            # dependency wiring
    │   │   ├── config/                    # AppConfig, ConfigurationException
    │   │   ├── db/                        # Database (pool + migration)
    │   │   ├── security/                  # AuthService, PasswordHasher, Session, Role
    │   │   ├── model/                     # Student, Instructor, Course, CourseModule, ExamResult…
    │   │   ├── repository/                # one per aggregate, all parameterised
    │   │   ├── util/                      # Validation
    │   │   └── ui/
    │   │       ├── MainFrame, LoginDialog, FirstRunDialog, PortalFrame
    │   │       ├── TablePanel, SimpleTableModel, FormBuilder, Dialogs, UiTheme
    │   │       ├── admin/                 # AdminPortalFrame + entity form dialogs
    │   │       ├── instructor/            # InstructorPortalFrame
    │   │       └── student/               # StudentPortalFrame
    │   └── resources/
    │       ├── application.properties     # defaults, no credentials
    │       ├── logback.xml
    │       └── db/schema.sql              # applied automatically at startup
    └── test/java/com/cms/                 # unit tests + gated integration tests

Quick Start

Prerequisites

  • JDK 21+java -version
  • MySQL 8.0+ reachable and running

Maven is not required; the bundled wrapper downloads it.

1. Create the database and a least-privilege user

Do not point the application at root.

CREATE DATABASE msystem CHARACTER SET utf8mb4;
CREATE USER 'cms'@'localhost' IDENTIFIED BY 'a-strong-password-here';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, REFERENCES ON msystem.* TO 'cms'@'localhost';
FLUSH PRIVILEGES;

CREATE, INDEX and REFERENCES are only needed for the automatic migration on first run. Revoke them afterwards, or set app.db.auto-migrate=false and apply schema.sql manually.

2. Supply credentials

Create config/application.properties next to where you run the app — this path is gitignored:

app.db.url=jdbc:mysql://localhost:3306/msystem?sslMode=REQUIRED&allowPublicKeyRetrieval=false&serverTimezone=UTC
app.db.username=cms
app.db.password=a-strong-password-here

Environment variables work too, and take precedence:

export APP_DB_USERNAME=cms
export APP_DB_PASSWORD='a-strong-password-here'

3. Build and run

# Linux / macOS
./mvnw clean package
java -jar target/course-management-system.jar
# Windows
.\mvnw.cmd clean package
java -jar target\course-management-system.jar

The build produces a self-contained runnable jar. Tables are created automatically on first start.

4. Create the first administrator

There are no default credentials. On an empty database, choosing Administrator opens a one-time setup dialog to create the first account (minimum 12 characters, stored only as a PBKDF2 hash). Further accounts are created from the admin portal, which can also issue logins to students and instructors.

A login only becomes useful once an administrator links it to a person record via Students → Edit or Instructors → Edit. Until then the portal explains that the account is unlinked.


Configuration

Sources are layered; later ones win:

  1. packaged application.properties defaults
  2. ./config/application.properties, or the file named by CMS_CONFIG_FILE
  3. JVM system properties — -Dapp.db.password=…
  4. environment variables — APP_DB_PASSWORD (dots and dashes become underscores, uppercased)
Key Default Purpose
app.db.url jdbc:mysql://localhost:3306/msystem?sslMode=REQUIRED… JDBC URL
app.db.username (required) Database user
app.db.password (empty) Database password
app.db.pool.max-size 8 Maximum pooled connections
app.db.auto-migrate true Apply schema.sql at startup
app.security.max-failed-attempts 5 Failures before lockout
app.security.lockout-minutes 15 Lockout duration
app.security.pbkdf2-iterations 210000 KDF cost; raising it re-hashes on next login
app.exam.pass-percentage 50 Pass threshold

sslMode=REQUIRED encrypts the connection and works with MySQL's self-signed certificate. Use VERIFY_CA with a truststore against a real CA. allowPublicKeyRetrieval stays false deliberately — enabling it lets a MITM substitute an RSA key during caching_sha2_password authentication.


Database

Eight tables with real foreign keys, unique constraints and check constraints — see schema.sql.

users ──┬── students ──┬── enrollments ── courses
        │              └── exams ── exam_marks
        └── instructors ── modules ── courses

Two structural changes over 1.x are worth calling out:

  • Marks are normalised. exam_marks holds one row per subject instead of subject1..subject5 / marks1..marks5 columns, so a semester can have any number of subjects, each with its own maximum.
  • Identity is by surrogate key. Deletes target id, not a concatenated name. In 1.x, entering ' OR '1'='1 into a delete box emptied the table.

Testing

./mvnw test

38 unit tests covering password hashing, validation and result arithmetic. They need no database.

A further 14 integration tests exercise the security properties against a live MySQL instance. They are skipped unless a database is supplied:

./mvnw test \
  -Dapp.db.url="jdbc:mysql://127.0.0.1:3306/msystem_test?sslMode=REQUIRED" \
  -Dapp.db.username=cms -Dapp.db.password=…

These write to the configured schema. Point them at a throwaway database.

They assert that seven classic injection payloads never authenticate in either the username or password field, that roles cannot cross portals, that lockout fires on the configured attempt and not earlier, that payloads stored in data fields round-trip as literal text, and that a failed marks write leaves no partial rows.

Verified in this state: the full clean package build, all 52 tests against MySQL 8.0.40 over TLS, automatic schema creation, and both startup failure paths (missing configuration and unreachable database). The Swing screens themselves have not been exercised by a human on a real display — they compile, and their data access is covered by the tests, but the interaction has not been manually walked through.


Security

Posture

  • No secrets in the repository. The only credential ever committed was a local root/empty-password dev default, now removed. config/application.properties and *.env are gitignored.
  • Injection. Every statement is parameterised. User input reaches the database only as a bound value.
  • Passwords. PBKDF2-HMAC-SHA256, 210 000 iterations, per-password 16-byte salt, constant-time comparison. The iteration count is stored with each hash and upgraded transparently on the next successful login. Plaintext is held in char[] and zeroed.
  • Authorization. PortalFrame calls Session.requireRole before the window is built, so a portal cannot be opened without the matching role.
  • Least privilege. The app expects a dedicated database user, never root.
  • Transport. TLS required; public-key retrieval disabled.

Reporting

Found something? Open a private security advisory on the repository rather than a public issue.


Deployment Checklist

  • Dedicated database user, root not used
  • CREATE/INDEX/REFERENCES revoked after first run, or auto-migrate disabled
  • Credentials supplied via environment or an out-of-tree file — never committed
  • sslMode=VERIFY_CA with a proper certificate
  • First administrator created with a strong, unique password
  • Every login linked to a student or instructor record
  • logs/ rotated and access-controlled — it records usernames and login outcomes
  • Database backups scheduled and a restore rehearsed

Upgrading from 1.x

The 2.0 schema is not backward compatible and 1.x data does not migrate automatically.

1.x stored passwords in plaintext and joined records by free-text name; 2.0 uses hashed credentials and surrogate keys. There is no lossless automated path, and rehashing plaintext passwords would preserve credentials that should be considered compromised.

Recommended path:

  1. Stand up 2.0 against a fresh msystem database.
  2. Export the 1.x tables and import the reference data — courses, students, instructors — mapping names to the new foreign keys.
  3. Issue new credentials to everyone. Treat every 1.x password as compromised: they were stored in plaintext and the login screen was bypassable.

The 1.x sources remain in git history at tag/commit b2b31e1 if you need them for reference.


Roadmap

  • Self-service password change and administrator-initiated reset
  • Flyway or Liquibase for versioned migrations
  • Audit log of privileged actions
  • CSV/PDF export of results and transcripts
  • Attendance tracking
  • Coverage reporting in CI

Contributing

  1. Fork and branch — git checkout -b feature/your-feature
  2. Keep ./mvnw test green and add tests for new behaviour
  3. All data access goes through a repository using PreparedStatement; never concatenate user input into SQL
  4. Commit, push, and open a Pull Request

License

No license file is included, so all rights are reserved by default. Others cannot legally reuse this code. To make it open source, add a LICENSE file.


Acknowledgements

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages