diff --git a/README.md b/README.md deleted file mode 100644 index b614c97b10a8eb64ed7f18feca453bafa5772a4f..0000000000000000000000000000000000000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# SWEN90006-A1-2019 - diff --git a/build.xml b/build.xml new file mode 100644 index 0000000000000000000000000000000000000000..afb3743690e91204371093ab757aad78d123074e --- /dev/null +++ b/build.xml @@ -0,0 +1,79 @@ +<project name="Project" default="default"> + + <target name="check_prog"> + <fail message="Please provide a program to test with -Dprogam=''. The arguments can be +one of {original, mutant-1, mutant-2, mutant-3, mutant-4, mutant-5}"> + <condition> + <or> + <not><isset property="program"/></not> + <not><contains +string="original,mutant-1,mutant-2,mutant-3,mutant-4,mutant-5" substring="${program}"/></not> + </or> + </condition> + </fail> + </target> + + <target name="check_test"> + <fail message="Please provide a test with -Dtest=''. The arguments can be one of +{boundary, partitioning}"> + <condition> + <or> + <not><isset property="test"/></not> + <not><contains string="Boundary,Partitioning" substring="${test}"/></not> + </or> + </condition> + </fail> + </target> + + <target name="compile_prog" depends="check_prog"> + <mkdir dir="classes/programs/${program}" /> + <depend srcdir="programs/${program}" destdir="classes/programs/${program}" +cache=".depcache/programs/${program}" closure="yes"/> + <javac srcdir="programs/${program}" destdir="classes/programs/${program}" +classpath="lib/junit-4.11.jar;lib/hamcrest-core-1.3.jar" includeantruntime="false"/> + </target> + + <target name="compile_orig"> + <mkdir dir="classes/programs/original" /> + <depend srcdir="tests" destdir="classes/programs/original" +cache=".depcache/programs/original" closure="yes"/> + <javac srcdir="programs/original" destdir="classes/programs/original" +classpath="lib/junit-4.11.jar;lib/hamcrest-core-1.3.jar" includeantruntime="false"/> + </target> + + <target name="compile_test" depends="compile_orig, check_test"> + <mkdir dir="classes/tests/${test}"/> + <depend srcdir="tests/${test}" destdir="classes/tests/${test}" +cache=".depcache/tests/${test}" closure="yes"/> + <javac srcdir="tests/${test}" destdir="classes/tests/${test}" +classpath="lib/junit-4.11.jar;lib/hamcrest-core-1.3.jar;classes/programs/original" +includeantruntime="false"/> + </target> + + <target name="test" depends="compile_prog, compile_test"> + <mkdir dir="results"/> + <parallel threadCount="1" timeout="5000"> + <sequential> + <junit printsummary="yes" fork="yes" haltonfailure="yes"> + <classpath> + <pathelement path="classes/programs/${program}"/> + <pathelement path="classes/tests/${test}"/> + <pathelement path="lib/junit-4.11.jar"/> + <pathelement path="lib/hamcrest-core-1.3.jar"/> + </classpath> + <formatter type="plain"/> + <test name="swen90006.passbook.${test}Tests" todir="results" +outfile="${test}_results.${program}"/> + </junit> + </sequential> + </parallel> + </target> + + <target name="clean"> + <delete dir="classes"/> + <delete dir=".depcache"/> + <delete><fileset dir="results" includes="**/*"/></delete> + </target> + +</project> + diff --git a/lib/hamcrest-core-1.3.jar b/lib/hamcrest-core-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..9d5fe16e3dd37ebe79a36f61f5d0e1a69a653a8a Binary files /dev/null and b/lib/hamcrest-core-1.3.jar differ diff --git a/lib/junit-4.11.jar b/lib/junit-4.11.jar new file mode 100644 index 0000000000000000000000000000000000000000..aaf74448492932e95902b40a70c7a4da5bad4744 Binary files /dev/null and b/lib/junit-4.11.jar differ diff --git a/programs/mutant-1/swen90006/passbook/AlreadyLoggedInException.java b/programs/mutant-1/swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000000000000000000000000000000000000..8bbbd85f001ac2939928e48c8abfe63d253e36bc --- /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 0000000000000000000000000000000000000000..74370b1668a24f83dae080aa184f8c39bda8bc79 --- /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 0000000000000000000000000000000000000000..cdfc80b86dd71e1fc5bfc400538208e492c8bd9e --- /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 0000000000000000000000000000000000000000..230cf58af6c85eb884849950e6fe1070018f09a5 --- /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 0000000000000000000000000000000000000000..7edb9168412549fa63ee16bc3a2665069e0047af --- /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 0000000000000000000000000000000000000000..ca4c5270a875723fd8d6cbae389227ea685b13d4 --- /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 0000000000000000000000000000000000000000..8f70502f35d86c4e9e504be1e89a750d10f0d9cb --- /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 0000000000000000000000000000000000000000..e0f9024d3b95635c9428d5b80bc29ff5b86d7df5 --- /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 logic details for a URL of a user. If the url + * username or password are null, the username/password details + * for this URL are 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 0000000000000000000000000000000000000000..c4bc33b29151d824244eb0d5105db51e2c7473d1 --- /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 0000000000000000000000000000000000000000..8bbbd85f001ac2939928e48c8abfe63d253e36bc --- /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 0000000000000000000000000000000000000000..74370b1668a24f83dae080aa184f8c39bda8bc79 --- /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 0000000000000000000000000000000000000000..cdfc80b86dd71e1fc5bfc400538208e492c8bd9e --- /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 0000000000000000000000000000000000000000..230cf58af6c85eb884849950e6fe1070018f09a5 --- /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 0000000000000000000000000000000000000000..7edb9168412549fa63ee16bc3a2665069e0047af --- /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 0000000000000000000000000000000000000000..ca4c5270a875723fd8d6cbae389227ea685b13d4 --- /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 0000000000000000000000000000000000000000..8f70502f35d86c4e9e504be1e89a750d10f0d9cb --- /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 0000000000000000000000000000000000000000..e0f9024d3b95635c9428d5b80bc29ff5b86d7df5 --- /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 logic details for a URL of a user. If the url + * username or password are null, the username/password details + * for this URL are 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 0000000000000000000000000000000000000000..c4bc33b29151d824244eb0d5105db51e2c7473d1 --- /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 0000000000000000000000000000000000000000..8bbbd85f001ac2939928e48c8abfe63d253e36bc --- /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 0000000000000000000000000000000000000000..74370b1668a24f83dae080aa184f8c39bda8bc79 --- /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 0000000000000000000000000000000000000000..cdfc80b86dd71e1fc5bfc400538208e492c8bd9e --- /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 0000000000000000000000000000000000000000..230cf58af6c85eb884849950e6fe1070018f09a5 --- /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 0000000000000000000000000000000000000000..7edb9168412549fa63ee16bc3a2665069e0047af --- /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 0000000000000000000000000000000000000000..ca4c5270a875723fd8d6cbae389227ea685b13d4 --- /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 0000000000000000000000000000000000000000..8f70502f35d86c4e9e504be1e89a750d10f0d9cb --- /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 0000000000000000000000000000000000000000..e0f9024d3b95635c9428d5b80bc29ff5b86d7df5 --- /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 logic details for a URL of a user. If the url + * username or password are null, the username/password details + * for this URL are 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 0000000000000000000000000000000000000000..c4bc33b29151d824244eb0d5105db51e2c7473d1 --- /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 0000000000000000000000000000000000000000..8bbbd85f001ac2939928e48c8abfe63d253e36bc --- /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 0000000000000000000000000000000000000000..74370b1668a24f83dae080aa184f8c39bda8bc79 --- /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 0000000000000000000000000000000000000000..cdfc80b86dd71e1fc5bfc400538208e492c8bd9e --- /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 0000000000000000000000000000000000000000..230cf58af6c85eb884849950e6fe1070018f09a5 --- /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 0000000000000000000000000000000000000000..7edb9168412549fa63ee16bc3a2665069e0047af --- /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 0000000000000000000000000000000000000000..ca4c5270a875723fd8d6cbae389227ea685b13d4 --- /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 0000000000000000000000000000000000000000..8f70502f35d86c4e9e504be1e89a750d10f0d9cb --- /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 0000000000000000000000000000000000000000..e0f9024d3b95635c9428d5b80bc29ff5b86d7df5 --- /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 ('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 logic details for a URL of a user. If the url + * username or password are null, the username/password details + * for this URL are 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 0000000000000000000000000000000000000000..c4bc33b29151d824244eb0d5105db51e2c7473d1 --- /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 0000000000000000000000000000000000000000..8bbbd85f001ac2939928e48c8abfe63d253e36bc --- /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 0000000000000000000000000000000000000000..74370b1668a24f83dae080aa184f8c39bda8bc79 --- /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 0000000000000000000000000000000000000000..cdfc80b86dd71e1fc5bfc400538208e492c8bd9e --- /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 0000000000000000000000000000000000000000..230cf58af6c85eb884849950e6fe1070018f09a5 --- /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 0000000000000000000000000000000000000000..7edb9168412549fa63ee16bc3a2665069e0047af --- /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 0000000000000000000000000000000000000000..ca4c5270a875723fd8d6cbae389227ea685b13d4 --- /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 0000000000000000000000000000000000000000..8f70502f35d86c4e9e504be1e89a750d10f0d9cb --- /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 0000000000000000000000000000000000000000..e0f9024d3b95635c9428d5b80bc29ff5b86d7df5 --- /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 logic details for a URL of a user. If the url + * username or password are null, the username/password details + * for this URL are 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 0000000000000000000000000000000000000000..c4bc33b29151d824244eb0d5105db51e2c7473d1 --- /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"); + } +} diff --git a/programs/original/swen90006/passbook/AlreadyLoggedInException.java b/programs/original/swen90006/passbook/AlreadyLoggedInException.java new file mode 100644 index 0000000000000000000000000000000000000000..8bbbd85f001ac2939928e48c8abfe63d253e36bc --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/DuplicateUserException.java b/programs/original/swen90006/passbook/DuplicateUserException.java new file mode 100644 index 0000000000000000000000000000000000000000..74370b1668a24f83dae080aa184f8c39bda8bc79 --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/IncorrectPassphraseException.java b/programs/original/swen90006/passbook/IncorrectPassphraseException.java new file mode 100644 index 0000000000000000000000000000000000000000..cdfc80b86dd71e1fc5bfc400538208e492c8bd9e --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/InvalidSessionIDException.java b/programs/original/swen90006/passbook/InvalidSessionIDException.java new file mode 100644 index 0000000000000000000000000000000000000000..230cf58af6c85eb884849950e6fe1070018f09a5 --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/NoSuchURLException.java b/programs/original/swen90006/passbook/NoSuchURLException.java new file mode 100644 index 0000000000000000000000000000000000000000..7edb9168412549fa63ee16bc3a2665069e0047af --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/NoSuchUserException.java b/programs/original/swen90006/passbook/NoSuchUserException.java new file mode 100644 index 0000000000000000000000000000000000000000..ca4c5270a875723fd8d6cbae389227ea685b13d4 --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/Pair.java b/programs/original/swen90006/passbook/Pair.java new file mode 100644 index 0000000000000000000000000000000000000000..8f70502f35d86c4e9e504be1e89a750d10f0d9cb --- /dev/null +++ b/programs/original/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/original/swen90006/passbook/PassBook.java b/programs/original/swen90006/passbook/PassBook.java new file mode 100644 index 0000000000000000000000000000000000000000..e0f9024d3b95635c9428d5b80bc29ff5b86d7df5 --- /dev/null +++ b/programs/original/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 logic details for a URL of a user. If the url + * username or password are null, the username/password details + * for this URL are 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/original/swen90006/passbook/WeakPassphraseException.java b/programs/original/swen90006/passbook/WeakPassphraseException.java new file mode 100644 index 0000000000000000000000000000000000000000..c4bc33b29151d824244eb0d5105db51e2c7473d1 --- /dev/null +++ b/programs/original/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/tests/swen90006/passbook/BoundaryTests.java b/tests/swen90006/passbook/BoundaryTests.java new file mode 100644 index 0000000000000000000000000000000000000000..cba90f1b376eeb0774a97a4924ea43d16f0dba39 --- /dev/null +++ b/tests/swen90006/passbook/BoundaryTests.java @@ -0,0 +1,25 @@ +package swen90006.passbook; + +import java.util.List; +import java.util.ArrayList; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileSystems; + +import org.junit.*; +import static org.junit.Assert.*; + +//By extending PartitioningTests, we inherit tests from the script +public class BoundaryTests + extends PartitioningTests +{ + //Add another test + @Test public void anotherTEst() + { + //include a message for better feedback + final int expected = 2; + final int actual = 2; + assertEquals("Some failure message", expected, actual); + } +} diff --git a/tests/swen90006/passbook/PartitioningTests.java b/tests/swen90006/passbook/PartitioningTests.java new file mode 100644 index 0000000000000000000000000000000000000000..e7a6efd37025b6e88fed7b30d618c3c430105ad5 --- /dev/null +++ b/tests/swen90006/passbook/PartitioningTests.java @@ -0,0 +1,68 @@ +package swen90006.passbook; + +import java.util.List; +import java.util.ArrayList; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileSystems; + +import org.junit.*; +import static org.junit.Assert.*; + +public class PartitioningTests +{ + protected PassBook pb; + + //Any method annotated with "@Before" will be executed before each test, + //allowing the tester to set up some shared resources. + @Before public void setUp() + { + pb = new PassBook(); + } + + //Any method annotated with "@After" will be executed after each test, + //allowing the tester to release any shared resources used in the setup. + @After public void tearDown() + { + } + + //Any method annotation with "@Test" is executed as a test. + @Test public void aTest() + { + //the assertEquals method used to check whether two values are + //equal, using the equals method + final int expected = 2; + final int actual = 1 + 1; + assertEquals(expected, actual); + } + + @Test public void anotherTest() + throws DuplicateUserException, WeakPassphraseException + { + pb.addUser("passbookUsername", "properPassphrase1"); + + //the assertTrue method is used to check whether something holds. + assertTrue(pb.isUser("passbookUsername")); + assertFalse(pb.isUser("nonUser")); + } + + //To test an exception, specify the expected exception after the @Test + @Test(expected = java.io.IOException.class) + public void anExceptionTest() + throws Throwable + { + throw new java.io.IOException(); + } + + //This test should fail. + //To provide additional feedback when a test fails, an error message + //can be included + @Test public void aFailedTest() + { + //include a message for better feedback + final int expected = 2; + final int actual = 1 + 2; + assertEquals("Some failure message", expected, actual); + } +}