PROJECT: ExpenseTracker


Overview

This portfolio aims to document the contributions made by Jason Chong Yi Sheng to the ExpenseTracker.

ExpenseTracker is a desktop expense tracking application developed to aid users in tracking their expenses and saving money. The user interacts with it using a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX library. It is written in Java, and has about 28 kLoC. The product was morphed from an Address Book over a period of 8 weeks under the constraints described here.

Some of its main features are: Setting a budget for expenses, data security, categorization and statistics for expenses.

Summary of contributions

  • Major enhancement: added Data Security

    • What it does: Allows multiple users to use the application on the same machine without being able to view or affect each other’s data without their passwords. Also keeps each user’s data file encrypted and inaccessible by others.

    • Justification: This feature improves the product significantly because multiple users can share the application on the same computer. For many people, financial information is often kept private so this function allows them to use the application without fear of other’s having unauthorized access to their spending data.

    • The following are the highlights of this feature:

      • This enhancement adds a robust user accounts system that comes with several features, namely signup, login and setPassword. Each user’s data file is kept separately and explicitly labelled with their username. The implementation too was challenging as it required changes to how data is loaded in the Storage component and stored in the Model component.

      • The encryption algorithm also makes use of hash algorithms and the AES symmetric key encryption algorithm to ensure that no one can gain access to a user’s information unless he/she knows the plain text password of the user, which is not stored anywhere.

    • Credits: Google Guava library for common hashing algorithms.

  • Code contributed: [Code collated by RepoSense]

  • Other contributions:

    • Project management:

      • Managed releases v1.1 - v1.4 (5 releases) on GitHub

    • Community:

      • PRs reviewed (with non-trivial review comments): #25, #30, #35, #39, #70, #82)

      • Attempts to help other batch mates on forums (examples: 1, 2)

      • Reported bugs and suggestions for other teams in the class (examples: 1 , 2 , 3 , 4 , 5 , 6 , 7 )

    • Tools:

      • Integrated the following Github plugins to the team repo: TravisCI, Appveyor, Codacy, Coveralls

      • Added Reposense configuration file to allow for collation of teammates' code contributions

Contributions to the User Guide

Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

Features

  • Before you are logged in, only login, signup and help commands are useable.

  • After you are logged in, the rest of the commands will become useable.

These are the commands available to use in Expense Tracker:

Signing up a user : signup

Creates a new user in Expense Tracker. You can log in to your newly created user after signing up.

Format: signup USERNAME

Examples:

  • signup username123
    Signs up a user with the username "username123".

  • USERNAME must be 1 to 250 characters long

  • USERNAME cannot contain white spaces or any of the following characters : > < : \ / | ?

Logging in as an existing user : login

Logs in to Expense Tracker as an existing user and expands the user interface to show Expense Tracker if it is not already showing.

Format: login u/USERNAME [p/PASSWORD]

Examples:

  • login u/username123
    Logs in a user with the username "username123".

  • login u/username1234 p/password1
    Logs in a user with the username "username1234" with password "password1".

  • USERNAME is case insensitive

  • PASSWORD is case sensitive

  • PASSWORD cannot contain any spaces and must be 6 to 100 characters long

  • Expense Tracker contains a sample user with USERNAME sample

  • If a user has no password set, then the login will be successful regardless of whether a PASSWORD has been provided

If the log in is successful (ie. the USERNAME and PASSWORD are correct and the user’s data has no issues), then the UI will be expanded as shown in the diagram below:

LogInExpectedOutcomeDiagram
Figure 1. Diagram showing log in success UI expansion for sample user

Setting a password for the current user : setPassword

Sets a new password for the user which is currently logged in.

Format: setPassword n/NEW_PASSWORD [o/OLD_PASSWORD]

Examples:

  • setPassword n/pass123
    Sets the current user’s password as "pass123", provided that there is no previously set password

  • setPassword n/pass123 o/password1
    Sets the current user’s password as "pass123", provided that the current password is "password1"

  • OLD_PASSWORD is needed if a password has been previously set for the current user and they have to match.

  • OLD_PASSWORD and NEW_PASSWORD are case sensitive

  • OLD_PASSWORD and NEW_PASSWORD cannot contain any spaces and must be 6 to 100 characters long

I have also contributed documentation for the encrypt and decrypt commands but they have been omitted from this portfolio as the above commands are sufficient to showcase my ability to write documentation targeting end-users. You can view this section in the User Guide if you wish to.

Contributions to the Developer Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

Data Security

The Expense Tracker ensures the security of users' data through the user accounts system and data encryption.

The user accounts system allows multiple users to use Expense Tracker on the same computer without interfering with each other’s data. It also includes an optional password system that allows users to protect their Expense Tracker information from being viewed or altered by others.

The encryption system ensures all expense data (excluding budget) is encrypted within the xml storage files.

Current Implementation

On initialization, MainApp class loads all xml files within the data folder according to the path in UserPrefs. The data is loaded by MainApp#initModelManager(Storage, UserPref).

The username value will be forced to match the name of the xml data filename (ignoring file extentions).

This feature is facilitated by 9 newly added methods in the Model interface. One of them is as follows:

Model#setPassword(Password, String) — Changes the Password of the user that is currently logged in. Requires the new password as a Password object and as a plain text String.

The list of these methods along with their descriptions have been omitted in this portfolio and can be found in the Developer Guide.

