commit 66ca16f8b06799a9fef494f40b5f5c2530065330 Author: Ben Roberts Date: Sat Dec 15 02:19:09 2007 +0000 diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..f91abd8 --- /dev/null +++ b/.htaccess @@ -0,0 +1,19 @@ + + + # Magic Quotes are the Root-of-all-Evil and must be burned at the stake. + php_flag magic_quotes_gpc off + + # Enable mod_rewrite for pretty urls + RewriteEngine on + # Treat all rules as starting from this directory + RewriteBase /ecs/ + + # Redirect rating requests specifically to the image generator + RewriteRule ratings/([0-9\.]*) resources/rating.php?r=$1 [L,NC,NS] + + # If the requeted item doesnt already exist, redirect it to our dispatch page + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule (.*) index.php [L,NC,NS] + + diff --git a/code/auth_mysql.php b/code/auth_mysql.php new file mode 100644 index 0000000..cf671db --- /dev/null +++ b/code/auth_mysql.php @@ -0,0 +1,38 @@ + diff --git a/code/config.php b/code/config.php new file mode 100644 index 0000000..4993ec1 --- /dev/null +++ b/code/config.php @@ -0,0 +1,103 @@ + get_web_base_dir(), + 'script-dir' => get_fs_base_dir(), + 'error-code' => 403, + 'error-message' => 'Unknown Error'); + // Placeholder for template fragments + $_template = array( 'messages' => array(), + 'redirect-to' => false, + 'head' => '', + 'title' => 'Default', + 'page' => ''); + // Forward declarations + $_session = null; + + // Templating + $_config['template-file'] = "{$_meta['script-dir']}/templates/default.php"; + + // Homepage + $_config['homepage'] = "{$_meta['script-dir']}/page-sources/home.php"; + + // Sessions + // How long a logged in session will last without activity from the user + $_config['session-login-timeout'] = 60*60*1; // One hour + // How long the username will be stoed in the users cookie + $_config['session-username-timeout'] = 60*60*24*7; // Seven days + + // See session-handler.php for reasons behind the session-network-* config variables + // session-network-mask is used to determine how much of the users IP is hashed into the salt + $_config['session-network-mask'] = 24; + // session-network-mode defines whether the above parameter was passed as a CIDR form, or dotted decimal form + $_config['session-network-mode'] = MASK_CIDR; + + // Maximum number of items to keep in the users session history + $_config['max-history'] = 5; + + // Account Lockout + // How many incorrect authentication attempts before the user is locked out + $_config['lockout-attempts'] = 3; + // How long the account lockout period lasts. During this time, the user will not + // be able to authenticate, even witha valid passwords + $_config['lockout-duration'] = 60*10; // Ten minutes + + + // Authantication + require_once( "{$_meta['script-dir']}/code/iauthenticator.php" ); + $_config['authentication-module'] = 'mysql'; + + // Database + // Mysql connections parameters + $_config['db'] = null; + $_config['mysql'] = array(); + $_config['mysql']['host'] = 'localhost'; + $_config['mysql']['port'] = 3306; + $_config['mysql']['username'] = ''; + $_config['mysql']['password'] = ''; + $_config['mysql']['database'] = ''; + + // Database tables + $_config['mysql']['prefix'] = ''; + + // Connecting to the database + $_db = null; + require_once( "{$_meta['script-dir']}/code/db_mysql.php" ); + + // Only show php error messages if the application is in debug mode + if( isset($_GET['nodebug']) ) $_config['DEBUG'] = false; + if( !$_config['DEBUG'] ) { + set_error_handler( "null_error_handler" ); + set_exception_handler( "null_exception_handler" ); + } + + // Set the default template for all individual pages + $_template['template-file'] = $_config['template-file']; + + + +?> diff --git a/code/db_mysql.php b/code/db_mysql.php new file mode 100644 index 0000000..840aa53 --- /dev/null +++ b/code/db_mysql.php @@ -0,0 +1,21 @@ + diff --git a/code/exceptions.php b/code/exceptions.php new file mode 100644 index 0000000..264cf86 --- /dev/null +++ b/code/exceptions.php @@ -0,0 +1,41 @@ +\n"; + echo '
';print_r($this->getTrace());echo '
'; + } + } + + }; + + + class FatalException extends BaseException { + // Overridden constructor prints an error message, then terminates the application + public function __construct( $message = '', $code = 0 ) { + parent::__construct( $message, $code ); + die( 'FATAL EXCEPTION: ' . $message ); + } + }; + + + class ConfigException extends BaseException {}; + class SessionException extends BaseException {}; + class AccountLockoutException extends BaseException {}; + class AuthenticationException extends BaseException {}; + class ParameterException extends BaseException {}; + class NotImplementedException extends BaseException {}; + +?> diff --git a/code/functions.php b/code/functions.php new file mode 100644 index 0000000..eba209d --- /dev/null +++ b/code/functions.php @@ -0,0 +1,76 @@ + $value ) { + $dest[$key] = $value; + } + } + + function print_rating_graph( $star_rating ) { + global $_meta; +?> + Rated: <?php echo $star_rating; ?> + diff --git a/code/iauthenticator.php b/code/iauthenticator.php new file mode 100644 index 0000000..cb2080b --- /dev/null +++ b/code/iauthenticator.php @@ -0,0 +1,60 @@ + diff --git a/code/iauthorisor.php b/code/iauthorisor.php new file mode 100644 index 0000000..27b7703 --- /dev/null +++ b/code/iauthorisor.php @@ -0,0 +1,58 @@ + diff --git a/code/request_handler.php b/code/request_handler.php new file mode 100644 index 0000000..6885618 --- /dev/null +++ b/code/request_handler.php @@ -0,0 +1,71 @@ +request_string = $request_string; + $this->cache = array(); + } + + public function current_page() { + return $this->request_string; + } + + public function get( $key, $value_pattern = '[^/]*' ) { + // Look in the cache to see if weve already decoded this variable + if( in_array( $key, $this->cache ) ) return $this->cache[ $key ]; + + // Construct the regex to search for /$key/$value/ pairs, and return the $value part + $key = str_replace('£', '\£', $key); + $value_pattern = str_replace('£', '\£', $value_pattern); + $pattern = "£/{$key}/({$value_pattern})(/|\$)£"; + + // Look to see if this variable is in the request string + $count = preg_match( $pattern, $this->request_string, $matches ); + + // See if the variable was set + if( $count == 0 ) return null; + + // Store the result for next time + $this->cache[ $key ] = $matches[1]; + + // And return it to the user + return $matches[1]; + } + + public function construct() { + global $_meta; + + // Varargsy + $args = func_get_args(); + + // Construct the proper request string for these arguments + $request_string = "{$_meta['base-dir']}/"; + $count = count( $args ); + for( $i = 0 ; $i < $count; $i++ ) { + $arg = $args[ $i ]; + // If this item is null, try to find the value of the previous key from the current + // request object as a convenience to the user. It assumes the default value pattern. + if( $arg === null && $i > 0) { + $arg = $this->get( $args[ $i -1 ] ); + } + $request_string .= urlencode($arg) . '/'; + } + + return $request_string; + } + + }; + +?> diff --git a/code/session_handler.php b/code/session_handler.php new file mode 100644 index 0000000..779ffa7 --- /dev/null +++ b/code/session_handler.php @@ -0,0 +1,229 @@ +start_new_session(); + } else { + // The session already exists, check its validity + if( $this->is_session_valid() ) { + + } else { + // BAD USER SESSION, start a new one + $this->start_new_session(); + // And inform the user + $_template['messages'][] = "Bad session, starting a new one"; + } + } + + // Load the Authentication modules + $this->authentication = IAuthenticatorFactory::get( $_config['authentication-module'] ); + $this->authentication->initialise(); + + } + + public function __destruct() { + // Shut down the authentication modules + $this->authentication->uninitialise(); + } + + /* + * Accessors + */ + public function authenticator() { return $this->authentication; } + + /* + * The following methods deal with the user session + */ + + // Initialises all the variables we require in each user session + public function start_new_session() { + global $_config; + + // Set up all the session variables + $_SESSION['initialised'] = true; + $_SESSION['logged_in'] = false; + $_SESSION['username'] = ''; + $_SESSION['hash'] = $this->generate_hash(); + $_SESSION['previous_page'] = $_config['homepage']; + $_SESSION['requested_page'] = ''; + $_SESSION['history'] = array('home'); + $_SESSION['lockout'] = false; + $_SESSION['lockout-attempts'] = 0; + + // Be paranoid, change session id + $this->auth_state_changed(); + } + + public function is_session_valid() { + // Check that the identifying information given by the user matches that which was + // saved in the session when it was created + return ($_SESSION['hash'] == $this->generate_hash()); + } + + /* + * This function should be called whenever the authorisation level + * changes in order to keep the session secure. + * It prevents against Session Fixation (http://www.acros.si/papers/session_fixation.pdf) + */ + public function auth_state_changed() { + session_regenerate_id(); + } + + /* + * This function marks a user as having been logged into the system + */ + private function mark_logged_in_as( $username ) { + $_SESSION['username'] = $username; + $_SESSION['logged_in'] = true; + + // The login state has changed + $this->auth_state_changed(); + } + + /* + * This function marks a user as having been logged out of the system + */ + private function mark_logged_out() { + $_SESSION['logged_in'] = false; + + // The login state has changedd + $this->auth_state_changed(); + } + + /* + * This function returns whether or not the user is logged in + */ + public function is_logged_in() { + return $_SESSION['logged_in']; + } + + /* + * This function returns the username of the currently logged in user + */ + public function username() { + return $_SESSION['username']; + } + + /* + * This function requires a user to be logged in, else an exception is thrown + */ + public function require_logged_in() { + if( !$_SESSION['logged_in'] ) throw new AuthenticationException('You must be logged on to view this resource'); + } + + /* + * This function generates a hash from user identifiable information + * to try and prevent session theft. + * If the session id is stolen by an attacker, chances are some of the + * information used to generate the hash will change, and the session + * will be immediately marked as invalid. This hash is checked for + * consistency on every request. + */ + public function generate_hash() { + global $_config; + // Hash together the following pieces of identifying information which remain constant throughout the session: + // User Agent string - This will be constant for the user, but might not be for the attacker + // Netork Mask - ensures each request is coming from the same network. We may not be able to use the + // while ip, because some ISPs use load-balanced proxies, so subsequent requests may come from a + // different machine ip, but we can still use at least part of the address to filter out would be attackers. + $key = $_SERVER['HTTP_USER_AGENT'] . get_network_mask( $_SERVER['REMOTE_ADDR'], $_config['session-network-mask'], $_config['session-network-mode']); + return md5( $key ); + } + + /* + * The following methods deal with the users history + * These will be used to set up redirection after special pages, such as login + */ + + // Add a new item to the user history + public function history_add_request( $page ) { + global $_config; + // Add this item to the beginning of the history array + array_unshift( $_SESSION['history'], $page ); + // Keep the size below a fixed limit + if( count($_SESSION['history']) > $_config['max-history'] ) { + array_pop( $_SESSION['history'] ); + } + } + + // Remove the current item from the user history + public function history_drop_request() { + // remove the item from the beginning of the array + array_shift( $_SESSION['history'] ); + } + + // Return an item from the user history + public function history_get( $index ) { + if( is_numeric($index) ) { + if( $index < count($_SESSION['history']) ) { + return $_SESSION['history'][$index]; + } + } + } + + /* + * The following methods deal with user authentication and authorisation + */ + + // Log the user in + public function login( $username, $password ) { + global $_config; + + // Check the user hasnt been locked out before trying to login + if( $_SESSION['lockout'] == true ) { + // See if the lockout has expired + if($_SESSION['lockout-expiry'] > time() ) throw new AccountLockoutException('Your session is currently locked as a result of enterring too many incorrect passwords. You will not be able to attempt a login for another ' . date('i \m\i\n(\s), s \s\e\c(\s)', $_SESSION['lockout-expiry']-time())); + else { + // Unset the lockout + $_SESSION['lockout'] = false; + $_SESSION['lockout-expiry'] = 0; + $_SESSION['lockout-attempts'] = 0; + } + } + + + try { + // Attempt to authenticate with these credentials + $this->authentication->authenticate( $username, $password ); + } catch( Exception $e ) { + // Increment the number of failed authentication attempts + $_SESSION['lockout-attempts']++; + // Check the lockout attempts + if( $_SESSION['lockout-attempts'] >= $_config['lockout-attempts'] ) { + $_SESSION['lockout'] = true; + $_SESSION['lockout-expiry'] = time() + $_config['lockout-duration']; + throw new AccountLockoutException('You have enterred an incorrect password too many times, and your session has been locked. You will not be able to attempt another login for the next 10 minutes.'); + } + + // The login failed, rethrow the original exception + throw $e; + } + + // Successful login, update the session state + $this->mark_logged_in_as( $username ); + } + + // Log the current user out + public function logout() { + $this->mark_logged_out(); + } + + }; + +?> diff --git a/deny.htaccess b/deny.htaccess new file mode 100644 index 0000000..a0bb584 --- /dev/null +++ b/deny.htaccess @@ -0,0 +1,2 @@ +Order Allow,Deny +Deny from all diff --git a/index.php b/index.php new file mode 100644 index 0000000..0790792 --- /dev/null +++ b/index.php @@ -0,0 +1,82 @@ +get('page')) ? strtolower($_req->get('page')) : 'home'); + $_page = $_chroot . '/' . $_pagename . '.php'; + + // Capture this request so we know where the user wanted to go + $_session->history_add_request( $_SERVER['REQUEST_URI'] ); + + // Check that this path exists + $_realpath = realpath($_page); + if( $_realpath === false ) throw new Exception('Requested page doesnt exist', 404); + + // Check that the real file exists under the pages directory + if( substr($_realpath, 0, strlen($_chroot)) != $_chroot ) throw new Exception('Forbidden', 403); + + + } catch( Exception $e ) { + $_meta['error-code'] = $e->getCode(); + $_meta['error-message'] = $e->getMessage(); + $_page = "{$_meta['script-dir']}/page-sources/error.php"; + } + + // Capture the output and store it in the template + ob_start(); + + try { + include( $_page ); + } catch( AuthenticationException $e ) { + // Redirect to the login page + $_template['messages'][] = $e->getMessage(); + $_page = "{$_meta['script-dir']}/page-sources/login.php"; + // Get the new page + ob_clean(); + include( $_page ); + } catch( ParameterException $e ) { + // Redirect to the error page + $_meta['error-code'] = $e->getCode(); + $_meta['error-message'] = "Required parameter is either missing, or contains an illegal value: '{$e->getMessage()}'"; + $_page = "{$_meta['script-dir']}/page-sources/error.php"; + // Get the new page + ob_clean(); + include( $_page ); + } + + $_template['page'] = ob_get_contents(); + + // Since we've already caught them, prevent the contents from being + // passed to the browser + ob_end_clean(); + + // Get the template + include( $_template['template-file'] ); + + // Send any remaining output to the browser + ob_end_flush(); + +?> \ No newline at end of file diff --git a/page-sources/cv.php b/page-sources/cv.php new file mode 100644 index 0000000..bef7c47 --- /dev/null +++ b/page-sources/cv.php @@ -0,0 +1,10 @@ + diff --git a/page-sources/error.php b/page-sources/error.php new file mode 100644 index 0000000..8242c23 --- /dev/null +++ b/page-sources/error.php @@ -0,0 +1,15 @@ +history_drop_request(); + // Display the error message, and redirect + $_template['title'] = "Errawr"; + $_template['messages'][] = $_meta['error-message']; + $_template['redirect-to'] = $_session->history_get(0); // Top of the list now + +?> \ No newline at end of file diff --git a/page-sources/home.php b/page-sources/home.php new file mode 100644 index 0000000..a8b50cf --- /dev/null +++ b/page-sources/home.php @@ -0,0 +1,10 @@ + diff --git a/page-sources/login.php b/page-sources/login.php new file mode 100644 index 0000000..ec59b03 --- /dev/null +++ b/page-sources/login.php @@ -0,0 +1,59 @@ + 0 ) { + // Hide this request from the user's history + $_session->history_drop_request(); + // Now it wont matter how many times a user fails authentication, they will always be redirected to the + // page they requested in the first place + + // Check for the presence of the required form fields + if( !isset($_POST['username']) ) throw new ParameterException(); $username = $_POST['username']; + + $password = ''; + if( isset($_POST['password']) ) $password = $_POST['password']; + + // Attempt the login + try { + $_session->login( $username, $password ); + + // Present a message to the user + $_template['messages'][] = 'You have successfully logged in.'; + + // Set a redirection to the page the user was originally on (now the top of the list, because we dropped this page) + $_template['redirect-to'] = $_session->history_get(0); + + } catch (AuthenticationException $e) { + // Authentication failed + $_template['messages'][] = 'Authentication failed'; + _show_login_form(); + } + + } else { + _show_login_form(); + } + + function _show_login_form() { + global $_meta, $_req; + + // Present the login form to the user +?> +
+

