View source
<?php
class Bakery {
protected $isMaster;
protected $domain;
protected $key;
protected $SsoCookieName;
protected $subCookieName;
protected $lifetime;
protected $expiration;
protected $debug;
protected static $instance = NULL;
public function __construct(array $config) {
$this->isMaster = $config['is_master'];
$this->domain = $config['domain'];
$this->key = $config['key'];
$this->SsoCookieName = isset($config['sso_cookie']) ? $config['sso_cookie'] : 'BAKERYSSO';
$this->subCookieName = isset($config['sub_cookie']) ? $config['sub_cookie'] : 'BAKERYSUB';
$this->lifetime = isset($config['lifetime']) ? $config['lifetime'] : 3600;
$this->expiration = isset($config['expiration']) ? $config['expiration'] : 3600 * 24 * 7;
$this->debug = isset($config['debug']) ? $config['debug'] : FALSE;
}
public static function instance(array $options = array()) {
if (!self::$instance) {
self::$instance = new self($options);
}
return self::$instance;
}
public function setSsoCookie($params) {
$cookie = array();
$type = $this->SsoCookieName;
$cookie['type'] = $type;
$cookie['name'] = $params['name'];
$cookie['mail'] = $params['mail'];
$cookie['init'] = $params['init'];
$cookie['uid'] = $params['uid'];
$cookie['data'] = isset($params['data']) ? $params['data'] : array();
$cookie['master'] = $this->isMaster;
$cookie['timestamp'] = $_SERVER['REQUEST_TIME'];
$data = $this
->bakeData($cookie);
$this
->setCookie($type, $data);
}
public function setSubCookie($name, $data, $start_url) {
$cookie = array();
$type = $this->subCookieName;
$cookie['type'] = $type;
$cookie['name'] = $name;
$cookie['data'] = $data;
$cookie['master'] = $this->isMaster;
$cookie['slave'] = $start_url;
$cookie['timestamp'] = $_SERVER['REQUEST_TIME'];
$data = $this
->bakeData($cookie);
$this
->setCookie($type, $data);
}
public function validateSsoCookie() {
$type = $this->SsoCookieName;
if (!isset($_COOKIE[$type]) || !$this->key || !$this->domain) {
return NULL;
}
try {
$data = $this
->validateData($_COOKIE[$type], $type);
$this
->debug('in validate SSO ', $data);
return $data;
} catch (BakeryException $e) {
$this
->log('Validation exception:', $e
->getMessage());
return FALSE;
}
}
public function validateSubCookie() {
$type = $this->subCookieName;
if (!isset($_COOKIE[$type]) || !$this->key || !$this->domain) {
return NULL;
}
try {
$data = $this
->validateData($_COOKIE[$type], $type);
$this
->debug('in validate Sub', $data);
return $data;
} catch (BakeryException $e) {
$this
->log('Validation exception:', $e
->getMessage());
return FALSE;
}
}
public function deleteSsoCookie() {
$this
->deleteCookie($this->SsoCookieName);
}
public function deleteSubCookie() {
$this
->deleteCookie($this->subCookieName);
}
protected function setCookie($name, $data) {
$cookie_secure = ini_get('session.cookie_secure');
setcookie($name, $data, $_SERVER['REQUEST_TIME'] + $this->expiration, '/', $this->domain, empty($cookie_secure) ? FALSE : TRUE);
}
protected function deleteCookie($name) {
$cookie_secure = ini_get('session.cookie_secure');
setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, '/', '', empty($cookie_secure) ? FALSE : TRUE);
setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, '/', $this->domain, empty($cookie_secure) ? FALSE : TRUE);
}
protected function bakeData(array $data) {
$this
->debug('bake ' . $data['type'], $data);
$data = $this
->serialize($data);
$encrypted_data = $this
->encrypt($data);
$signature = $this
->sign($encrypted_data);
return base64_encode($signature . $encrypted_data);
}
protected function validateData($data, $type) {
$this
->debug('validated data', $data);
$data = base64_decode($data);
$signature = substr($data, 0, 64);
$encrypted_data = substr($data, 64);
if ($signature !== $this
->sign($encrypted_data)) {
throw new BakeryException(3001, 'Signature mismatch');
}
$data = $this
->decrypt($encrypted_data);
$decrypted_data = $this
->unserialize($data);
$this
->debug('decrypted', $decrypted_data);
if ($type !== NULL && $decrypted_data['type'] !== $type) {
throw new BakeryException(3002, 'Type mismatch');
}
if ($decrypted_data['timestamp'] + $this->lifetime >= $_SERVER['REQUEST_TIME']) {
return $decrypted_data;
}
else {
throw new BakeryException(3003, 'Data expired');
}
}
private function encrypt($text) {
$key = $this->key;
$td = mcrypt_module_open('rijndael-128', '', 'ecb', '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, $text);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $data;
}
private function decrypt($data) {
$key = $this->key;
$td = mcrypt_module_open('rijndael-128', '', 'ecb', '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$key = substr($key, 0, mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$text = mdecrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $text;
}
private function sign($data) {
$key = $this->key;
return hash_hmac('sha256', $data, $key);
}
protected function serialize($data) {
return serialize($data);
}
protected function unserialize($data) {
return unserialize($data);
}
private function log($message, $data = array()) {
watchdog('Bakery', '@message @data', array(
'@message' => $message,
'@data' => var_export($data, TRUE),
), WATCHDOG_NOTICE);
}
private function debug($message, $data = array()) {
if ($this->debug) {
watchdog('Bakery', '@message @data', array(
'@message' => $message,
'@data' => var_export($data, TRUE),
), WATCHDOG_DEBUG);
}
}
}
class BakeryException extends Exception {
public $code;
public $message;
public $data;
function __construct($code, $message = NULL, $data = NULL) {
$this->code = $code;
$this->message = $message;
$this->data = $data;
}
}