diff --git a/.gitignore b/.gitignore index 7aa7065..ac8f2f2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ dumps incoming_symbols logs htdocs/lib/logs -.hg +.hg* diff --git a/htdocs/.htaccess b/htdocs/.htaccess new file mode 100644 index 0000000..8aa276d --- /dev/null +++ b/htdocs/.htaccess @@ -0,0 +1 @@ +php_flag display_errors on diff --git a/htdocs/account.php b/htdocs/account.php new file mode 100644 index 0000000..81ca00e --- /dev/null +++ b/htdocs/account.php @@ -0,0 +1,59 @@ +isAnonymous()) +{ + $S->loginRedirect(); +} +$user = $S->user; + +if (isset($_POST["user_id"])) +{ + $user->name = $_POST["name"]; + $msg = $user->update() ? "Information updated" : "Update failed"; + http::redirect("/account.php?msg=" . urlencode($msg)); +} +Layout::header(); +?> + +
This application is used for analyzing crash reports and statitstics for the Singularity +Viewer project.
+ +Access to this tool is granted to the members of the development team. +The main goal is to identify the most common problems and improve the expierience +for the users of Singularity Viewer.
+ +isAnonymous()) +{ + if ($S->user->isAllowed()) + { + print 'Your account has been granted access.
'; + } + else + { + print 'Your account has no access to the system at this time.
'; + } +} +Layout::footer(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/lib/.htaccess b/htdocs/lib/.htaccess new file mode 100644 index 0000000..8d2f256 --- /dev/null +++ b/htdocs/lib/.htaccess @@ -0,0 +1 @@ +deny from all diff --git a/htdocs/lib/DBH.php b/htdocs/lib/DBH.php new file mode 100644 index 0000000..d8f186b --- /dev/null +++ b/htdocs/lib/DBH.php @@ -0,0 +1,185 @@ +db_name = $db_name; + $this->db_pass = $db_pass; + $this->db_user = $db_user; + $this->db_host = $db_host; + + $this->dbh = @mysql_connect($db_host, $db_user, $db_pass); + + if (!$this->dbh) { + DBH::log("[error] connection to database failed"); + DBH::log("[error] connection string used '$conn_str'"); + return false; + } + + if (!mysql_select_db($db_name)) { + DBH::log("[error] database {$db_name} dose not exist"); + return false; + } + + + $this->query("SET SQL_MODE='TRADITIONAL'"); + $this->query("SET NAMES 'utf8'"); + return true; + + } + + function query($q) + { + $res = @mysql_query($q, $this->dbh); + + if (!$res) { + DBH::log("[error] ".$q); + DBH::log("[error_msg] " . mysql_error($this->dbh)); + $this->last_error = mysql_error($this->dbh); + + $e = debug_backtrace(); + $c = count($e); + $btr = ""; + + for ($i=0; $i<$c; $i++) { + $btr .= "{$e[$i]['class']}::{$e[$i]['function']} {$e[$i]['file']}({$e[$i]['line']})\n"; + } + + DBH::log("[backtrace]\n".$btr); + + return false; + } else { + if ($res !== TRUE) { + $result_id = (int)$res; + if (!isset($this->field_desc[$result_id])) { + $nf = mysql_num_fields($res); + for ($i=0; $i<$nf; $i++) { + $this->field_desc[$result_id][mysql_field_name($res, $i)] = mysql_field_type($res, $i); + } + } + } + DBH::log("[success] ".$q); + return $res; + } + } + + function loadFromDbRow(&$obj, $res, $row) + { + foreach ($row as $symbolicName => $nativeName){ + if ($nativeName && ($this->field_desc[(int)$res][$symbolicName] == "timestamp" || + $this->field_desc[(int)$res][$symbolicName] == "date" || + $this->field_desc[(int)$res][$symbolicName] == "datetime")) { + $obj->{$symbolicName} = strtotime($nativeName); + } else { + $obj->{$symbolicName} = $nativeName; + } + } + return true; + } + + function insertID() + { + return @mysql_insert_id($this->dbh); + } + + function numRows($res) + { + return @mysql_num_rows($res); + } + + function affectedRows() + { + return @mysql_affected_rows($this->dbh); + } + + function fieldName($res, $num) + { + return @mysql_field_name($res, $num); + } + + function numFields($res) + { + return @mysql_num_fields($res); + } + + function fetchRow($res) + { + return @mysql_fetch_assoc($res); + } + + function begin() + { + return $this->query('begin'); + } + + function rollback() + { + return $this->query('rollback'); + } + + function commit() + { + return $this->query('commit'); + } + + /* FIXME: port from postgres */ + function nextId($seq) + { + $res = $this->query("select nextval('$seq') as n"); + + if (!$res || !$row = $this->fetchRow($res)) { + return false; + } else { + return (int)$row['n']; + } + } + + /* Date time conversion */ + function db2unix($s) + { + return strtotime($s); + } + +} +/* +* Local variables: +* tab-width: 4 +* c-basic-offset: 4 +* End: +* vim600: sw=4 ts=4 fdm=marker +* vim<600: sw=4 ts=4 +*/ +?> \ No newline at end of file diff --git a/htdocs/lib/GoogleOpenID.php b/htdocs/lib/GoogleOpenID.php new file mode 100644 index 0000000..abe5e1e --- /dev/null +++ b/htdocs/lib/GoogleOpenID.php @@ -0,0 +1,478 @@ +redirect(); + * + * OR + * + * $handle = GoogleOpenID::getAssociationHandle(); // <--save this! valid for two weeks! + * + * $googleGateway = GoogleOpenID::createRequest("http://www.mydomain.com/checkauth.php", $handle, true); + * $googleGateway->redirect(); + * + * + * When you want to recieve a Google OpenID response, simply pass the given + * parameters to GoogleOpenID::create(). In most cases, you should just be able + * to pass the $_GET variable. + * + * To continue the previous example, the following code would go in checkauth.php + * + * $googleResponse = GoogleOpenID::create($_GET); + * $sucess = $googleResponse->success();//true or false + * $user_identity = $googleResponse->identity();//the user's ID + * $user_email = $googleResponse->email();//the user's email + * + * OR, even easier + * + * $googleResponse = GoogleOpenID::getResponse(); // <-- automatically reads $_GET + * + * Advanced users can create a slightly more customized request using the create + * method. It accepts an associative array of openid parameters and sets those + * that it deems appropriate to set (mostly, the parameters that don't have a + * definite value when interacting with Google) + * + * + * Full class signature: + * + * public static createRequest(String, [String], [Boolean]) + * public static getResponse() + * public static create(Array) + * public static getAssociationHandle([String]) + * public static getEndPoint() + * public redirect() + * public getArray() + * public endPoint() + * public success() + * public assoc_handle() + * public email() + * public identity() + * + * Features to implement: + * + * -In constructor, fix relative->absolute URL conversion (it is messy/buggy) + * -In getAssociationHandle(), use encryption + * -Verify Google's response with signed, sig etc + ****************************************************************************/ + + class GoogleOpenID{ + //the google discover url + const google_discover_url = "https://www.google.com/accounts/o8/id"; + + //some constant parameters + const openid_ns = "http://specs.openid.net/auth/2.0"; + //required for email attribute exchange + const openid_ns_ext1 = "http://openid.net/srv/ax/1.0"; + const openid_ext1_mode = "fetch_request"; + const openid_ext1_type_email = "http://schema.openid.net/contact/email"; + const openid_ext1_required = "email"; + + //parameters set by constructor + private $mode;//the mode (checkid_setup, id_res, or cancel) + private $response_nonce; + private $return_to;//return URL + private $realm;//the realm that the user is being asked to trust + private $assoc_handle;//the association handle between this service and Google + private $claimed_id;//the id claimed by the user + private $identity;//for google, this is the same as claimed_id + private $signed; + private $sig; + private $email;//the user's email address + + //if true, fetch email address + private $require_email; + + //private constructor + private function GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email){ + + //if assoc_handle is null, fetch one + if(is_null($assoc_handle)) + $assoc_handle = GoogleOpenID::getAssociationHandle(); + + //if return_to is a relative URL, make it absolute + if(stripos($return_to, "http://")!==0 && + stripos($return_to, "https://")!==0){ + //if the first character is a slash, delete it + if(substr($return_to, 0, 1)=="/") + $return_to = substr($return_to, 1); + //get the position of server_name + $server_name_pos = stripos($return_to, $_SERVER['SERVER_NAME']); + //if server_name is already at position zero + if($server_name_pos != false && $server_name_pos==0){ + $return_to = "http://".$return_to; + } else { + $return_to = "http://".$_SERVER['SERVER_NAME']."/".$return_to; + }//else (server name not at position zero) + }//if return_to is relative + + //if realm is null, attempt to set it via return_to + if(is_null($realm)){ + //if return_to is set + if(!is_null($return_to)){ + $pieces = parse_url($return_to); + $realm = $pieces['scheme']."://".$pieces['host']; + }//if return_to set + }//if realm null + + $this->mode = $mode; + $this->op_endpoint = $op_endpoint; + $this->response_nonce = $response_nonce; + $this->return_to = $return_to; + $this->realm = $realm; + $this->assoc_handle = $assoc_handle; + $this->claimed_id = $claimed_id; + $this->identity = $claimed_id; + $this->signed = $signed; + $this->sig = $sig; + $this->email = $email; + $this->require_email = ($require_email) ? true : false; + }//GoogleOpenID + + //static creator that accepts only a return_to URL + //this creator should be used when creating a GoogleOpenID for a redirect + public static function createRequest($return_to, $assoc_handle=null, $require_email=false){ + return new GoogleOpenID("checkid_setup", null, null, $return_to, null, $assoc_handle, "http://specs.openid.net/auth/2.0/identifier_select", null, null, null, $require_email); + }//createRequest + + //static creator that accepts an associative array of parameters and + //sets only the setable attributes (does not overwrite constants) + public static function create($params){ + //loop through each parameter + foreach($params as $param => $value){ + switch($param){ + case "openid_mode": + //check validity of mode + if($value=="checkid_setup" || + $value=="id_res" || + $value=="cancel") + $mode = $value; + else + $mode = "cancel"; + continue 2; + + case "openid_op_endpoint": + $op_endpoint = $value; + continue 2; + + case "openid_response_nonce": + $response_nonce = $value; + continue 2; + + case "openid_return_to": + $return_to = $value; + continue 2; + + case "openid_realm": + $realm = $value; + continue 2; + + case "openid_assoc_handle": + $assoc_handle = $value; + continue 2; + + case "openid_claimed_id": + $claimed_id = $value; + continue 2; + + case "openid_identity": + $claimed_id = $value; + continue 2; + + case "openid_signed": + $signed = $value; + continue 2; + + case "openid_sig": + $sig = $value; + continue 2; + + case "openid_ext1_value_email": + $email = $value; + continue 2; + + case "require_email": + $require_email = $value; + continue 2; + + default: + continue 2; + }//switch param + }//loop through params + + //if require email is not set, set it to false + if(!is_bool($require_email)) + $require_email = false; + //if mode is not set, set to default for redirection + if(is_null($mode)) + $mode = "checkid_setup"; + //if return_to is not set and mode is checkid_setup, throw an error + if(is_null($return_to) && $mode=="checkid_setup") + throw new Exception("GoogleOpenID.create() needs parameter openid.return_to"); + + //return a new GoogleOpenID with the given parameters + return new GoogleOpenID($mode, $op_endpoint, $response_nonce, $return_to, $realm, $assoc_handle, $claimed_id, $signed, $sig, $email, $require_email); + }//create + + //creates and returns a GoogleOpenID from the $_GET variable + public static function getResponse(){ + return GoogleOpenID::create($_GET); + }//getResponse + + //fetches an association handle from google. association handles are valid + //for two weeks, so coders should do their best to save association handles + //externally and pass them to createRequest() + //NOTE: This function does not use encryption, but it SHOULD! At the time + //I wrote this I wanted it done fast, and I couldn't seem to find a good + //two-way SHA-1 or SHA-256 library for PHP. Encryption is not my thing, so + //it remains unimplemented. + public static function getAssociationHandle($endpoint=null){ + //if no endpoint given + if(is_null($endpoint)) + //fetch one from Google + $request_url = GoogleOpenID::getEndPoint(); + //if endpoint given, set it + else + $request_url = $endpoint; + + //append parameters (these never change) + $request_url .= "?openid.ns=".urlencode(GoogleOpenID::openid_ns); + $request_url .= "&openid.mode=associate"; + $request_url .= "&openid.assoc_type=HMAC-SHA1"; + $request_url .= "&openid.session_type=no-encryption"; + + //create a CURL session with the request URL + $c = curl_init($request_url); + + //set a few options + curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt($c, CURLOPT_HEADER, false); + + //get the contents of request URL + $request_contents = curl_exec($c); + + //close the CURL session + curl_close($c); + + //a handle to be returned + $assoc_handle = null; + + //split the response into lines + $lines = explode("\n", $request_contents); + + //loop through each line + foreach($lines as $line){ + //if this line is assoc_handle + if(substr($line, 0, 13)=="assoc_handle:"){ + //save the assoc handle + $assoc_handle = substr($line, 13); + //exit the loop + break; + }//if this line is assoc_handle + }//loop through lines + + //return the handle + return $assoc_handle; + }//getAssociationHandle + + //fetches an endpoint from Google + public static function getEndPoint(){ + //fetch the request URL + $request_url = GoogleOpenID::google_discover_url; + + //create a CURL session with the request URL + $c = curl_init($request_url); + + //set a few options + curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt($c, CURLOPT_HEADER, false); + + //fetch the contents of the request URL + $request_contents = curl_exec($c); + + //close the CURL session + curl_close($c); + + //create a DOM document so we can extract the URI element + $domdoc = new DOMDocument(); + $domdoc->loadXML($request_contents); + + //fetch the contents of the URI element + $uri = $domdoc->getElementsByTagName("URI"); + $uri = $uri->item(0)->nodeValue; + + //return the given URI + return $uri; + }//getEndPoint + + //returns an associative array of all openid parameters for this openid + //session. the array contains all the GET attributes that would be sent + //or that have been recieved, meaning: + // + //if mode = "cancel" returns only the mode and ns attributes + //if mode = "id_res" returns all attributes that are not null + //if mode = "checkid_setup" returns only attributes that need to be sent + // in the HTTP request + public function getArray(){ + //an associative array to return + $ret = array(); + + $ret['openid.ns'] = GoogleOpenID::openid_ns; + + //if mode is cancel, return only ns and mode + if($this->mode=="cancel"){ + $ret['openid.mode'] = "cancel"; + return $ret; + }//if cancel + + //set attributes that are returned for all cases + if(!is_null($this->claimed_id)){ + $ret['openid.claimed_id'] = $this->claimed_id; + $ret['openid.identity'] = $this->claimed_id; + } + if(!is_null($this->return_to)) + $ret['openid.return_to'] = $this->return_to; + if(!is_null($this->realm)) + $ret['openid.realm'] = $this->realm; + if(!is_null($this->assoc_handle)) + $ret['openid.assoc_handle'] = $this->assoc_handle; + if(!is_null($this->mode)) + $ret['openid.mode'] = $this->mode; + + //set attributes that are returned only if this is a request + //and if getting email is required OR if this is a response and the + //email is given + if(($this->mode=="checkid_setup" AND $this->require_email) OR + ($this->mode=="id_res" AND !is_null($this->email))){ + $ret['openid.ns.ext1'] = GoogleOpenID::openid_ns_ext1; + $ret['openid.ext1.mode'] = GoogleOpenID::openid_ext1_mode; + $ret['openid.ext1.type.email'] = GoogleOpenID::openid_ext1_type_email; + $ret['openid.ext1.required'] = GoogleOpenID::openid_ext1_required; + if(!is_null($this->email)) + $ret['openid.ext1.value.email'] = $this->email; + }//if redirect and get email + + //set attributes that are returned only if this is a response + if($this->mode=="id_res"){ + $ret['openid.op_endpoint'] = $this->op_endpoint; + if(!is_null($this->response_nonce)) + $ret['openid.response_nonce'] = $this->response_nonce; + if(!is_null($this->signed)) + $ret['openid.signed'] = $this->signed; + if(!is_null($this->sig)) + $ret['openid.sig'] = $this->sig; + } + + //return the array + return $ret; + }//getArray + + //sends a request to google and fetches the url to which google is asking + //us to redirect (unless the endpoint is already known, in which case the + //function simply returns it) + public function endPoint(){ + //if we know the value of op_endpoint already + if(!is_null($this->op_endpoint)) + return $this->op_endpoint; + + //fetch the endpoint from Google + $endpoint = GoogleOpenID::getEndPoint(); + + //save it + $this->op_endpoint = $endpoint; + + //return the endpoint + return $endpoint; + }//getedPoint + + //returns the URL to which we should send a request (including all GET params) + public function getRequestURL(){ + //get all parameters + $params = $this->getArray(); + + //the base URL + $url = $this->endPoint(); + + //flag indicating whether to set a '?' or an '&' + $first_attribute = true; + + //loop through all params + foreach($params as $param => $value){ + //if first attribute print a ?, else print a & + if($first_attribute){ + $url .= "?"; + $first_attribute = false; + } else { + $url .= "&"; + }//else (not first attribute) + + $url .= urlencode($param) . "=" . urlencode($value); + }//loop through params + + //return the URL + return $url; + }//getRequestURL + + //redirects the browser to the appropriate request URL + public function redirect(){ + header("Location: ".$this->getRequestURL()); + }//redirect + + //returns true if the response was a success + public function success(){ + return ($this->mode=="id_res"); + }//success + + //returns the identity given in the response + public function identity(){ + if($this->mode!="id_res") + return null; + else + return $this->claimed_id; + }//identity + + //returns the email given in the response + public function email(){ + if($this->mode!="id_res") + return null; + else + return $this->email; + }//email + + //returns the assoc_handle + public function assoc_handle(){ + return $this->assoc_handle(); + }//assoc_handle + }//class GoogleOpenID +?> diff --git a/htdocs/lib/Layout.php b/htdocs/lib/Layout.php new file mode 100644 index 0000000..1329739 --- /dev/null +++ b/htdocs/lib/Layout.php @@ -0,0 +1,157 @@ + +* @copyright Copyright © 2012, Latif Khalifa +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files +* (the "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* - The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +class Layout +{ + + function since($since) + { + $since = time() - $since; + $chunks = array( + array(60 * 60 * 24 * 365 , 'year'), + array(60 * 60 * 24 * 30 , 'month'), + array(60 * 60 * 24 * 7, 'week'), + array(60 * 60 * 24 , 'day'), + array(60 * 60 , 'hour'), + array(60 , 'minute'), + array(1 , 'second') + ); + + for ($i = 0, $j = count($chunks); $i < $j; $i++) { + $seconds = $chunks[$i][0]; + $name = $chunks[$i][1]; + if (($count = floor($since / $seconds)) != 0) { + break; + } + } + + $print = ($count == 1) ? '1 '.$name : "$count {$name}s"; + return $print; + } + + function header() + { + global $S; + + $menu = array(); + + if ($S->isAnonymous()) + { + $item = new stdClass; + $item->label = "Login"; + $item->link = "/login.php"; + $menu[] = $item; + } + else + { + if ($S->user->isAllowed()) + { + $item = new stdClass; + $item->label = "Crash reports"; + $item->link = "/crashes.php"; + $menu[] = $item; + + $item = new stdClass; + $item->label = "Statistics"; + $item->link = "/statistics.php"; + $menu[] = $item; + } + + if ($S->user->isAdmin()) + { + $item = new stdClass; + $item->label = "Users"; + $item->link = "/users.php"; + $menu[] = $item; + } + + $item = new stdClass; + $item->label = "My Account ({$S->user->email})"; + $item->link = "/account.php"; + $menu[] = $item; + + $item = new stdClass; + $item->label = "Logout"; + $item->link = "/logout.php"; + $menu[] = $item; + } + + ?> + + + + + + +| Singularity Main Site | +About | +Issue Tracker | +Source Tracker | +© 2013 Singularity Viewer Project | +
+ +"; + exit; + } + + + public static function redirect($url) + { + http::redirectAbsolute(URL_ROOT . $url); + + } + + public static function notFound() + { + header("HTTP/1.1 404 Not Found"); + print " +
++
+
+
Please use your Google Account to login.
+ +Login Failed"; + +Layout::footer(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/logout.php b/htdocs/logout.php new file mode 100644 index 0000000..0f35e4c --- /dev/null +++ b/htdocs/logout.php @@ -0,0 +1,15 @@ +remove(); +http::redirect("/"); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/process_login.php b/htdocs/process_login.php new file mode 100644 index 0000000..94f169c --- /dev/null +++ b/htdocs/process_login.php @@ -0,0 +1,49 @@ +success();//true or false +if (!$success) +{ + http::redirect("/login_failed.php"); +} + +$user_identity = $google_response->identity();//the user's ID +$user_email = $google_response->email();//the user's email + +$user = User::getByLogin($user_identity); + +if (!$user) +{ + $user = new User(); + $user->email = $user_email; + $user->is_admin = 0; + $user->is_allowed = 0; + $user->login = $user_identity; + if (!$user->save()) + { + http::redirect("/login_failed.php"); + } +} + +$S->user = $user; +$S->user_id = $user->user_id; +$S->authenticated = 1; +$S->update(); + +if (isset($_GET['caller'])) +{ + http::redirect($_GET['caller']); +} +http::redirect('/'); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/htdocs/report.php b/htdocs/report.php new file mode 100644 index 0000000..08af7f3 --- /dev/null +++ b/htdocs/report.php @@ -0,0 +1,32 @@ +query($query)) + { + $report_id = $DB->insertID(); + } +} + + +header("Content-Type: application/llsd+xml"); +print '| ID | +Name | +Access | +Actions | +|
|---|---|---|---|---|
| user_id ?> | +name); ?> | +email); ?> | +isAdmin() ? "Admin" : ($users[$i]->isAllowed() ? "Granted" : "No"); ?> | ++isAdmin()) + { + $action = $users[$i]->isAllowed() ? "revoke" : "grant"; + $url = URL_ROOT . "/users.php?action=$action&id=" . (int)$users[$i]->user_id; + $delete = URL_ROOT . "/users.php?action=remove&id=" . (int)$users[$i]->user_id; + print "{$action} access, "; + print "delete account"; + } +?> + | +