diff options
author | David Veenstra <davidjulianveenstra@gmail.com> | 2015-09-12 14:03:05 +0200 |
---|---|---|
committer | Arnold Daniels <arnold@jasny.net> | 2015-09-27 16:54:19 +0200 |
commit | 09250d81225398ec8e8b07191c74a8f82df9578d (patch) | |
tree | 4c766c2a4ad9881f485a47a497436c5e1d0f438e | |
parent | c2932ac5e26c3aad4a5738a32531fb1a59760a46 (diff) | |
download | sso-09250d81225398ec8e8b07191c74a8f82df9578d.zip sso-09250d81225398ec8e8b07191c74a8f82df9578d.tar.gz sso-09250d81225398ec8e8b07191c74a8f82df9578d.tar.bz2 |
work for #13 #14 #15: tests, cacheing, examples
27 files changed, 414 insertions, 343 deletions
diff --git a/codeception.yml b/codeception.yml new file mode 100644 index 0000000..3a8fca8 --- /dev/null +++ b/codeception.yml @@ -0,0 +1,21 @@ +actor: Tester +paths: + tests: tests + log: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +settings: + bootstrap: _bootstrap.php + colors: true + memory_limit: 1024M +extensions: + enabled: + - Codeception\Extension\RunFailed +modules: + config: + Db: + dsn: '' + user: '' + password: '' + dump: tests/_data/dump.sql diff --git a/composer.json b/composer.json index 28b4b23..8f6bee0 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,15 @@ }, "require": { "php": ">=5.3.0", - "codeception/codeception": "*" + "codeception/codeception": "*", + "desarrolla2/cache": "dev-master" }, "autoload": { "psr-4": { "Jasny\\SSO\\": "src/" } + }, + "require-dev": { + "jasny/php-code-quality": "^1.1" } } diff --git a/examples/ajax-broker/ajax.php b/examples/ajax-broker/ajax.php index 68d252f..63679ec 100644 --- a/examples/ajax-broker/ajax.php +++ b/examples/ajax-broker/ajax.php @@ -1,41 +1,37 @@ <?php require_once $_SERVER['DOCUMENT_ROOT'] . '/src/Broker.php'; -function send_error($message) { - header("Content-Type: application/json"); - header("HTTP/1.1 406 Not Acceptable"); - echo '{error: "$message"}'; -} +$command = $_REQUEST['command']; +$broker = new Jasny\SSO\Broker('http://localhost:9000/examples/server/', 'BrokerApi', 'BrokerApi'); if (empty($_REQUEST['command'])) { - send_error('command not specified'); - exit(); -} -else if ($_REQUEST['command'] == 'on') { - send_error('unsupported command'); + header("Content-Type: application/json"); + header("HTTP/1.1 406 Not Acceptable"); + echo json_encode(['error' => 'Command not specified']); exit(); } - -$command = $_REQUEST['command']; -$broker = new Jasny\SSO\Broker('http://localhost:9000/examples/server/', 'Alice', 'Bob'); - -if (!empty($_REQUEST['token'])) { - $broker->token = $_REQUEST['token']; -} - -if (realpath($_SERVER["SCRIPT_FILENAME"]) == realpath(__FILE__) && isset($_REQUEST['command'])) { +else if (realpath($_SERVER["SCRIPT_FILENAME"]) == realpath(__FILE__)) { error_log('executing: '. $_REQUEST['command']); try { - $result = $broker->$_GET['command'](); - } - catch (\Exception $ex) { - $result = $ex->getMessage(); + $result = $broker->$_REQUEST['command'](); + header("Content-Type: application/json"); + echo json_encode($result); + } catch (Exception $ex) { + $errorCode = $ex->getCode(); + error_log('error code' . $errorCode); + + header("Content-Type: application/json"); + if ($errorCode == 401) header("HTTP/1.1 401 Unauthorized"); + if ($errorCode == 406) header("HTTP/1.1 406 Not Acceptable"); + + echo json_encode(['error' => $ex->getMessage()]); } } else { error_log('nothing to execute'); -} -header("Content-Type: application/json"); -echo json_encode($result); -?>
\ No newline at end of file + header("Content-Type: application/json"); + header("HTTP/1.1 406 Not Acceptable"); + echo json_encode(['error' => 'Command not supported']); + exit(); +} diff --git a/examples/ajax-broker/helpers.js b/examples/ajax-broker/helpers.js index 5e1b4c3..95b5d0e 100644 --- a/examples/ajax-broker/helpers.js +++ b/examples/ajax-broker/helpers.js @@ -1,41 +1,57 @@ -function microAjax(B,A){this.bindFunction=function(E,D){return function(){return E.apply(D,[D]);};};this.stateChange=function(D){if(this.request.readyState==4){this.callbackFunction(this.request.responseText);}};this.getRequest=function(){if(window.ActiveXObject){return new ActiveXObject("Microsoft.XMLHTTP");}else{if(window.XMLHttpRequest){return new XMLHttpRequest();}}return false;};this.postBody=(arguments[2]||"");this.callbackFunction=A;this.url=B;this.request=this.getRequest();if(this.request){var C=this.request;C.onreadystatechange=this.bindFunction(this.stateChange,this);if(this.postBody!==""){C.open("POST",B,true);C.setRequestHeader("X-Requested-With","XMLHttpRequest");C.setRequestHeader("Content-type","application/x-www-form-urlencoded");C.setRequestHeader("Connection","close");}else{C.open("GET",B,true);}C.send(this.postBody);}}; +function microAjax(B,A) +{ + this.bindFunction=function (E,D) { + return function () { + return E.apply(D,[D]);};};this.stateChange=function (D) { + if (this.request.readyState==4) { + this.callbackFunction(this.request.responseText);}};this.getRequest=function () { + if (window.ActiveXObject) { + return new ActiveXObject("Microsoft.XMLHTTP");} else { + if (window.XMLHttpRequest) { + return new XMLHttpRequest();}}return false;};this.postBody=(arguments[2]||"");this.callbackFunction=A;this.url=B;this.request=this.getRequest();if (this.request) { + var C=this.request;C.onreadystatechange=this.bindFunction(this.stateChange,this);if (this.postBody!=="") { + C.open("POST",B,true);C.setRequestHeader("X-Requested-With","XMLHttpRequest");C.setRequestHeader("Content-type","application/x-www-form-urlencoded");C.setRequestHeader("Connection","close");} else { + C.open("GET",B,true);}C.send(this.postBody);}}; var token; -function attachSession() { - microAjax('/examples/ajax-broker/ajax.php?command=attach&token='+ token, function(data) { - console.log(data); - }); +function attachSession() +{ + microAjax('/examples/ajax-broker/ajax.php?command=attach&token='+ token, function (data) { + console.log(data); + }); } -function getToken(f) { - microAjax('/examples/ajax-broker/ajax.php?command=getToken', function(data) { - token = data; - console.log('token is ready'); - }); +function getToken(f) +{ + microAjax('/examples/ajax-broker/ajax.php?command=getToken', function (data) { + token = data; + console.log('token is ready'); + }); } -function login() { - var username = document.querySelector('input[name="username"]').value; - var password = document.querySelector('input[name="password"]').value; - var query = [ +function login() +{ + var username = document.querySelector('input[name="username"]').value; + var password = document.querySelector('input[name="password"]').value; + var query = [ 'command=login', 'username='+username, 'password='+password, 'token='+token - ]; + ]; - microAjax('/examples/ajax-broker/ajax.php?' + query.join('&'), function(data) { - console.log(data); - var outputDiv = document.querySelector('#output'); - var output = ""; - var jsonData = JSON.parse(data); + microAjax('/examples/ajax-broker/ajax.php?' + query.join('&'), function (data) { + console.log(data); + var outputDiv = document.querySelector('#output'); + var output = ""; + var jsonData = JSON.parse(data); - for (var key in jsonData) { - output += key + ": " + jsonData[key] + "<br>"; - } - outputDiv.innerHTML = output; - }); + for (var key in jsonData) { + output += key + ": " + jsonData[key] + "<br>"; + } + outputDiv.innerHTML = output; + }); } getToken(); diff --git a/examples/ajax-broker/token.php b/examples/ajax-broker/token.php index 60aa0fb..9c3e3f6 100644 --- a/examples/ajax-broker/token.php +++ b/examples/ajax-broker/token.php @@ -8,4 +8,3 @@ $result = array( header("Content-Type: application/json"); echo json_encode($result); -?>
\ No newline at end of file diff --git a/examples/broker/index.php b/examples/broker/index.php index 258b760..3ada49d 100644 --- a/examples/broker/index.php +++ b/examples/broker/index.php @@ -20,14 +20,16 @@ if (!$user) { <body> <h1>Single Sign-On demo</h1> <h2><?= $broker->broker ?></h2> - <?php if ($user): ?> + <?php if ($user) : ?> <h3>Logged in</h3> - <?php endif ?> + <?php +endif ?> <dl> - <?php foreach($user as $key => $value): ?> + <?php foreach ($user as $key => $value) : ?> <dt><?= $key ?></dt><dd><?= $value ?></dd> - <?php endforeach; ?> + <?php +endforeach; ?> </dl> <a id="logout" href="login.php?logout=1">Logout</a> </body> diff --git a/examples/broker/login.php b/examples/broker/login.php index a1226cd..2265756 100644 --- a/examples/broker/login.php +++ b/examples/broker/login.php @@ -6,7 +6,8 @@ $broker = new Jasny\SSO\Broker('http://localhost:9000/examples/server/', 'Alice' if (!empty($_GET['logout'])) { $broker->logout(); -} elseif ($broker->getUserInfo() || ($_SERVER['REQUEST_METHOD'] == 'POST' && $broker->login($_POST['username'], $_POST['password']))) { +} elseif ($broker->getUserInfo() + || ($_SERVER['REQUEST_METHOD'] == 'POST' && $broker->login($_POST['username'], $_POST['password']))) { header("Location: index.php", true, 303); exit; } diff --git a/examples/server/example-server.php b/examples/server/example-server.php deleted file mode 100644 index ca4043b..0000000 --- a/examples/server/example-server.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -session_save_path('/tmp/SSO1'); -require_once __DIR__ . '/../../vendor/autoload.php'; - -class ExampleServer extends Jasny\SSO\Server -{ - private static $brokers = array ( - 'Alice' => array('secret'=>"Bob"), - 'Greg' => array('secret'=>'Geraldo') - ); - - private static $users = array ( - 'admin' => array( - 'fullname' => 'jackie', - 'email' => 'jackie@admin.com' - ) - ); - - protected function getBrokerInfo($broker) - { - return self::$brokers[$broker]; - } - - protected function checkLogin($username, $password) - { - return $username == 'admin' && $password == 'admin'; - } - - protected function getUserInfo($user) - { - return self::$users[$user]; - } -} -?> diff --git a/examples/server/index.php b/examples/server/index.php index a4a2c67..828598a 100644 --- a/examples/server/index.php +++ b/examples/server/index.php @@ -1,13 +1,16 @@ <?php -require_once("example-server.php"); +require_once __DIR__ . '/../../vendor/autoload.php'; +session_save_path('/tmp/SSO1'); // Execute controller command if (realpath($_SERVER["SCRIPT_FILENAME"]) == realpath(__FILE__) && isset($_REQUEST['command'])) { - error_log('executing: '. $_REQUEST['command']); - $sso = new ExampleServer(); - $sso->$_GET['command'](); + $sso = new Jasny\SSO\TestServer(); + $sso->$_REQUEST['command'](); } else { - error_log('nothing to execute'); + error_log('Unkown command'); + header("HTTP/1.1 406 Not Acceptable"); + header('Content-type: application/json; charset=UTF-8'); + + echo "{error: 'Uknown command'}"; } -?>
\ No newline at end of file diff --git a/src/Broker.php b/src/Broker.php index c4f4029..621cf9c 100644 --- a/src/Broker.php +++ b/src/Broker.php @@ -1,6 +1,8 @@ <?php namespace Jasny\SSO; +require_once __DIR__ . '/../vendor/autoload.php'; + /** * Single sign-on broker. * @@ -31,7 +33,7 @@ class Broker * Session token of the client * @var string */ - public $token; + public $token; /** * User info recieved from the server. @@ -42,18 +44,19 @@ class Broker /** * Class constructor * - * @param string $url Url of SSO server - * @param string $broker My identifier, given by SSO provider. - * @param string $secret My secret word, given by SSO provider. + * @param string $url Url of SSO server + * @param string $broker My identifier, given by SSO provider. + * @param string $secret My secret word, given by SSO provider. */ public function __construct($url, $broker, $secret) { $this->url = $url; $this->broker = $broker; $this->secret = $secret; - session_start(); - //error_log('userinfo: '. $_SESSION['SSO']['userinfo']); - error_log('session ' . json_encode($_SESSION)); + + if (session_status() === PHP_SESSION_NONE) session_start(); + //error_log('session ' . json_encode($_SESSION)); + if (isset($_SESSION['SSO']['token'])) $this->token = $_SESSION['SSO']['token']; // if (isset($_SESSION['SSO']['userinfo'])) $this->userinfo = $_SESSION['SSO']['userinfo']; @@ -67,8 +70,11 @@ class Broker */ protected function getSessionId() { - if (!isset($this->token)) return null; - return "SSO-{$this->broker}-{$this->token}-" . md5('session' . $this->token . $_SERVER['REMOTE_ADDR'] . $this->secret); + if (!isset($this->token)) return null; + + $checksum = md5('session' . $this->token . $_SERVER['REMOTE_ADDR'] . $this->secret); + + return "SSO-{$this->broker}-{$this->token}-" . $checksum; } /** @@ -111,15 +117,21 @@ class Broker /** * Attach our session to the user's session on the SSO server. * - * @param string $returnUrl The URL the client should be returned to after attaching + * @param string $returnUrl The URL the client should be returned to after attaching */ public function attach($returnUrl = null) { error_log('trying to attach'); if ($this->isAttached()) return; - if (!isset($returnUrl)) $returnUrl = "http://{$_SERVER["SERVER_NAME"]}{$_SERVER["REQUEST_URI"]}"; - $url = $this->getAttachUrl() . "&returnUrl=" . urlencode($returnUrl); + $url = $this->getAttachUrl(); + + if (isset($returnUrl)) { + $url .= "&returnUrl=" . urlencode("http://{$_SERVER["SERVER_NAME"]}{$_SERVER["REQUEST_URI"]}"); + } + else if (!empty($_REQUEST['returnUrl'])) { + $url .= "&returnUrl=" . urlencode($_REQUEST['returnUrl']); + } header("Location: $url", true, 307); echo "You're redirected to <a href=\"$url\">$url</a>"; @@ -141,7 +153,7 @@ class Broker /** * Get the request url for a command * - * @param string $command + * @param string $command * @return string */ protected function getRequestUrl($command) @@ -159,34 +171,36 @@ class Broker /** * Execute on SSO server. * - * @param string $command Command - * @param array $params Post parameters + * @param string $command Command + * @param array $params Post parameters * @return array */ protected function request($command, $params = array()) { $ch = curl_init($this->getRequestUrl($command)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - error_log($this->getSessionId()); curl_setopt($ch, CURLOPT_POST, true); $params[session_name()] = $this->getSessionId(); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); $response = curl_exec($ch); - if (curl_errno($ch) != 0) throw new \Exception("Server request failed: " . curl_error($ch)); + if (curl_errno($ch) != 0) { + throw new Exception("Server request failed: " . curl_error($ch)); + } - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE ); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); $contentType = explode('; ', $contentType)[0]; if ($contentType != 'application/json') { - throw new \Exception("Response did not come from the SSO server. $response", $httpCode); + throw new Exception("Response did not come from the SSO server. $response", $httpCode); } error_log('response ' . $response); - $data = json_decode(stripslashes(trim($response)), true); - if ($httpCode != 200) throw new \Exception($httpCode); + $data = json_decode($response, true); + if ($httpCode != 200) throw new Exception($data['error'], $httpCode); + return $data; } @@ -197,21 +211,21 @@ class Broker * Only brokers marked trused can collect and send the user's credentials. Other brokers should omit $username and * $password. * - * @param string $username - * @param string $password + * @param string $username + * @param string $password * @return object */ public function login($username = null, $password = null) { - if (!isset($username)) $username = $_REQUEST['username']; - if (!isset($password)) $password = $_REQUEST['password']; + if (!isset($username)) $username = $_POST['username']; + if (!isset($password)) $password = $_POST['password']; + $result = $this->request('login', compact('username', 'password')); if (!array_key_exists('error', $result)) { $this->userinfo = $result; // $_SESSION['SSO']['userinfo'] = $result; error_log('success'); - } - else { + } else { error_log('failure'); } return $result; @@ -232,15 +246,13 @@ class Broker { error_log('trying to get user info'); try { - # TODO: the data is not updated + // TODO: the data is not updated if (!isset($this->userinfo)) { $this->userinfo = $this->request('userInfo'); } return $this->userinfo; - } - catch (\Exception $ex) { - error_log($ex); + } catch (Exception $ex) { return null; } } @@ -253,9 +265,7 @@ class Broker */ public function on($event, $data) { - if (method_exists($this, "on{$event}")) { - $this->{"on{$event}"}($data); - } + if (method_exists($this, "on{$event}")) $this->{"on{$event}"}($data); } /** @@ -282,4 +292,3 @@ class Broker $this->userinfo = $data; } } -?>
\ No newline at end of file diff --git a/src/Exception.php b/src/Exception.php new file mode 100644 index 0000000..3188b89 --- /dev/null +++ b/src/Exception.php @@ -0,0 +1,9 @@ +<?php +namespace Jasny\SSO; + +/** + * SSO Exception + */ +class Exception extends \Exception +{ +} diff --git a/src/Server.php b/src/Server.php index 790cd79..dff06ef 100644 --- a/src/Server.php +++ b/src/Server.php @@ -1,7 +1,11 @@ <?php - namespace Jasny\SSO; +require_once __DIR__ . '/../vendor/autoload.php'; + +use Desarrolla2\Cache\Cache; +use Desarrolla2\Cache\Adapter\File; + /** * Single sign-on server. * @@ -10,19 +14,10 @@ namespace Jasny\SSO; abstract class Server { /** - * Path to link files. - * - * If you run the SSO server on a shared hosting system, be sure to change this to a path in your home folder. - * This path MUST NOT be accessable by the web. - * - * @var string - */ - public static $linkPath; - - /** * Probability that the garbage collector is activated to remove of link files. * * Similar to gc_probability/gc_divisor + * * @link http://www.php.net/manual/en/session.configuration.php#ini.session.gc-probability * * @var float @@ -31,39 +26,46 @@ abstract class Server private $started = false; - /** - * Get path to link files + * Cache that stores the special session data for the brokers. + * + * @var Desarrolla2\Cache\Cache */ - public static function getLinkPath() + public $cache; + + public function __construct() { - if (!isset(self::$linkPath)) self::$linkPath = sys_get_temp_dir() . '/sso'; - if (file_exists(self::$linkPath)) mkdir(self::$linkPath, 1777, true); + $this->cache = $this->createCacheAdapter(); + $this->cache->set('hello world', 'bonjour'); + error_log('cache: ' . $this->cache->get('hello world')); + error_log('request:'. json_encode($_REQUEST)); } - /** * Start session and protect against session hijacking */ protected function sessionStart() { - if ($this->started) - return; + if ($this->started) return; + $this->started = true; // Broker session $matches = null; + error_log('request: ' . json_encode($_REQUEST)); - if (isset($_REQUEST[session_name()]) && preg_match('/^SSO-(\w*+)-(\w*+)-([a-z0-9]*+)$/', $_REQUEST[session_name()], $matches)) { + if (isset($_REQUEST[session_name()]) + && preg_match('/^SSO-(\w*+)-(\w*+)-([a-z0-9]*+)$/', $_REQUEST[session_name()], $matches)) { + error_log('starting broker session'); $sid = $_REQUEST[session_name()]; + error_log('retrieved sid: '. $sid); - - $link = (session_save_path() ? session_save_path() : sys_get_temp_dir()) . "/sess_" . $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']); - if (file_exists($link)) { - session_id(file_get_contents($link)); + $linkedId = $this->cache->get($sid); + if ($linkedId) { + session_id($linkedId); session_start(); - #TODO: the session cookie expires in 1 second. + // TODO: the session cookie expires in 1 second. setcookie(session_name(), "", 1); } else { session_start(); @@ -89,12 +91,12 @@ abstract class Server error_log('starting user session'); session_start(); - if (isset($_SESSION['client_addr']) && $_SESSION['client_addr'] != $_SERVER['REMOTE_ADDR']) + if (isset($_SESSION['client_addr']) && $_SESSION['client_addr'] != $_SERVER['REMOTE_ADDR']) { session_regenerate_id(true); - if (!isset($_SESSION['client_addr'])) + } + if (!isset($_SESSION['client_addr'])) { $_SESSION['client_addr'] = $_SERVER['REMOTE_ADDR']; - - error_log('session ' . json_encode($_SESSION)); + } } /** @@ -105,11 +107,10 @@ abstract class Server protected function generateSessionId($broker, $token, $client_addr = null) { $brokerInfo = $this->getBrokerInfo($broker); - if (!isset($brokerInfo)) - return null; - if (!isset($client_addr)) - $client_addr = $_SERVER['REMOTE_ADDR']; + if (!isset($brokerInfo)) return null; + if (!isset($client_addr)) $client_addr = $_SERVER['REMOTE_ADDR']; + return "SSO-{$broker}-{$token}-" . md5('session' . $token . $client_addr . $brokerInfo['secret']); } @@ -121,8 +122,8 @@ abstract class Server protected function generateAttachChecksum($broker, $token) { $brokerInfo = $this->getBrokerInfo($broker); - if (!isset($brokerInfo)) - return null; + if (!isset($brokerInfo)) return null; + return md5('attach' . $token . $_SERVER['REMOTE_ADDR'] . $brokerInfo['secret']); } @@ -133,13 +134,12 @@ abstract class Server { $this->sessionStart(); - if (empty($_POST['username'])) - $this->failLogin("No user specified"); - if (empty($_POST['password'])) - $this->failLogin("No password specified"); + if (empty($_POST['username'])) $this->failLogin("No user specified"); + if (empty($_POST['password'])) $this->failLogin("No password specified"); - if (!$this->checkLogin($_POST['username'], $_POST['password'])) + if (!$this->checkLogin($_POST['username'], $_POST['password'])) { $this->failLogin("Incorrect credentials"); + } $_SESSION['username'] = $_POST['username']; $this->userInfo(); @@ -164,43 +164,33 @@ abstract class Server { $this->sessionStart(); - if (empty($_REQUEST['broker'])) - $this->fail("No broker specified"); - if (empty($_REQUEST['token'])) - $this->fail("No token specified"); - if (empty($_REQUEST['checksum']) || $this->generateAttachChecksum($_REQUEST['broker'], $_REQUEST['token']) != $_REQUEST['checksum']) - $this->fail("Invalid checksum"); + if (empty($_REQUEST['broker'])) $this->fail("No broker specified"); + if (empty($_REQUEST['token'])) $this->fail("No token specified"); - if (!isset(self::$linkPath)) { - $link = (session_save_path() ? session_save_path() : sys_get_temp_dir()) . "/sess_" . $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']); - error_log('writing file|' . $link . '|'); - error_log('session id: ' . session_id()); - if (!file_exists($link)) - $attached = file_put_contents($link, session_id()); - if (!$attached) - trigger_error("Failed to attach; Link file wasn't created.", E_USER_ERROR); - - if (!file_exists($link)) - trigger_error("Failed to attach; Link file wasn't created.", E_USER_ERROR); - error_log('number of bytes written: ' . $attached); - error_log(error_get_last()); - } else { - $link = "{self::$linkPath}/" . $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']); - if (!file_exists($link)) - $attached = file_put_contents($link, session_id()); - if (!$attached) - trigger_error("Failed to attach; Link file wasn't created.", E_USER_ERROR); + $checksum = $this->generateAttachChecksum($_REQUEST['broker'], $_REQUEST['token']); + $sid = $this->generateSessionId($_REQUEST['broker'], $_REQUEST['token']); + error_log('sid: ' . $sid); + error_log('checksum: ' . $checksum); + if (empty($_REQUEST['checksum']) + || $checksum != $_REQUEST['checksum']) { + $this->fail("Invalid checksum"); } - //error_log ('request '. json_encode($_REQUEST)); - if (isset($_REQUEST['returnUrl'])) { + // what if there already exists an entry ? + $this->cache->set($sid, session_id()); + + if (!empty($_REQUEST['returnUrl'])) { header('Location: ' . $_REQUEST['returnUrl'], true, 307); exit(); } // Output an image specially for AJAX apps - header("Content-Type: image/png"); - readfile("empty.png"); + + header('Content-type: application/json; charset=UTF-8'); + echo json_encode(['token' => $_REQUEST['token']]); + //echo "{success:true, token:'", $_REQUEST['token'], "'}"; + //header("Content-Type: image/png"); + //readfile("empty.png"); } /** @@ -210,15 +200,13 @@ abstract class Server public function userInfo() { $this->sessionStart(); - if (!isset($_SESSION['username'])) - $this->failLogin("Not logged in"); + if (!isset($_SESSION['username'])) $this->failLogin("Not logged in"); $userData = $this->getUserInfo($_SESSION['username']); $userData['username'] = $_SESSION['username']; - forEach($userData as $key => $value) - { - # TODO: find alternative for htmlspecialchars, as this can be a vulnerability. + foreach ($userData as $key => $value) { + // TODO: find alternative for htmlspecialchars, as this can be a vulnerability. $userData[$key] = htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); } @@ -236,6 +224,8 @@ abstract class Server { header("HTTP/1.1 406 Not Acceptable"); header('Content-type: application/json; charset=UTF-8'); + error_log($message); + echo json_encode(array('error' => $message)); exit; } @@ -250,12 +240,24 @@ abstract class Server { header("HTTP/1.1 401 Unauthorized"); header('Content-type: application/json; charset=UTF-8'); + error_log($message); echo json_encode(array('error' => $message)); exit; } + /** + * Create a cache. + * + * This method is called in the constructor to make a cache to store the broker session id. + */ + protected function createCacheAdapter() + { + $adapter = new File('/tmp'); + $adapter->setOption('ttl', 10 * 3600); + return new Cache($adapter); + } + abstract protected function checkLogin($username, $password); abstract protected function getBrokerInfo($brokerId); abstract protected function getUserInfo($brokerId); } -?>
\ No newline at end of file diff --git a/src/TestServer.php b/src/TestServer.php new file mode 100644 index 0000000..b6ed2ac --- /dev/null +++ b/src/TestServer.php @@ -0,0 +1,51 @@ +<?php +namespace Jasny\SSO; + +require_once __DIR__ . '/../vendor/autoload.php'; + +use Desarrolla2\Cache\Cache; +use Desarrolla2\Cache\Adapter\Memory; + +class TestServer extends Server +{ + private static $brokers = array ( + 'Alice' => array('secret'=>"Bob"), + 'Greg' => array('secret'=>'Geraldo'), + 'BrokerApi' => array('secret'=>'BrokerApi'), + 'ServerApi' => array('secret' => 'ServerApi') + ); + + private static $users = array ( + 'admin' => array( + 'fullname' => 'jackie', + 'email' => 'jackie@admin.com' + ) + ); + + public function __construct() + { + parent::__construct(); + } + + protected function getBrokerInfo($broker) + { + return self::$brokers[$broker]; + } + + protected function checkLogin($username, $password) + { + return $username == 'admin' && $password == 'admin'; + } + + protected function getUserInfo($user) + { + return self::$users[$user]; + } + + // protected function createCacheAdapter() { + // $adapter = new Memory(); + // $adapter->setOption('ttl', 10 * 3600); + // return new Cache($adapter); + // } +} +?> diff --git a/tests/_support/FunctionalTester.php b/tests/_support/FunctionalTester.php deleted file mode 100644 index 8f9d5f2..0000000 --- a/tests/_support/FunctionalTester.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php - - -/** - * Inherited Methods - * @method void wantToTest($text) - * @method void wantTo($text) - * @method void execute($callable) - * @method void expectTo($prediction) - * @method void expect($prediction) - * @method void amGoingTo($argumentation) - * @method void am($role) - * @method void lookForwardTo($achieveValue) - * @method void comment($description) - * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null) - * - * @SuppressWarnings(PHPMD) -*/ -class FunctionalTester extends \Codeception\Actor -{ - use _generated\FunctionalTesterActions; - - /** - * Define custom actions here - */ -} diff --git a/tests/_support/UnitTester.php b/tests/_support/UnitTester.php deleted file mode 100644 index 68c09cf..0000000 --- a/tests/_support/UnitTester.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php - - -/** - * Inherited Methods - * @method void wantToTest($text) - * @method void wantTo($text) - * @method void execute($callable) - * @method void expectTo($prediction) - * @method void expect($prediction) - * @method void amGoingTo($argumentation) - * @method void am($role) - * @method void lookForwardTo($achieveValue) - * @method void comment($description) - * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null) - * - * @SuppressWarnings(PHPMD) -*/ -class UnitTester extends \Codeception\Actor -{ - use _generated\UnitTesterActions; - - /** - * Define custom actions here - */ -} diff --git a/tests/acceptance.suite.yml b/tests/acceptance.suite.yml deleted file mode 100644 index 75b6853..0000000 --- a/tests/acceptance.suite.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for acceptance tests. -# Perform tests in browser using the WebDriver or PhpBrowser. -# If you need both WebDriver and PHPBrowser tests - create a separate suite. - -class_name: AcceptanceTester -modules: - enabled: - - PhpBrowser: - url: http://localhost:9001/examples/broker - - \Helper\Acceptance
\ No newline at end of file diff --git a/tests/acceptance/WelcomeCept.php b/tests/acceptance/WelcomeCept.php deleted file mode 100644 index 6645347..0000000 --- a/tests/acceptance/WelcomeCept.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -$I = new AcceptanceTester($scenario); -$I->wantTo('ensure that frontpage works'); -$I->amOnPage('/'); -$I->see('Single Sign-On demo - Login', 'h1'); - -$I->amOnPage('/login.php'); -$I->seeCookie('PHPSESSID'); -$I->submitForm('#login', [ - 'username' => 'admin', - 'password' => 'admin' -]); - -$I->amOnPage('/'); -$I->see('Logged in', 'h3'); -$I->click('#logout'); - - -$I->amOnPage('/'); -$I->dontSee('Logged in', 'h3'); - -$I->amOnPage('/login.php'); -?> diff --git a/tests/brokerApi.suite.yml b/tests/brokerApi.suite.yml new file mode 100644 index 0000000..655361f --- /dev/null +++ b/tests/brokerApi.suite.yml @@ -0,0 +1,7 @@ +class_name: ServerApiTester +modules: + enabled: + - REST: + url: http://localhost:9001/examples/ajax-broker/ajax.php + depends: PhpBrowser + part: Json
\ No newline at end of file diff --git a/tests/brokerApi/BrokerTesterCept.php b/tests/brokerApi/BrokerTesterCept.php new file mode 100644 index 0000000..7fc7cbc --- /dev/null +++ b/tests/brokerApi/BrokerTesterCept.php @@ -0,0 +1,55 @@ +<?php +$token = 'hello_world'; +$broker = "Api"; +$checksum = '6ff4f638b06327544f26a248d6ad6890'; +$password = 'admin'; +$username = 'admin'; + +$I = new ServerApiTester($scenario); + +$I->wantTo('login through broker and view user data'); +$I->sendServerRequest('getUserInfo'); +$I->seeResponseIsJson(); +$I->seeResponseCodeIs(200); +$I->seeResponseEquals('null'); + +$I->sendServerRequest('attach'); +$I->seeResponseIsJson(); + +$I->sendServerRequest('getUserInfo'); +$I->seeResponseIsJson(); +$I->seeResponseCodeIs(200); +$I->seeResponseEquals('null'); + + +$I->sendServerRequest('login', [ + 'password' => $username, + 'username' => $password +]); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson(['token' => $token]); + +$I->sendServerRequest('getUserInfo'); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson(); +$I->seeResponseContainsJson([ + 'fullname' => 'jackie', + 'email' => 'jackie@admin.com', + 'username' => 'admin' +]); + +$I->sendServerRequest('detach'); +$I->sendServerRequest('attach'); + +$I->sendServerRequest('getUserInfo'); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson(); +$I->seeResponseContainsJson([ + 'fullname' => 'jackie', + 'email' => 'jackie@admin.com', + 'username' => 'admin' +]); + +$I->sendServerRequest('logout'); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson('null');
\ No newline at end of file diff --git a/tests/acceptance/_bootstrap.php b/tests/brokerApi/_bootstrap.php index 8a88555..8a88555 100644 --- a/tests/acceptance/_bootstrap.php +++ b/tests/brokerApi/_bootstrap.php diff --git a/tests/functional.suite.yml b/tests/functional.suite.yml deleted file mode 100644 index 075765b..0000000 --- a/tests/functional.suite.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for functional (integration) tests -# Emulate web requests and make application process them -# Include one of framework modules (Symfony2, Yii2, Laravel5) to use it - -class_name: FunctionalTester -modules: - enabled: - # add framework module here - - \Helper\Functional
\ No newline at end of file diff --git a/tests/serverApi.suite.yml b/tests/serverApi.suite.yml new file mode 100644 index 0000000..5fd2cd6 --- /dev/null +++ b/tests/serverApi.suite.yml @@ -0,0 +1,7 @@ +class_name: ServerApiTester +modules: + enabled: + - REST: + url: http://localhost:9000/examples/server + depends: PhpBrowser + part: Json
\ No newline at end of file diff --git a/tests/serverApi/ServerApiCept.php b/tests/serverApi/ServerApiCept.php new file mode 100644 index 0000000..d580f65 --- /dev/null +++ b/tests/serverApi/ServerApiCept.php @@ -0,0 +1,57 @@ +<?php +$token = 'hello_world'; +$broker = "ServerApi"; +$checksum = 'fe3892f0d85b3b92bdda31cbbf993ace'; +$password = 'admin'; +$username = 'admin'; + +$I = new ServerApiTester($scenario); +$I->defaultArgs = [ + 'token' => $token, + 'broker' => $broker, 'checksum' => $checksum, + 'PHPSESSID' => 'SSO-ServerApi-hello_world-ec31fb7dff02625359acc8d1bd6d9dc0' +]; + +$I->wantTo('attach session and view user info and logout'); +$I->sendServerRequest('attach', ['PHPSESSID' => '']); +$I->seeResponseIsJson(); +$I->seeResponseCodeIs(200); +$I->seeResponseContainsJson(['token' => $token]); + +$I->sendServerRequest('userInfo'); +$I->seeResponseCodeIs(401); +$I->seeResponseIsJson(); +$I->seeResponseContainsJson(['error' => 'Not logged in']); + +$I->sendServerRequest('login', [ + 'password' => 'wrong', + 'username' => 'wrong' +]); + +$I->seeResponseCodeIs(401); +$I->seeResponseIsJson(['error' => 'Incorrect credentials']); + +$I->sendServerRequest('login', [ + 'password' => $username, + 'username' => $password +]); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson(['token' => $token]); + +$I->sendServerRequest('userInfo'); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson(); +$I->seeResponseContainsJson([ + 'fullname' => 'jackie', + 'email' => 'jackie@admin.com', + 'username' => 'admin' +]); + +$I->sendServerRequest('logout'); +$I->seeResponseCodeIs(200); +$I->seeResponseIsJson(); + +$I->sendServerRequest('userInfo'); +$I->seeResponseCodeIs(401); +$I->seeResponseIsJson(); +$I->seeResponseContainsJson(['error' => 'Not logged in']);
\ No newline at end of file diff --git a/tests/functional/_bootstrap.php b/tests/serverApi/_bootstrap.php index 8a88555..8a88555 100644 --- a/tests/functional/_bootstrap.php +++ b/tests/serverApi/_bootstrap.php diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml deleted file mode 100644 index 02dc9b1..0000000 --- a/tests/unit.suite.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for unit (internal) tests. - -class_name: UnitTester -modules: - enabled: - - Asserts - - \Helper\Unit
\ No newline at end of file diff --git a/tests/unit/ServerTest.php b/tests/unit/ServerTest.php deleted file mode 100644 index c3cc1f3..0000000 --- a/tests/unit/ServerTest.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -require_once __DIR__ . '/../../vendor/autoload.php'; - -class ServerTest extends \Codeception\TestCase\Test -{ - /** - * @var \UnitTester - */ - protected $tester; - - protected function _before() - { - } - - protected function _after() - { - } - - // tests - public function testMe() - { - $broker = new Jasny\SSO\Broker('http://localhost:9000/examples/server/', 'Alice', 'Bob'); - - } -} diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php deleted file mode 100644 index 8a88555..0000000 --- a/tests/unit/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -<?php -// Here you can initialize variables that will be available to your tests |