When implementing methods in ModelManager that requires a user to already be logged in, one can use ModelManager#requireUserSelected(), which throws a checked NoUserSelectedException if there is no logged in user. I.e your method should look like this:

New method example
@Override
public void methodName() throws NoUserSelectedException {
    requireUserSelected();
    // Rest of the method body...
}

The classes Username and Password have also been implemented and have the following noteworthy characteristics:

  • Two Username classes are equivalent if and only if the internal username String are equivalent (case-insensitive).

  • Username cannot be constructed with a String containing a white space or any of the following characters: " > < : \ / | ? *

  • When a Password class is constructed with plain text, the password is hashed using SHA-256 before being stored as an internal String in the Password object

  • Password is only valid if the plain text form is at least 6 characters long

Utility methods related to data encryption are implemented in the EncryptionUtil class. They include methods to encrypt/decrypt String, Expense and ExpenseTracker and to generate a 128-bit encryption key from a plain text password String. More details can be found in the Developer Guide.

Encrypted versions of the ExpenseTracker and most of the classes it contains were implemented. These classes have their class names prepended with Encrypted and are shown in the class diagram below:

EncryptionClassDiagram
Figure 2. Class diagram for Enrypted classes

The following are other noteworthy details of the implementation for data encryption:

  • Users' expense data are encrypted using AES encryption with a 128-bit MurmurHash of their plain text password as the encryption key. These are not stored anywhere in the data files to ensure the security of their data.

  • The encrypted information is stored in new classes to ensure that encrypted data is not used before decryption.

  • The encrypted information has to be stored in Model as the encryption key will only be known at runtime when a user logs in with his/her correct Password.

  • Each Encrypted class will know how to decrypt itself into its decrypted equivalent. e.g EncryptedExpenseField#decrypt(String) uses the input String as an encryption key to decrypt itself into a ExpenseField.

Below is an example usage scenario and how the User Account System behaves at each step when the application is launched.

  1. The user launches the application and the directory path in the UserPref points at the data folder

  2. The method StorageManager#readAllExpenses(Path) is called by the MainApp and the method loads all the xml data files in the data folder and returns the loaded data as a Map<Username, EncryptedExpensetracker> with the Username of the user data as the key and the user data as an EncryptedExpenseTracker as the value to the MainApp class.

  3. A Model instance will then be initialized using the previously mentioned Map of user data.

Below is the UML sequence diagram of the StorageManager#readAllExpenses(Path) method mentioned.

ReadAllExpensesSequenceDiagram
Figure 3. Sequence diagram of the StorageManager#readAllExpenses(Path) method

Below is an example usage scenario and how the Sign Up and Login system behaves at each step after the application is launched.

  1. The user executes the command signup john to create a user with the Username john

  2. The signup command calls Model#addUser(Username) which adds the user john to Model. The operation is successful as john does not break any of the Username constraints and does not already exist in the Model.

  3. The user then executes the command login u/john to log in to his user account

  4. The login command calls the LoginCredentials(Username, String) constructor with a null String password as a password was not provided.

  5. The login command then calls Model#loadUserData(LoginCredentials) with the LoginCredentials instance created in the previous step. The method is executed successfully as the user john has no password set.

  6. john’s data that is stored as EncryptedExpenseTracker is decrypted using the EncryptedExpenseTracker#decryptTracker(String) using an encryption key generated from john’s password (In this case an empty String is used as the password since john’s account has no password).

  7. The selected data in Model is switched to john’s and an UserLoggedInEvent is raised for UI to show john’s Expense Tracker data

The sequence diagram for SignUpCommand has been omitted as it is largely similar to subsequent sequence diagrams in this section. You can view it in the Developer Guide if you wish to.

Below is the UML sequence diagram that shows how LoginCommand works.

LoginCommandSequenceDiagram
Figure 4. Sequence diagram showing how LoginCommand works

Below is an example usage scenario and how the Password system behaves at each step after the he/she is logged in.

  1. The user is already logged in to the account john with an existing password password1 and executes the command setPassword o/password1 n/password2 to change his password to password2

  2. The setPassword command calls the Model#setPassword(Password) method since the given old password matches his existing password and password2 does not violate any password constraints

  3. The Model#setPassword(Password) method changes john’s account password to password2

  4. john’s expense data gets encrypted using a new encryption key generated from the String password2. This also applies in future whenever it is saved to the data file.

Below is the UML sequence diagram that shows how SetPasswordCommand works.

SetPasswordCommandSequenceDiagram
Figure 5. Sequence diagram showing how SetPasswordCommand works

Design Considerations

Aspect: Loading of User Data
  • Alternative 1 (current choice): Loading of User data is only done on initialization of Expense Tracker

    • Pros: Ability to switch user accounts quickly after Expense Tracker is loaded as all users are already loaded into memory

    • Cons: External changes to the data files after initialization will not be reflected may be overwritten

  • Alternative 2: User data is loaded only when the user attempts to log in

    • Pros: Unnecessary data is not kept in memory so memory space is not wasted

    • Cons: The Model or Logic component will have to depend on the Storage component as the login command will require the Storage to load and return the user’s data.

Another aspect, Storage of Separate User Data, has been omitted from this portfolio section as I’ve already demonstrated my ability to consider different design options. You can view it in the Developer Guide if you wish to.