From d972e15c83800bfa5db73ef8141ed12a998a7b1d Mon Sep 17 00:00:00 2001 From: Jin Xintian <jinxin.tian@student.unimelb.edu.au> Date: Fri, 6 Sep 2019 19:20:43 +1000 Subject: [PATCH] SUBMIT --- .../passbook/AlreadyLoggedInException.java | 9 + .../passbook/DuplicateUserException.java | 9 + .../IncorrectPassphraseException.java | 9 + .../passbook/InvalidSessionIDException.java | 9 + .../passbook/NoSuchURLException.java | 11 + .../passbook/NoSuchUserException.java | 9 + .../mutant-1/swen90006/passbook/Pair.java | 31 +++ .../mutant-1/swen90006/passbook/PassBook.java | 262 ++++++++++++++++++ .../passbook/WeakPassphraseException.java | 14 + .../passbook/AlreadyLoggedInException.java | 9 + .../passbook/DuplicateUserException.java | 9 + .../IncorrectPassphraseException.java | 9 + .../passbook/InvalidSessionIDException.java | 9 + .../passbook/NoSuchURLException.java | 11 + .../passbook/NoSuchUserException.java | 9 + .../mutant-2/swen90006/passbook/Pair.java | 31 +++ .../mutant-2/swen90006/passbook/PassBook.java | 262 ++++++++++++++++++ .../passbook/WeakPassphraseException.java | 14 + .../passbook/AlreadyLoggedInException.java | 9 + .../passbook/DuplicateUserException.java | 9 + .../IncorrectPassphraseException.java | 9 + .../passbook/InvalidSessionIDException.java | 9 + .../passbook/NoSuchURLException.java | 11 + .../passbook/NoSuchUserException.java | 9 + .../mutant-3 /swen90006/passbook/Pair.java | 31 +++ .../swen90006/passbook/PassBook.java | 262 ++++++++++++++++++ .../passbook/WeakPassphraseException.java | 14 + .../passbook/AlreadyLoggedInException.java | 9 + .../passbook/DuplicateUserException.java | 9 + .../IncorrectPassphraseException.java | 9 + .../passbook/InvalidSessionIDException.java | 9 + .../passbook/NoSuchURLException.java | 11 + .../passbook/NoSuchUserException.java | 9 + .../mutant-4/swen90006/passbook/Pair.java | 31 +++ .../mutant-4/swen90006/passbook/PassBook.java | 262 ++++++++++++++++++ .../passbook/WeakPassphraseException.java | 14 + .../passbook/AlreadyLoggedInException.java | 9 + .../passbook/DuplicateUserException.java | 9 + .../IncorrectPassphraseException.java | 9 + .../passbook/InvalidSessionIDException.java | 9 + .../passbook/NoSuchURLException.java | 11 + .../passbook/NoSuchUserException.java | 9 + .../mutant-5/swen90006/passbook/Pair.java | 31 +++ .../mutant-5/swen90006/passbook/PassBook.java | 262 ++++++++++++++++++ .../passbook/WeakPassphraseException.java | 14 + 45 files changed, 1815 insertions(+) create mode 100644 programs/mutant-1/swen90006/passbook/AlreadyLoggedInException.java create mode 100644 programs/mutant-1/swen90006/passbook/DuplicateUserException.java create mode 100644 programs/mutant-1/swen90006/passbook/IncorrectPassphraseException.java create mode 100644 programs/mutant-1/swen90006/passbook/InvalidSessionIDException.java create mode 100644 programs/mutant-1/swen90006/passbook/NoSuchURLException.java create mode 100644 programs/mutant-1/swen90006/passbook/NoSuchUserException.java create mode 100644 programs/mutant-1/swen90006/passbook/Pair.java create mode 100644 programs/mutant-1/swen90006/passbook/PassBook.java create mode 100644 programs/mutant-1/swen90006/passbook/WeakPassphraseException.java create mode 100644 programs/mutant-2/swen90006/passbook/AlreadyLoggedInException.java create mode 100644 programs/mutant-2/swen90006/passbook/DuplicateUserException.java create mode 100644 programs/mutant-2/swen90006/passbook/IncorrectPassphraseException.java create mode 100644 programs/mutant-2/swen90006/passbook/InvalidSessionIDException.java create mode 100644 programs/mutant-2/swen90006/passbook/NoSuchURLException.java create mode 100644 programs/mutant-2/swen90006/passbook/NoSuchUserException.java create mode 100644 programs/mutant-2/swen90006/passbook/Pair.java create mode 100644 programs/mutant-2/swen90006/passbook/PassBook.java create mode 100644 programs/mutant-2/swen90006/passbook/WeakPassphraseException.java create mode 100644 programs/mutant-3 /swen90006/passbook/AlreadyLoggedInException.java create mode 100644 programs/mutant-3 /swen90006/passbook/DuplicateUserException.java create mode 100644 programs/mutant-3 /swen90006/passbook/IncorrectPassphraseException.java create mode 100644 programs/mutant-3 /swen90006/passbook/InvalidSessionIDException.java create mode 100644 programs/mutant-3 /swen90006/passbook/NoSuchURLException.java create mode 100644 programs/mutant-3 /swen90006/passbook/NoSuchUserException.java create mode 100644 programs/mutant-3 /swen90006/passbook/Pair.java create mode 100644 programs/mutant-3 /swen90006/passbook/PassBook.java create mode 100644 programs/mutant-3 /swen90006/passbook/WeakPassphraseException.java create mode 100644 programs/mutant-4/swen90006/passbook/AlreadyLoggedInException.java create mode 100644 programs/mutant-4/swen90006/passbook/DuplicateUserException.java create mode 100644 programs/mutant-4/swen90006/passbook/IncorrectPassphraseException.java create mode 100644 programs/mutant-4/swen90006/passbook/InvalidSessionIDException.java create mode 100644 programs/mutant-4/swen90006/passbook/NoSuchURLException.java create mode 100644 programs/mutant-4/swen90006/passbook/NoSuchUserException.java create mode 100644 programs/mutant-4/swen90006/passbook/Pair.java create mode 100644 programs/mutant-4/swen90006/passbook/PassBook.java create mode 100644 programs/mutant-4/swen90006/passbook/WeakPassphraseException.java create mode 100644 programs/mutant-5/swen90006/passbook/AlreadyLoggedInException.java create mode 100644 programs/mutant-5/swen90006/passbook/DuplicateUserException.java create mode 100644 programs/mutant-5/swen90006/passbook/IncorrectPassphraseException.java create mode 100644 programs/mutant-5/swen90006/passbook/InvalidSessionIDException.java create mode 100644 programs/mutant-5/swen90006/passbook/NoSuchURLException.java create mode 100644 programs/mutant-5/swen90006/passbook/NoSuchUserException.java create mode 100644 programs/mutant-5/swen90006/passbook/Pair.java create mode 100644 programs/mutant-5/swen90006/passbook/PassBook.java create mode 100644 programs/mutant-5/swen90006/passbook/WeakPassphraseException.java diff --git a/programs/mutant-1/swen90006/passbook/AlreadyLoggedInException.java b/programs/mutant-1/swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000..8bbbd85 --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/AlreadyLoggedInException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class AlreadyLoggedInException extends Exception +{ + public AlreadyLoggedInException(String username) + { + super("Username already logged in: " + username); + } +} diff --git a/programs/mutant-1/swen90006/passbook/DuplicateUserException.java b/programs/mutant-1/swen90006/passbook/DuplicateUserException.java new file mode 100644 index 0000000..74370b1 --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/DuplicateUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class DuplicateUserException extends Exception +{ + public DuplicateUserException(String username) + { + super("Username already exists: " + username); + } +} diff --git a/programs/mutant-1/swen90006/passbook/IncorrectPassphraseException.java b/programs/mutant-1/swen90006/passbook/IncorrectPassphraseException.java new file mode 100644 index 0000000..cdfc80b --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/IncorrectPassphraseException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class IncorrectPassphraseException extends Exception +{ + public IncorrectPassphraseException(String username, String passphrase) + { + super("Incorrect passphrase: " + passphrase + " for user " + username); + } +} diff --git a/programs/mutant-1/swen90006/passbook/InvalidSessionIDException.java b/programs/mutant-1/swen90006/passbook/InvalidSessionIDException.java new file mode 100644 index 0000000..230cf58 --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/InvalidSessionIDException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class InvalidSessionIDException extends Exception +{ + public InvalidSessionIDException(Integer sessionID) + { + super("Invalid session ID: " + sessionID); + } +} diff --git a/programs/mutant-1/swen90006/passbook/NoSuchURLException.java b/programs/mutant-1/swen90006/passbook/NoSuchURLException.java new file mode 100644 index 0000000..7edb916 --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/NoSuchURLException.java @@ -0,0 +1,11 @@ +package swen90006.passbook; + +import java.net.URL; + +public class NoSuchURLException extends Exception +{ + public NoSuchURLException (String username, URL url) + { + super("User " + username + " does not have password for URL " + url.toString()); + } +} diff --git a/programs/mutant-1/swen90006/passbook/NoSuchUserException.java b/programs/mutant-1/swen90006/passbook/NoSuchUserException.java new file mode 100644 index 0000000..ca4c527 --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/NoSuchUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class NoSuchUserException extends Exception +{ + public NoSuchUserException (String username) + { + super("Username does not exist: " + username); + } +} diff --git a/programs/mutant-1/swen90006/passbook/Pair.java b/programs/mutant-1/swen90006/passbook/Pair.java new file mode 100644 index 0000000..8f70502 --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/Pair.java @@ -0,0 +1,31 @@ +package swen90006.passbook; + +/** + * A pair of objects. + */ +public class Pair<X, Y> +{ + private X first; + private Y second; + + public Pair(X first, Y second) + { + this.first = first; + this.second = second; + } + + public X getFirst() + { + return this.first; + } + + public Y getSecond() + { + return this.second; + } + + public boolean equals(Pair<X, Y> other) + { + return first.equals(other.first) && second.equals(other.second); + } +} diff --git a/programs/mutant-1/swen90006/passbook/PassBook.java b/programs/mutant-1/swen90006/passbook/PassBook.java new file mode 100644 index 0000000..94e2dfd --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/PassBook.java @@ -0,0 +1,262 @@ +package swen90006.passbook; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Random; +import java.util.Arrays; + +/** + * PassBook is a (fictional) online password manager. A password + * manager is a software application that generates, stores, and + * retrieves login details for users. + * + * A user has an account with PassBook. This account is protected by a + * master password, or passphrase. Each user can store login details + * for multiple websites, identified by their URL. A user can add a + * login details for a given website. The passphrase is used to + * encrypt all passwords, but for this implementation, we have ignored + * encryption. + * + * The PassBook passphrase must conform to the following requirements: + * Must be at least eight characters long and contain at + * least one digit (range 0-9), one letter in the range 'a'-'z', and + * one letter in the range 'A'-'Z'. + * + * Username and passwords for stored URLs have no password + * requirements. + * + * When a user logs into PassBook, they are given a session ID. This + * session ID is used to identify them for all transactions until they + * log out. + */ +public class PassBook +{ + /** The minimum length of a passphrase */ + public final static int MINIMUM_PASSPHRASE_LENGTH = 8; + + /** Valid URL protocols for the passbook */ + public final static String [] VALID_URL_PROTOCOLS = {"http", "https"}; + + //The passphrases (master passwords) for all users + private Map<String, String> passphrases; + + //The login details for all users and the URLs they have stored + private Map<String, PasswordTable> details; + + //Mapping from session IDs to usernames + private Map<Integer, String> userIDs; + + //Mapping from usernames to sessionIDs + private Map<String, Integer> sessionIDs; + + /** + * Constructs an empty passbook. + */ + public PassBook() + { + passphrases = new HashMap<String, String>(); + details = new HashMap<String, PasswordTable>(); + userIDs = new HashMap<Integer, String>(); + sessionIDs = new HashMap<String, Integer>(); + } + + /** + * Adds a new user to the passbook. + * + * @param passbookUsername the username for the user to be added + * @param passphrase the passphrase (master password) for the user + * @throws DuplicateUserException if the username is already in the passbook + * @throws WeakPassphraseException if the password does not fit the passphrase + rules (see class documentation) + * + * Assumption: passbookUsername and passphrase are non-null + */ + public void addUser(String passbookUsername, String passphrase) + throws DuplicateUserException, WeakPassphraseException + { + //Check if this user exists + if (passphrases.containsKey(passbookUsername)) { + throw new DuplicateUserException(passbookUsername); + } + //check the passphrase strength + else { + if (passphrase.length() < MINIMUM_PASSPHRASE_LENGTH) { + throw new WeakPassphraseException(passphrase); + } + + boolean containsLowerCase = false; + boolean containsUpperCase = false; + boolean containsNumber = false; + for (int i = 0; i < passphrase.length(); i++) { + + if ('a' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'z') { + containsLowerCase = true; + } + else if ('A' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'Z') { + containsUpperCase = true; + } + else if ('0' <= passphrase.charAt(i) && passphrase.charAt(i) <= '9') { + containsNumber = true; + } + } + + if (!containsLowerCase || !containsUpperCase || !containsNumber) { + throw new WeakPassphraseException(passphrase); + } + } + + passphrases.put(passbookUsername, passphrase); + PasswordTable pt = new PasswordTable(); + details.put(passbookUsername, pt); + } + + /** + * Checks if a user exists + * @param passbookUsername the passbookUsername + * @return true if and only if this user is in the passbook + * + * Assumption: passbookUsername is non-null + */ + public boolean isUser(String passbookUsername) + { + return passphrases.containsKey(passbookUsername); + } + + /** + * Logs a user into the passbook. + * + * @param passbookUsername the passbookUsername for the user + * @param passphrase the passphrase (master password) for the user + * @returns a session ID greater than or equal to 0 + * @throws NoSuchUserException if the user does not exist in the passbook + * @throws AlreadyLoggedInException if the user is already logged in + * @throws IncorrectPassphraseException if the passphrase is incorrect for this user + * + * Assumption: passbookUsername and passphrase are non-null + */ + public int loginUser(String passbookUsername, String passphrase) + throws NoSuchUserException, AlreadyLoggedInException, IncorrectPassphraseException + { + //check that the user exists + if (!passphrases.containsKey(passbookUsername)) { + throw new NoSuchUserException(passbookUsername); + } + else if (sessionIDs.get(passbookUsername) != null) { + throw new AlreadyLoggedInException(passbookUsername); + } + else if (!passphrases.get(passbookUsername).equals(passphrase)) { + throw new IncorrectPassphraseException(passbookUsername, passphrase); + } + + //generate a random session ID that is not already taken + int sessionID = new Random().nextInt(Integer.MAX_VALUE); + while (userIDs.containsKey(sessionID)) { + sessionID = new Random().nextInt(Integer.MAX_VALUE); + } + + //add the session ID + sessionIDs.put(passbookUsername, sessionID); + userIDs.put(sessionID, passbookUsername); + + return sessionID; + } + + /** + * Logs out a user based on session ID. Has no affect if the session ID does not exist. + * + * @param sessionID the session ID to be terminated + * + * Assumption: session ID is non-null + */ + public void logoutUser(Integer sessionID) + { + sessionIDs.remove(userIDs.get(sessionID)); + userIDs.remove(sessionID); + } + + /** + * Updates the login details for a URL of a user. If the url + * username or password are null, the login details for this URL + * must be removed. + * + * @param sessionID the session ID for the logged-in user + * @param urlUsername the username for the user to be added + * @param url the URL for the username/password pair + * @param urlPassword the password to be add + * @throws InvalidSessionIDException if the session ID does not exists + * @throws MalformedURLException if the protocol is not 'http' or 'https' + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public void updateDetails(Integer sessionID, URL url, String urlUsername, String urlPassword) + throws InvalidSessionIDException, MalformedURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + if (urlUsername == null || urlPassword == null) { + pt.remove(url); + } + else { + pt.put(url, new Pair<String, String> (urlUsername, urlPassword)); + details.put(passbookUsername, pt); + } + } + + + /** + * Retrieves login details for a given URL and user. + * + * @param sessionID the session ID + * @param url the URL for the password being retrieved. + * @returns the username and password for a given url for the corresponding user + * @throws NoSuchUserException if the username does not exist in the passbook + * @throws MalformedURLException if the syntax is invalid (see class documentation) + * @throws NoSuchURLException if user does not have login for this URL + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public Pair<String, String> retrieveDetails(Integer sessionID, URL url) + throws InvalidSessionIDException, MalformedURLException, NoSuchURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + //if this returned nothing, the user has no details for any url + if (pt == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + Pair<String, String> pair = pt.get(url); + + //if this returned nothing, the user does not have a login for this url + if (pair == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + return pair; + } + + //A simple label to improve code readability + private class PasswordTable extends HashMap<URL, Pair<String, String>> {} +} diff --git a/programs/mutant-1/swen90006/passbook/WeakPassphraseException.java b/programs/mutant-1/swen90006/passbook/WeakPassphraseException.java new file mode 100644 index 0000000..c4bc33b --- /dev/null +++ b/programs/mutant-1/swen90006/passbook/WeakPassphraseException.java @@ -0,0 +1,14 @@ +package swen90006.passbook; + +public class WeakPassphraseException extends Exception +{ + public WeakPassphraseException (String passphrase) + { + super("Passphrase does not comply with the PassBook rules\n" + + "\t- must contains at least " + + PassBook.MINIMUM_PASSPHRASE_LENGTH + " characters\n" + + "\t- must contain at least one numeric character\n" + + "\t- must contain at least one lower case letter\n" + + "\t- must contain at least one upper case letter\n"); + } +} diff --git a/programs/mutant-2/swen90006/passbook/AlreadyLoggedInException.java b/programs/mutant-2/swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000..8bbbd85 --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/AlreadyLoggedInException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class AlreadyLoggedInException extends Exception +{ + public AlreadyLoggedInException(String username) + { + super("Username already logged in: " + username); + } +} diff --git a/programs/mutant-2/swen90006/passbook/DuplicateUserException.java b/programs/mutant-2/swen90006/passbook/DuplicateUserException.java new file mode 100644 index 0000000..74370b1 --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/DuplicateUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class DuplicateUserException extends Exception +{ + public DuplicateUserException(String username) + { + super("Username already exists: " + username); + } +} diff --git a/programs/mutant-2/swen90006/passbook/IncorrectPassphraseException.java b/programs/mutant-2/swen90006/passbook/IncorrectPassphraseException.java new file mode 100644 index 0000000..cdfc80b --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/IncorrectPassphraseException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class IncorrectPassphraseException extends Exception +{ + public IncorrectPassphraseException(String username, String passphrase) + { + super("Incorrect passphrase: " + passphrase + " for user " + username); + } +} diff --git a/programs/mutant-2/swen90006/passbook/InvalidSessionIDException.java b/programs/mutant-2/swen90006/passbook/InvalidSessionIDException.java new file mode 100644 index 0000000..230cf58 --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/InvalidSessionIDException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class InvalidSessionIDException extends Exception +{ + public InvalidSessionIDException(Integer sessionID) + { + super("Invalid session ID: " + sessionID); + } +} diff --git a/programs/mutant-2/swen90006/passbook/NoSuchURLException.java b/programs/mutant-2/swen90006/passbook/NoSuchURLException.java new file mode 100644 index 0000000..7edb916 --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/NoSuchURLException.java @@ -0,0 +1,11 @@ +package swen90006.passbook; + +import java.net.URL; + +public class NoSuchURLException extends Exception +{ + public NoSuchURLException (String username, URL url) + { + super("User " + username + " does not have password for URL " + url.toString()); + } +} diff --git a/programs/mutant-2/swen90006/passbook/NoSuchUserException.java b/programs/mutant-2/swen90006/passbook/NoSuchUserException.java new file mode 100644 index 0000000..ca4c527 --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/NoSuchUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class NoSuchUserException extends Exception +{ + public NoSuchUserException (String username) + { + super("Username does not exist: " + username); + } +} diff --git a/programs/mutant-2/swen90006/passbook/Pair.java b/programs/mutant-2/swen90006/passbook/Pair.java new file mode 100644 index 0000000..8f70502 --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/Pair.java @@ -0,0 +1,31 @@ +package swen90006.passbook; + +/** + * A pair of objects. + */ +public class Pair<X, Y> +{ + private X first; + private Y second; + + public Pair(X first, Y second) + { + this.first = first; + this.second = second; + } + + public X getFirst() + { + return this.first; + } + + public Y getSecond() + { + return this.second; + } + + public boolean equals(Pair<X, Y> other) + { + return first.equals(other.first) && second.equals(other.second); + } +} diff --git a/programs/mutant-2/swen90006/passbook/PassBook.java b/programs/mutant-2/swen90006/passbook/PassBook.java new file mode 100644 index 0000000..94e2dfd --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/PassBook.java @@ -0,0 +1,262 @@ +package swen90006.passbook; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Random; +import java.util.Arrays; + +/** + * PassBook is a (fictional) online password manager. A password + * manager is a software application that generates, stores, and + * retrieves login details for users. + * + * A user has an account with PassBook. This account is protected by a + * master password, or passphrase. Each user can store login details + * for multiple websites, identified by their URL. A user can add a + * login details for a given website. The passphrase is used to + * encrypt all passwords, but for this implementation, we have ignored + * encryption. + * + * The PassBook passphrase must conform to the following requirements: + * Must be at least eight characters long and contain at + * least one digit (range 0-9), one letter in the range 'a'-'z', and + * one letter in the range 'A'-'Z'. + * + * Username and passwords for stored URLs have no password + * requirements. + * + * When a user logs into PassBook, they are given a session ID. This + * session ID is used to identify them for all transactions until they + * log out. + */ +public class PassBook +{ + /** The minimum length of a passphrase */ + public final static int MINIMUM_PASSPHRASE_LENGTH = 8; + + /** Valid URL protocols for the passbook */ + public final static String [] VALID_URL_PROTOCOLS = {"http", "https"}; + + //The passphrases (master passwords) for all users + private Map<String, String> passphrases; + + //The login details for all users and the URLs they have stored + private Map<String, PasswordTable> details; + + //Mapping from session IDs to usernames + private Map<Integer, String> userIDs; + + //Mapping from usernames to sessionIDs + private Map<String, Integer> sessionIDs; + + /** + * Constructs an empty passbook. + */ + public PassBook() + { + passphrases = new HashMap<String, String>(); + details = new HashMap<String, PasswordTable>(); + userIDs = new HashMap<Integer, String>(); + sessionIDs = new HashMap<String, Integer>(); + } + + /** + * Adds a new user to the passbook. + * + * @param passbookUsername the username for the user to be added + * @param passphrase the passphrase (master password) for the user + * @throws DuplicateUserException if the username is already in the passbook + * @throws WeakPassphraseException if the password does not fit the passphrase + rules (see class documentation) + * + * Assumption: passbookUsername and passphrase are non-null + */ + public void addUser(String passbookUsername, String passphrase) + throws DuplicateUserException, WeakPassphraseException + { + //Check if this user exists + if (passphrases.containsKey(passbookUsername)) { + throw new DuplicateUserException(passbookUsername); + } + //check the passphrase strength + else { + if (passphrase.length() < MINIMUM_PASSPHRASE_LENGTH) { + throw new WeakPassphraseException(passphrase); + } + + boolean containsLowerCase = false; + boolean containsUpperCase = false; + boolean containsNumber = false; + for (int i = 0; i < passphrase.length(); i++) { + + if ('a' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'z') { + containsLowerCase = true; + } + else if ('A' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'Z') { + containsUpperCase = true; + } + else if ('0' <= passphrase.charAt(i) && passphrase.charAt(i) <= '9') { + containsNumber = true; + } + } + + if (!containsLowerCase || !containsUpperCase || !containsNumber) { + throw new WeakPassphraseException(passphrase); + } + } + + passphrases.put(passbookUsername, passphrase); + PasswordTable pt = new PasswordTable(); + details.put(passbookUsername, pt); + } + + /** + * Checks if a user exists + * @param passbookUsername the passbookUsername + * @return true if and only if this user is in the passbook + * + * Assumption: passbookUsername is non-null + */ + public boolean isUser(String passbookUsername) + { + return passphrases.containsKey(passbookUsername); + } + + /** + * Logs a user into the passbook. + * + * @param passbookUsername the passbookUsername for the user + * @param passphrase the passphrase (master password) for the user + * @returns a session ID greater than or equal to 0 + * @throws NoSuchUserException if the user does not exist in the passbook + * @throws AlreadyLoggedInException if the user is already logged in + * @throws IncorrectPassphraseException if the passphrase is incorrect for this user + * + * Assumption: passbookUsername and passphrase are non-null + */ + public int loginUser(String passbookUsername, String passphrase) + throws NoSuchUserException, AlreadyLoggedInException, IncorrectPassphraseException + { + //check that the user exists + if (!passphrases.containsKey(passbookUsername)) { + throw new NoSuchUserException(passbookUsername); + } + else if (sessionIDs.get(passbookUsername) != null) { + throw new AlreadyLoggedInException(passbookUsername); + } + else if (!passphrases.get(passbookUsername).equals(passphrase)) { + throw new IncorrectPassphraseException(passbookUsername, passphrase); + } + + //generate a random session ID that is not already taken + int sessionID = new Random().nextInt(Integer.MAX_VALUE); + while (userIDs.containsKey(sessionID)) { + sessionID = new Random().nextInt(Integer.MAX_VALUE); + } + + //add the session ID + sessionIDs.put(passbookUsername, sessionID); + userIDs.put(sessionID, passbookUsername); + + return sessionID; + } + + /** + * Logs out a user based on session ID. Has no affect if the session ID does not exist. + * + * @param sessionID the session ID to be terminated + * + * Assumption: session ID is non-null + */ + public void logoutUser(Integer sessionID) + { + sessionIDs.remove(userIDs.get(sessionID)); + userIDs.remove(sessionID); + } + + /** + * Updates the login details for a URL of a user. If the url + * username or password are null, the login details for this URL + * must be removed. + * + * @param sessionID the session ID for the logged-in user + * @param urlUsername the username for the user to be added + * @param url the URL for the username/password pair + * @param urlPassword the password to be add + * @throws InvalidSessionIDException if the session ID does not exists + * @throws MalformedURLException if the protocol is not 'http' or 'https' + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public void updateDetails(Integer sessionID, URL url, String urlUsername, String urlPassword) + throws InvalidSessionIDException, MalformedURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + if (urlUsername == null || urlPassword == null) { + pt.remove(url); + } + else { + pt.put(url, new Pair<String, String> (urlUsername, urlPassword)); + details.put(passbookUsername, pt); + } + } + + + /** + * Retrieves login details for a given URL and user. + * + * @param sessionID the session ID + * @param url the URL for the password being retrieved. + * @returns the username and password for a given url for the corresponding user + * @throws NoSuchUserException if the username does not exist in the passbook + * @throws MalformedURLException if the syntax is invalid (see class documentation) + * @throws NoSuchURLException if user does not have login for this URL + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public Pair<String, String> retrieveDetails(Integer sessionID, URL url) + throws InvalidSessionIDException, MalformedURLException, NoSuchURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + //if this returned nothing, the user has no details for any url + if (pt == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + Pair<String, String> pair = pt.get(url); + + //if this returned nothing, the user does not have a login for this url + if (pair == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + return pair; + } + + //A simple label to improve code readability + private class PasswordTable extends HashMap<URL, Pair<String, String>> {} +} diff --git a/programs/mutant-2/swen90006/passbook/WeakPassphraseException.java b/programs/mutant-2/swen90006/passbook/WeakPassphraseException.java new file mode 100644 index 0000000..c4bc33b --- /dev/null +++ b/programs/mutant-2/swen90006/passbook/WeakPassphraseException.java @@ -0,0 +1,14 @@ +package swen90006.passbook; + +public class WeakPassphraseException extends Exception +{ + public WeakPassphraseException (String passphrase) + { + super("Passphrase does not comply with the PassBook rules\n" + + "\t- must contains at least " + + PassBook.MINIMUM_PASSPHRASE_LENGTH + " characters\n" + + "\t- must contain at least one numeric character\n" + + "\t- must contain at least one lower case letter\n" + + "\t- must contain at least one upper case letter\n"); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/AlreadyLoggedInException.java b/programs/mutant-3 /swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000..8bbbd85 --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/AlreadyLoggedInException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class AlreadyLoggedInException extends Exception +{ + public AlreadyLoggedInException(String username) + { + super("Username already logged in: " + username); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/DuplicateUserException.java b/programs/mutant-3 /swen90006/passbook/DuplicateUserException.java new file mode 100644 index 0000000..74370b1 --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/DuplicateUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class DuplicateUserException extends Exception +{ + public DuplicateUserException(String username) + { + super("Username already exists: " + username); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/IncorrectPassphraseException.java b/programs/mutant-3 /swen90006/passbook/IncorrectPassphraseException.java new file mode 100644 index 0000000..cdfc80b --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/IncorrectPassphraseException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class IncorrectPassphraseException extends Exception +{ + public IncorrectPassphraseException(String username, String passphrase) + { + super("Incorrect passphrase: " + passphrase + " for user " + username); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/InvalidSessionIDException.java b/programs/mutant-3 /swen90006/passbook/InvalidSessionIDException.java new file mode 100644 index 0000000..230cf58 --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/InvalidSessionIDException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class InvalidSessionIDException extends Exception +{ + public InvalidSessionIDException(Integer sessionID) + { + super("Invalid session ID: " + sessionID); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/NoSuchURLException.java b/programs/mutant-3 /swen90006/passbook/NoSuchURLException.java new file mode 100644 index 0000000..7edb916 --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/NoSuchURLException.java @@ -0,0 +1,11 @@ +package swen90006.passbook; + +import java.net.URL; + +public class NoSuchURLException extends Exception +{ + public NoSuchURLException (String username, URL url) + { + super("User " + username + " does not have password for URL " + url.toString()); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/NoSuchUserException.java b/programs/mutant-3 /swen90006/passbook/NoSuchUserException.java new file mode 100644 index 0000000..ca4c527 --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/NoSuchUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class NoSuchUserException extends Exception +{ + public NoSuchUserException (String username) + { + super("Username does not exist: " + username); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/Pair.java b/programs/mutant-3 /swen90006/passbook/Pair.java new file mode 100644 index 0000000..8f70502 --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/Pair.java @@ -0,0 +1,31 @@ +package swen90006.passbook; + +/** + * A pair of objects. + */ +public class Pair<X, Y> +{ + private X first; + private Y second; + + public Pair(X first, Y second) + { + this.first = first; + this.second = second; + } + + public X getFirst() + { + return this.first; + } + + public Y getSecond() + { + return this.second; + } + + public boolean equals(Pair<X, Y> other) + { + return first.equals(other.first) && second.equals(other.second); + } +} diff --git a/programs/mutant-3 /swen90006/passbook/PassBook.java b/programs/mutant-3 /swen90006/passbook/PassBook.java new file mode 100644 index 0000000..94e2dfd --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/PassBook.java @@ -0,0 +1,262 @@ +package swen90006.passbook; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Random; +import java.util.Arrays; + +/** + * PassBook is a (fictional) online password manager. A password + * manager is a software application that generates, stores, and + * retrieves login details for users. + * + * A user has an account with PassBook. This account is protected by a + * master password, or passphrase. Each user can store login details + * for multiple websites, identified by their URL. A user can add a + * login details for a given website. The passphrase is used to + * encrypt all passwords, but for this implementation, we have ignored + * encryption. + * + * The PassBook passphrase must conform to the following requirements: + * Must be at least eight characters long and contain at + * least one digit (range 0-9), one letter in the range 'a'-'z', and + * one letter in the range 'A'-'Z'. + * + * Username and passwords for stored URLs have no password + * requirements. + * + * When a user logs into PassBook, they are given a session ID. This + * session ID is used to identify them for all transactions until they + * log out. + */ +public class PassBook +{ + /** The minimum length of a passphrase */ + public final static int MINIMUM_PASSPHRASE_LENGTH = 8; + + /** Valid URL protocols for the passbook */ + public final static String [] VALID_URL_PROTOCOLS = {"http", "https"}; + + //The passphrases (master passwords) for all users + private Map<String, String> passphrases; + + //The login details for all users and the URLs they have stored + private Map<String, PasswordTable> details; + + //Mapping from session IDs to usernames + private Map<Integer, String> userIDs; + + //Mapping from usernames to sessionIDs + private Map<String, Integer> sessionIDs; + + /** + * Constructs an empty passbook. + */ + public PassBook() + { + passphrases = new HashMap<String, String>(); + details = new HashMap<String, PasswordTable>(); + userIDs = new HashMap<Integer, String>(); + sessionIDs = new HashMap<String, Integer>(); + } + + /** + * Adds a new user to the passbook. + * + * @param passbookUsername the username for the user to be added + * @param passphrase the passphrase (master password) for the user + * @throws DuplicateUserException if the username is already in the passbook + * @throws WeakPassphraseException if the password does not fit the passphrase + rules (see class documentation) + * + * Assumption: passbookUsername and passphrase are non-null + */ + public void addUser(String passbookUsername, String passphrase) + throws DuplicateUserException, WeakPassphraseException + { + //Check if this user exists + if (passphrases.containsKey(passbookUsername)) { + throw new DuplicateUserException(passbookUsername); + } + //check the passphrase strength + else { + if (passphrase.length() < MINIMUM_PASSPHRASE_LENGTH) { + throw new WeakPassphraseException(passphrase); + } + + boolean containsLowerCase = false; + boolean containsUpperCase = false; + boolean containsNumber = false; + for (int i = 0; i < passphrase.length(); i++) { + + if ('a' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'z') { + containsLowerCase = true; + } + else if ('A' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'Z') { + containsUpperCase = true; + } + else if ('0' <= passphrase.charAt(i) && passphrase.charAt(i) <= '9') { + containsNumber = true; + } + } + + if (!containsLowerCase || !containsUpperCase || !containsNumber) { + throw new WeakPassphraseException(passphrase); + } + } + + passphrases.put(passbookUsername, passphrase); + PasswordTable pt = new PasswordTable(); + details.put(passbookUsername, pt); + } + + /** + * Checks if a user exists + * @param passbookUsername the passbookUsername + * @return true if and only if this user is in the passbook + * + * Assumption: passbookUsername is non-null + */ + public boolean isUser(String passbookUsername) + { + return passphrases.containsKey(passbookUsername); + } + + /** + * Logs a user into the passbook. + * + * @param passbookUsername the passbookUsername for the user + * @param passphrase the passphrase (master password) for the user + * @returns a session ID greater than or equal to 0 + * @throws NoSuchUserException if the user does not exist in the passbook + * @throws AlreadyLoggedInException if the user is already logged in + * @throws IncorrectPassphraseException if the passphrase is incorrect for this user + * + * Assumption: passbookUsername and passphrase are non-null + */ + public int loginUser(String passbookUsername, String passphrase) + throws NoSuchUserException, AlreadyLoggedInException, IncorrectPassphraseException + { + //check that the user exists + if (!passphrases.containsKey(passbookUsername)) { + throw new NoSuchUserException(passbookUsername); + } + else if (sessionIDs.get(passbookUsername) != null) { + throw new AlreadyLoggedInException(passbookUsername); + } + else if (!passphrases.get(passbookUsername).equals(passphrase)) { + throw new IncorrectPassphraseException(passbookUsername, passphrase); + } + + //generate a random session ID that is not already taken + int sessionID = new Random().nextInt(Integer.MAX_VALUE); + while (userIDs.containsKey(sessionID)) { + sessionID = new Random().nextInt(Integer.MAX_VALUE); + } + + //add the session ID + sessionIDs.put(passbookUsername, sessionID); + userIDs.put(sessionID, passbookUsername); + + return sessionID; + } + + /** + * Logs out a user based on session ID. Has no affect if the session ID does not exist. + * + * @param sessionID the session ID to be terminated + * + * Assumption: session ID is non-null + */ + public void logoutUser(Integer sessionID) + { + sessionIDs.remove(userIDs.get(sessionID)); + userIDs.remove(sessionID); + } + + /** + * Updates the login details for a URL of a user. If the url + * username or password are null, the login details for this URL + * must be removed. + * + * @param sessionID the session ID for the logged-in user + * @param urlUsername the username for the user to be added + * @param url the URL for the username/password pair + * @param urlPassword the password to be add + * @throws InvalidSessionIDException if the session ID does not exists + * @throws MalformedURLException if the protocol is not 'http' or 'https' + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public void updateDetails(Integer sessionID, URL url, String urlUsername, String urlPassword) + throws InvalidSessionIDException, MalformedURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + if (urlUsername == null || urlPassword == null) { + pt.remove(url); + } + else { + pt.put(url, new Pair<String, String> (urlUsername, urlPassword)); + details.put(passbookUsername, pt); + } + } + + + /** + * Retrieves login details for a given URL and user. + * + * @param sessionID the session ID + * @param url the URL for the password being retrieved. + * @returns the username and password for a given url for the corresponding user + * @throws NoSuchUserException if the username does not exist in the passbook + * @throws MalformedURLException if the syntax is invalid (see class documentation) + * @throws NoSuchURLException if user does not have login for this URL + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public Pair<String, String> retrieveDetails(Integer sessionID, URL url) + throws InvalidSessionIDException, MalformedURLException, NoSuchURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + //if this returned nothing, the user has no details for any url + if (pt == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + Pair<String, String> pair = pt.get(url); + + //if this returned nothing, the user does not have a login for this url + if (pair == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + return pair; + } + + //A simple label to improve code readability + private class PasswordTable extends HashMap<URL, Pair<String, String>> {} +} diff --git a/programs/mutant-3 /swen90006/passbook/WeakPassphraseException.java b/programs/mutant-3 /swen90006/passbook/WeakPassphraseException.java new file mode 100644 index 0000000..c4bc33b --- /dev/null +++ b/programs/mutant-3 /swen90006/passbook/WeakPassphraseException.java @@ -0,0 +1,14 @@ +package swen90006.passbook; + +public class WeakPassphraseException extends Exception +{ + public WeakPassphraseException (String passphrase) + { + super("Passphrase does not comply with the PassBook rules\n" + + "\t- must contains at least " + + PassBook.MINIMUM_PASSPHRASE_LENGTH + " characters\n" + + "\t- must contain at least one numeric character\n" + + "\t- must contain at least one lower case letter\n" + + "\t- must contain at least one upper case letter\n"); + } +} diff --git a/programs/mutant-4/swen90006/passbook/AlreadyLoggedInException.java b/programs/mutant-4/swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000..8bbbd85 --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/AlreadyLoggedInException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class AlreadyLoggedInException extends Exception +{ + public AlreadyLoggedInException(String username) + { + super("Username already logged in: " + username); + } +} diff --git a/programs/mutant-4/swen90006/passbook/DuplicateUserException.java b/programs/mutant-4/swen90006/passbook/DuplicateUserException.java new file mode 100644 index 0000000..74370b1 --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/DuplicateUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class DuplicateUserException extends Exception +{ + public DuplicateUserException(String username) + { + super("Username already exists: " + username); + } +} diff --git a/programs/mutant-4/swen90006/passbook/IncorrectPassphraseException.java b/programs/mutant-4/swen90006/passbook/IncorrectPassphraseException.java new file mode 100644 index 0000000..cdfc80b --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/IncorrectPassphraseException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class IncorrectPassphraseException extends Exception +{ + public IncorrectPassphraseException(String username, String passphrase) + { + super("Incorrect passphrase: " + passphrase + " for user " + username); + } +} diff --git a/programs/mutant-4/swen90006/passbook/InvalidSessionIDException.java b/programs/mutant-4/swen90006/passbook/InvalidSessionIDException.java new file mode 100644 index 0000000..230cf58 --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/InvalidSessionIDException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class InvalidSessionIDException extends Exception +{ + public InvalidSessionIDException(Integer sessionID) + { + super("Invalid session ID: " + sessionID); + } +} diff --git a/programs/mutant-4/swen90006/passbook/NoSuchURLException.java b/programs/mutant-4/swen90006/passbook/NoSuchURLException.java new file mode 100644 index 0000000..7edb916 --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/NoSuchURLException.java @@ -0,0 +1,11 @@ +package swen90006.passbook; + +import java.net.URL; + +public class NoSuchURLException extends Exception +{ + public NoSuchURLException (String username, URL url) + { + super("User " + username + " does not have password for URL " + url.toString()); + } +} diff --git a/programs/mutant-4/swen90006/passbook/NoSuchUserException.java b/programs/mutant-4/swen90006/passbook/NoSuchUserException.java new file mode 100644 index 0000000..ca4c527 --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/NoSuchUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class NoSuchUserException extends Exception +{ + public NoSuchUserException (String username) + { + super("Username does not exist: " + username); + } +} diff --git a/programs/mutant-4/swen90006/passbook/Pair.java b/programs/mutant-4/swen90006/passbook/Pair.java new file mode 100644 index 0000000..8f70502 --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/Pair.java @@ -0,0 +1,31 @@ +package swen90006.passbook; + +/** + * A pair of objects. + */ +public class Pair<X, Y> +{ + private X first; + private Y second; + + public Pair(X first, Y second) + { + this.first = first; + this.second = second; + } + + public X getFirst() + { + return this.first; + } + + public Y getSecond() + { + return this.second; + } + + public boolean equals(Pair<X, Y> other) + { + return first.equals(other.first) && second.equals(other.second); + } +} diff --git a/programs/mutant-4/swen90006/passbook/PassBook.java b/programs/mutant-4/swen90006/passbook/PassBook.java new file mode 100644 index 0000000..1d82ffc --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/PassBook.java @@ -0,0 +1,262 @@ +package swen90006.passbook; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Random; +import java.util.Arrays; + +/** + * PassBook is a (fictional) online password manager. A password + * manager is a software application that generates, stores, and + * retrieves login details for users. + * + * A user has an account with PassBook. This account is protected by a + * master password, or passphrase. Each user can store login details + * for multiple websites, identified by their URL. A user can add a + * login details for a given website. The passphrase is used to + * encrypt all passwords, but for this implementation, we have ignored + * encryption. + * + * The PassBook passphrase must conform to the following requirements: + * Must be at least eight characters long and contain at + * least one digit (range 0-9), one letter in the range 'a'-'z', and + * one letter in the range 'A'-'Z'. + * + * Username and passwords for stored URLs have no password + * requirements. + * + * When a user logs into PassBook, they are given a session ID. This + * session ID is used to identify them for all transactions until they + * log out. + */ +public class PassBook +{ + /** The minimum length of a passphrase */ + public final static int MINIMUM_PASSPHRASE_LENGTH = 8; + + /** Valid URL protocols for the passbook */ + public final static String [] VALID_URL_PROTOCOLS = {"http", "https"}; + + //The passphrases (master passwords) for all users + private Map<String, String> passphrases; + + //The login details for all users and the URLs they have stored + private Map<String, PasswordTable> details; + + //Mapping from session IDs to usernames + private Map<Integer, String> userIDs; + + //Mapping from usernames to sessionIDs + private Map<String, Integer> sessionIDs; + + /** + * Constructs an empty passbook. + */ + public PassBook() + { + passphrases = new HashMap<String, String>(); + details = new HashMap<String, PasswordTable>(); + userIDs = new HashMap<Integer, String>(); + sessionIDs = new HashMap<String, Integer>(); + } + + /** + * Adds a new user to the passbook. + * + * @param passbookUsername the username for the user to be added + * @param passphrase the passphrase (master password) for the user + * @throws DuplicateUserException if the username is already in the passbook + * @throws WeakPassphraseException if the password does not fit the passphrase + rules (see class documentation) + * + * Assumption: passbookUsername and passphrase are non-null + */ + public void addUser(String passbookUsername, String passphrase) + throws DuplicateUserException, WeakPassphraseException + { + //Check if this user exists + if (passphrases.containsKey(passbookUsername)) { + throw new DuplicateUserException(passbookUsername); + } + //check the passphrase strength + else { + if (passphrase.length() < MINIMUM_PASSPHRASE_LENGTH) { + throw new WeakPassphraseException(passphrase); + } + + boolean containsLowerCase = false; + boolean containsUpperCase = false; + boolean containsNumber = false; + for (int i = 0; i < passphrase.length(); i++) { + + if ('a' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'z') { + containsLowerCase = true; + } + else if ('A' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'Z') { + containsUpperCase = true; + } + else if ('/' <= passphrase.charAt(i) && passphrase.charAt(i) <= '9') { + containsNumber = true; + } + } + + if (!containsLowerCase || !containsUpperCase || !containsNumber) { + throw new WeakPassphraseException(passphrase); + } + } + + passphrases.put(passbookUsername, passphrase); + PasswordTable pt = new PasswordTable(); + details.put(passbookUsername, pt); + } + + /** + * Checks if a user exists + * @param passbookUsername the passbookUsername + * @return true if and only if this user is in the passbook + * + * Assumption: passbookUsername is non-null + */ + public boolean isUser(String passbookUsername) + { + return passphrases.containsKey(passbookUsername); + } + + /** + * Logs a user into the passbook. + * + * @param passbookUsername the passbookUsername for the user + * @param passphrase the passphrase (master password) for the user + * @returns a session ID greater than or equal to 0 + * @throws NoSuchUserException if the user does not exist in the passbook + * @throws AlreadyLoggedInException if the user is already logged in + * @throws IncorrectPassphraseException if the passphrase is incorrect for this user + * + * Assumption: passbookUsername and passphrase are non-null + */ + public int loginUser(String passbookUsername, String passphrase) + throws NoSuchUserException, AlreadyLoggedInException, IncorrectPassphraseException + { + //check that the user exists + if (!passphrases.containsKey(passbookUsername)) { + throw new NoSuchUserException(passbookUsername); + } + else if (sessionIDs.get(passbookUsername) != null) { + throw new AlreadyLoggedInException(passbookUsername); + } + else if (!passphrases.get(passbookUsername).equals(passphrase)) { + throw new IncorrectPassphraseException(passbookUsername, passphrase); + } + + //generate a random session ID that is not already taken + int sessionID = new Random().nextInt(Integer.MAX_VALUE); + while (userIDs.containsKey(sessionID)) { + sessionID = new Random().nextInt(Integer.MAX_VALUE); + } + + //add the session ID + sessionIDs.put(passbookUsername, sessionID); + userIDs.put(sessionID, passbookUsername); + + return sessionID; + } + + /** + * Logs out a user based on session ID. Has no affect if the session ID does not exist. + * + * @param sessionID the session ID to be terminated + * + * Assumption: session ID is non-null + */ + public void logoutUser(Integer sessionID) + { + sessionIDs.remove(userIDs.get(sessionID)); + userIDs.remove(sessionID); + } + + /** + * Updates the login details for a URL of a user. If the url + * username or password are null, the login details for this URL + * must be removed. + * + * @param sessionID the session ID for the logged-in user + * @param urlUsername the username for the user to be added + * @param url the URL for the username/password pair + * @param urlPassword the password to be add + * @throws InvalidSessionIDException if the session ID does not exists + * @throws MalformedURLException if the protocol is not 'http' or 'https' + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public void updateDetails(Integer sessionID, URL url, String urlUsername, String urlPassword) + throws InvalidSessionIDException, MalformedURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + if (urlUsername == null || urlPassword == null) { + pt.remove(url); + } + else { + pt.put(url, new Pair<String, String> (urlUsername, urlPassword)); + details.put(passbookUsername, pt); + } + } + + + /** + * Retrieves login details for a given URL and user. + * + * @param sessionID the session ID + * @param url the URL for the password being retrieved. + * @returns the username and password for a given url for the corresponding user + * @throws NoSuchUserException if the username does not exist in the passbook + * @throws MalformedURLException if the syntax is invalid (see class documentation) + * @throws NoSuchURLException if user does not have login for this URL + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public Pair<String, String> retrieveDetails(Integer sessionID, URL url) + throws InvalidSessionIDException, MalformedURLException, NoSuchURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + //if this returned nothing, the user has no details for any url + if (pt == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + Pair<String, String> pair = pt.get(url); + + //if this returned nothing, the user does not have a login for this url + if (pair == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + return pair; + } + + //A simple label to improve code readability + private class PasswordTable extends HashMap<URL, Pair<String, String>> {} +} diff --git a/programs/mutant-4/swen90006/passbook/WeakPassphraseException.java b/programs/mutant-4/swen90006/passbook/WeakPassphraseException.java new file mode 100644 index 0000000..c4bc33b --- /dev/null +++ b/programs/mutant-4/swen90006/passbook/WeakPassphraseException.java @@ -0,0 +1,14 @@ +package swen90006.passbook; + +public class WeakPassphraseException extends Exception +{ + public WeakPassphraseException (String passphrase) + { + super("Passphrase does not comply with the PassBook rules\n" + + "\t- must contains at least " + + PassBook.MINIMUM_PASSPHRASE_LENGTH + " characters\n" + + "\t- must contain at least one numeric character\n" + + "\t- must contain at least one lower case letter\n" + + "\t- must contain at least one upper case letter\n"); + } +} diff --git a/programs/mutant-5/swen90006/passbook/AlreadyLoggedInException.java b/programs/mutant-5/swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000..8bbbd85 --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/AlreadyLoggedInException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class AlreadyLoggedInException extends Exception +{ + public AlreadyLoggedInException(String username) + { + super("Username already logged in: " + username); + } +} diff --git a/programs/mutant-5/swen90006/passbook/DuplicateUserException.java b/programs/mutant-5/swen90006/passbook/DuplicateUserException.java new file mode 100644 index 0000000..74370b1 --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/DuplicateUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class DuplicateUserException extends Exception +{ + public DuplicateUserException(String username) + { + super("Username already exists: " + username); + } +} diff --git a/programs/mutant-5/swen90006/passbook/IncorrectPassphraseException.java b/programs/mutant-5/swen90006/passbook/IncorrectPassphraseException.java new file mode 100644 index 0000000..cdfc80b --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/IncorrectPassphraseException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class IncorrectPassphraseException extends Exception +{ + public IncorrectPassphraseException(String username, String passphrase) + { + super("Incorrect passphrase: " + passphrase + " for user " + username); + } +} diff --git a/programs/mutant-5/swen90006/passbook/InvalidSessionIDException.java b/programs/mutant-5/swen90006/passbook/InvalidSessionIDException.java new file mode 100644 index 0000000..230cf58 --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/InvalidSessionIDException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class InvalidSessionIDException extends Exception +{ + public InvalidSessionIDException(Integer sessionID) + { + super("Invalid session ID: " + sessionID); + } +} diff --git a/programs/mutant-5/swen90006/passbook/NoSuchURLException.java b/programs/mutant-5/swen90006/passbook/NoSuchURLException.java new file mode 100644 index 0000000..7edb916 --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/NoSuchURLException.java @@ -0,0 +1,11 @@ +package swen90006.passbook; + +import java.net.URL; + +public class NoSuchURLException extends Exception +{ + public NoSuchURLException (String username, URL url) + { + super("User " + username + " does not have password for URL " + url.toString()); + } +} diff --git a/programs/mutant-5/swen90006/passbook/NoSuchUserException.java b/programs/mutant-5/swen90006/passbook/NoSuchUserException.java new file mode 100644 index 0000000..ca4c527 --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/NoSuchUserException.java @@ -0,0 +1,9 @@ +package swen90006.passbook; + +public class NoSuchUserException extends Exception +{ + public NoSuchUserException (String username) + { + super("Username does not exist: " + username); + } +} diff --git a/programs/mutant-5/swen90006/passbook/Pair.java b/programs/mutant-5/swen90006/passbook/Pair.java new file mode 100644 index 0000000..8f70502 --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/Pair.java @@ -0,0 +1,31 @@ +package swen90006.passbook; + +/** + * A pair of objects. + */ +public class Pair<X, Y> +{ + private X first; + private Y second; + + public Pair(X first, Y second) + { + this.first = first; + this.second = second; + } + + public X getFirst() + { + return this.first; + } + + public Y getSecond() + { + return this.second; + } + + public boolean equals(Pair<X, Y> other) + { + return first.equals(other.first) && second.equals(other.second); + } +} diff --git a/programs/mutant-5/swen90006/passbook/PassBook.java b/programs/mutant-5/swen90006/passbook/PassBook.java new file mode 100644 index 0000000..267011c --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/PassBook.java @@ -0,0 +1,262 @@ +package swen90006.passbook; + +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Random; +import java.util.Arrays; + +/** + * PassBook is a (fictional) online password manager. A password + * manager is a software application that generates, stores, and + * retrieves login details for users. + * + * A user has an account with PassBook. This account is protected by a + * master password, or passphrase. Each user can store login details + * for multiple websites, identified by their URL. A user can add a + * login details for a given website. The passphrase is used to + * encrypt all passwords, but for this implementation, we have ignored + * encryption. + * + * The PassBook passphrase must conform to the following requirements: + * Must be at least eight characters long and contain at + * least one digit (range 0-9), one letter in the range 'a'-'z', and + * one letter in the range 'A'-'Z'. + * + * Username and passwords for stored URLs have no password + * requirements. + * + * When a user logs into PassBook, they are given a session ID. This + * session ID is used to identify them for all transactions until they + * log out. + */ +public class PassBook +{ + /** The minimum length of a passphrase */ + public final static int MINIMUM_PASSPHRASE_LENGTH = 8; + + /** Valid URL protocols for the passbook */ + public final static String [] VALID_URL_PROTOCOLS = {"http", "https"}; + + //The passphrases (master passwords) for all users + private Map<String, String> passphrases; + + //The login details for all users and the URLs they have stored + private Map<String, PasswordTable> details; + + //Mapping from session IDs to usernames + private Map<Integer, String> userIDs; + + //Mapping from usernames to sessionIDs + private Map<String, Integer> sessionIDs; + + /** + * Constructs an empty passbook. + */ + public PassBook() + { + passphrases = new HashMap<String, String>(); + details = new HashMap<String, PasswordTable>(); + userIDs = new HashMap<Integer, String>(); + sessionIDs = new HashMap<String, Integer>(); + } + + /** + * Adds a new user to the passbook. + * + * @param passbookUsername the username for the user to be added + * @param passphrase the passphrase (master password) for the user + * @throws DuplicateUserException if the username is already in the passbook + * @throws WeakPassphraseException if the password does not fit the passphrase + rules (see class documentation) + * + * Assumption: passbookUsername and passphrase are non-null + */ + public void addUser(String passbookUsername, String passphrase) + throws DuplicateUserException, WeakPassphraseException + { + //Check if this user exists + if (passphrases.containsKey(passbookUsername)) { + throw new DuplicateUserException(passbookUsername); + } + //check the passphrase strength + else { + if (passphrase.length() < MINIMUM_PASSPHRASE_LENGTH) { + throw new WeakPassphraseException(passphrase); + } + + boolean containsLowerCase = false; + boolean containsUpperCase = false; + boolean containsNumber = false; + for (int i = 0; i < passphrase.length(); i++) { + + if ('a' <= passphrase.charAt(i) || passphrase.charAt(i) <= 'z') { + containsLowerCase = true; + } + else if ('A' <= passphrase.charAt(i) && passphrase.charAt(i) <= 'Z') { + containsUpperCase = true; + } + else if ('0' <= passphrase.charAt(i) && passphrase.charAt(i) <= '9') { + containsNumber = true; + } + } + + if (!containsLowerCase || !containsUpperCase || !containsNumber) { + throw new WeakPassphraseException(passphrase); + } + } + + passphrases.put(passbookUsername, passphrase); + PasswordTable pt = new PasswordTable(); + details.put(passbookUsername, pt); + } + + /** + * Checks if a user exists + * @param passbookUsername the passbookUsername + * @return true if and only if this user is in the passbook + * + * Assumption: passbookUsername is non-null + */ + public boolean isUser(String passbookUsername) + { + return passphrases.containsKey(passbookUsername); + } + + /** + * Logs a user into the passbook. + * + * @param passbookUsername the passbookUsername for the user + * @param passphrase the passphrase (master password) for the user + * @returns a session ID greater than or equal to 0 + * @throws NoSuchUserException if the user does not exist in the passbook + * @throws AlreadyLoggedInException if the user is already logged in + * @throws IncorrectPassphraseException if the passphrase is incorrect for this user + * + * Assumption: passbookUsername and passphrase are non-null + */ + public int loginUser(String passbookUsername, String passphrase) + throws NoSuchUserException, AlreadyLoggedInException, IncorrectPassphraseException + { + //check that the user exists + if (!passphrases.containsKey(passbookUsername)) { + throw new NoSuchUserException(passbookUsername); + } + else if (sessionIDs.get(passbookUsername) != null) { + throw new AlreadyLoggedInException(passbookUsername); + } + else if (!passphrases.get(passbookUsername).equals(passphrase)) { + throw new IncorrectPassphraseException(passbookUsername, passphrase); + } + + //generate a random session ID that is not already taken + int sessionID = new Random().nextInt(Integer.MAX_VALUE); + while (userIDs.containsKey(sessionID)) { + sessionID = new Random().nextInt(Integer.MAX_VALUE); + } + + //add the session ID + sessionIDs.put(passbookUsername, sessionID); + userIDs.put(sessionID, passbookUsername); + + return sessionID; + } + + /** + * Logs out a user based on session ID. Has no affect if the session ID does not exist. + * + * @param sessionID the session ID to be terminated + * + * Assumption: session ID is non-null + */ + public void logoutUser(Integer sessionID) + { + sessionIDs.remove(userIDs.get(sessionID)); + userIDs.remove(sessionID); + } + + /** + * Updates the login details for a URL of a user. If the url + * username or password are null, the login details for this URL + * must be removed. + * + * @param sessionID the session ID for the logged-in user + * @param urlUsername the username for the user to be added + * @param url the URL for the username/password pair + * @param urlPassword the password to be add + * @throws InvalidSessionIDException if the session ID does not exists + * @throws MalformedURLException if the protocol is not 'http' or 'https' + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public void updateDetails(Integer sessionID, URL url, String urlUsername, String urlPassword) + throws InvalidSessionIDException, MalformedURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + if (urlUsername == null && urlPassword == null) { + pt.remove(url); + } + else { + pt.put(url, new Pair<String, String> (urlUsername, urlPassword)); + details.put(passbookUsername, pt); + } + } + + + /** + * Retrieves login details for a given URL and user. + * + * @param sessionID the session ID + * @param url the URL for the password being retrieved. + * @returns the username and password for a given url for the corresponding user + * @throws NoSuchUserException if the username does not exist in the passbook + * @throws MalformedURLException if the syntax is invalid (see class documentation) + * @throws NoSuchURLException if user does not have login for this URL + * + * Assumption: url is non-null and a valid URL object + * Assumption: sessionID is non-null + */ + public Pair<String, String> retrieveDetails(Integer sessionID, URL url) + throws InvalidSessionIDException, MalformedURLException, NoSuchURLException + { + //check that the session ID exists + String passbookUsername = userIDs.get(sessionID); + if (passbookUsername == null) { + throw new InvalidSessionIDException(sessionID); + } + else if (!Arrays.asList(VALID_URL_PROTOCOLS).contains(url.getProtocol())) { + throw new MalformedURLException(passbookUsername); + } + + PasswordTable pt = details.get(passbookUsername); + //if this returned nothing, the user has no details for any url + if (pt == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + Pair<String, String> pair = pt.get(url); + + //if this returned nothing, the user does not have a login for this url + if (pair == null) { + throw new NoSuchURLException(passbookUsername, url); + } + + return pair; + } + + //A simple label to improve code readability + private class PasswordTable extends HashMap<URL, Pair<String, String>> {} +} diff --git a/programs/mutant-5/swen90006/passbook/WeakPassphraseException.java b/programs/mutant-5/swen90006/passbook/WeakPassphraseException.java new file mode 100644 index 0000000..c4bc33b --- /dev/null +++ b/programs/mutant-5/swen90006/passbook/WeakPassphraseException.java @@ -0,0 +1,14 @@ +package swen90006.passbook; + +public class WeakPassphraseException extends Exception +{ + public WeakPassphraseException (String passphrase) + { + super("Passphrase does not comply with the PassBook rules\n" + + "\t- must contains at least " + + PassBook.MINIMUM_PASSPHRASE_LENGTH + " characters\n" + + "\t- must contain at least one numeric character\n" + + "\t- must contain at least one lower case letter\n" + + "\t- must contain at least one upper case letter\n"); + } +} -- GitLab