+
+ +

+
+ diff --git a/page-sources/logout.php b/page-sources/logout.php new file mode 100644 index 0000000..2574f3d --- /dev/null +++ b/page-sources/logout.php @@ -0,0 +1,17 @@ +logout(); + + // Leave the user a notice, and redirect them back to the home page + $_template['title'] = 'Logout'; + $_template['messages'][] = 'You have successfully been logged out.'; + $_template['redirect-to'] = $_req->construct('page','home'); + + +?> \ No newline at end of file diff --git a/resources/normal.css b/resources/normal.css new file mode 100644 index 0000000..7fd1b58 --- /dev/null +++ b/resources/normal.css @@ -0,0 +1,133 @@ +/* + Layout +*/ + +body { + margin: 0em; + padding: 0em; + font-family: verdana, tahoma, sans-serif; +} + +div.header { + border-bottom: 1px solid grey; + padding-bottom: 0.5em; + margin: 0.5em 1em 1em 1em; +} + +div.sidebar { + border-right: 1px solid grey; + margin: 0em; + padding: 1em; + float: left; + line-height: 1.8em; +} + +div.page { + margin: 1em; + margin-left: 9em; +} + +div.footer { + border-top: 1px solid grey; + margin: 1em; + margin-top: 2em; + padding-top: 0.5em; + height: 3em; + clear: both; + + font-size: smaller; + color: grey; + text-align: center; +} + +div.sidebar ul { + list-style: circle; +} + +/* + Page styles +*/ + +div.header h1 { + font-size: 3.0em; + margin: 0.2em; + margin-left: 0em; +} + + +/* + Utilities +*/ +br.spacer { + clear: both; +} + +div.quote { + margin-left: 1em; + padding-left: 2em; + border: 1px solid moccasin; + background-color: moccasin; + background-image: url('../resources/quote-32.png'); + background-repeat: no-repeat; + background-position: top left; +} + +div.quote blockquote { + margin: 0em; + padding: 0.3em 0.6em 0em 0.3em; + font-style: italic; + background-color: lightyellow; +} + + +/* + Dialog boxes +*/ + +p.message { + border: 1px solid red; + background-color: lightsalmon; + color: darkred; + padding: 10px; +} + +p.information { + border: 1px solid blue; + background-color: powderblue; + color: darkslateblue; + padding: 10px; +} + + +/* + Boxed item +*/ + +div.box { + margin-bottom: 2em; + padding: 1.5em; + border: 1px solid grey; +} + +div.box ul { + width: 20em; + line-height: 1.5em; +} + +div.box span.result { + float: right; +} + + +/* + Forms +*/ +p.loginform { + width: 30em; +} +p.loginform label { + line-height: 200%; +} +p.loginform input { + float: right; +} \ No newline at end of file diff --git a/resources/quote-32.png b/resources/quote-32.png new file mode 100644 index 0000000..abeb954 Binary files /dev/null and b/resources/quote-32.png differ diff --git a/resources/rating.php b/resources/rating.php new file mode 100644 index 0000000..23f6ac5 --- /dev/null +++ b/resources/rating.php @@ -0,0 +1,36 @@ + diff --git a/resources/rating_bg.png b/resources/rating_bg.png new file mode 100644 index 0000000..ee8d806 Binary files /dev/null and b/resources/rating_bg.png differ diff --git a/resources/rating_fg.png b/resources/rating_fg.png new file mode 100644 index 0000000..f618a89 Binary files /dev/null and b/resources/rating_fg.png differ diff --git a/templates/default.php b/templates/default.php new file mode 100644 index 0000000..b0aff0e --- /dev/null +++ b/templates/default.php @@ -0,0 +1,80 @@ + + + + <?php echo $_template['title']; ?> + + + + + + +
+

Ben Roberts

+ MEng Computer Science @ ecs.soton.ac.uk +
+ + + + +
+ 0 ) { + foreach( $_template['messages'] as $_message ) { + ?> +

+ +

+ +

+ +

+ About to be redirected... If nothing happens after 10 seconds, please click here. +

+ +
+ + + + + \ No newline at end of file diff --git a/test.php b/test.php new file mode 100644 index 0000000..b5ff16c --- /dev/null +++ b/test.php @@ -0,0 +1,39 @@ +'; + $req1 = new RequestHandler( "/page/user_reviews/username/tw205/aux&/true/false/" ); + var_dump( $req1->get("page") ); + var_dump( $req1->get("username") ); + var_dump( $req1->get("aux") ); + var_dump( $req1->get("aux&", ".+") ); + + var_dump( $req1->construct('page', $req1->get('page'), 'username', null, 'aux&', null) ); + + echo ''; + } + + if( isset($_GET['session']) ) { + session_start(); + + echo '
'; 
+	 	var_dump($_SESSION);
+	 	echo '
'; + } + +?> \ No newline at end of file