From 57c1077ebb317d7d2146b6546e9b17034be0fa65 Mon Sep 17 00:00:00 2001 From: Benjamin Sonntag Date: Thu, 18 Sep 2014 12:01:34 +0200 Subject: [PATCH] adding Alternc_Api_Token and Alternc_Api_Auth_SharedSecret + Alternc_Api_Auth_Interface --- api/api.sql | 22 ++++ lib/Alternc/Api/Auth/Interface.php | 23 +++++ lib/Alternc/Api/Auth/Sharedsecret.php | 68 +++++++++++++ lib/Alternc/Api/Response.php | 29 +++++- lib/Alternc/Api/Service.php | 114 +++++++++++++++++++++ lib/Alternc/Api/Token.php | 138 ++++++++++++++++++++++++++ 6 files changed, 390 insertions(+), 4 deletions(-) create mode 100644 api/api.sql create mode 100644 lib/Alternc/Api/Auth/Interface.php create mode 100644 lib/Alternc/Api/Auth/Sharedsecret.php create mode 100644 lib/Alternc/Api/Service.php create mode 100644 lib/Alternc/Api/Token.php diff --git a/api/api.sql b/api/api.sql new file mode 100644 index 00000000..016fb8ed --- /dev/null +++ b/api/api.sql @@ -0,0 +1,22 @@ + + +-- used by Alternc_Api_Auth_Sharedsecret + +CREATE TABLE IF NOT EXISTS `sharedsecret` ( + `uid` int(10) unsigned NOT NULL, + `secret` varchar(32) NOT NULL, + PRIMARY KEY (`uid`,`secret`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Shared secrets used by Alternc_Api_Auth_Sharedsecret'; + + +-- used by Alternc_Api_Token + +CREATE TABLE IF NOT EXISTS `token` ( + `token` varchar(32) NOT NULL, + `expire` datetime NOT NULL, + `data` text NOT NULL, + PRIMARY KEY (`token`), + KEY `expire` (`expire`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Tokens used by API callers'; + + diff --git a/lib/Alternc/Api/Auth/Interface.php b/lib/Alternc/Api/Auth/Interface.php new file mode 100644 index 00000000..4254991a --- /dev/null +++ b/lib/Alternc/Api/Auth/Interface.php @@ -0,0 +1,23 @@ +db = $service->getDb(); + + } // __construct + + + /** + * Authenticate a user + * + * @param $options options, depending on the auth scheme, including uid for setuid users + * here, login is the alternc username, and secret is a valid shared secret for this user. + * @return an Alternc_Api_Token + */ + function auth($options) { + + if (!isset($options["login"]) || !is_string($options["login"])) { + throw new \Exception("Missing required parameter login", self::ERR_INVALID_ARGUMENT); + } + if (!isset($options["secret"]) || !is_string($options["secret"])) { + throw new \Exception("Missing required parameter secret", self::ERR_INVALID_ARGUMENT); + } + if (!preg_match("#^[0-9a-zA-Z]{32}$#",$options["secret"])) { + throw new \Exception("Invalid shared secret", self::ERR_INVALID_ARGUMENT); + } + + if (!preg_match("#^[0-9a-zA-Z-]{1,32}$#",$options["login"])) { // FIXME : normalize this on AlternC !!! + throw new \Exception("Invalid login", self::ERR_INVALID_LOGIN); + } + + $stmt = $db->query("SELECT m.enabled,m.uid,m.login,m.su FROM membres m, sharedsecret s WHERE s.uid=m.uid AND m.login=? AND s.secret=?;",array($options["login"],$options["secret"]),PDO::FETCH_CLASS); + $me=$stmt->fetch(); + if (!$me) + return new Alternc_Api_Response(array("code"=>ERR_INVALID_AUTH, "message" => "Invalid shared secret")); + if (!$me->enabled) + return new Alternc_Api_Response(array("code"=>ERR_DISABLED_ACCOUNT, "message" => "Account is disabled")); + + return Alternc_Api_Token::tokenGenerate( + array("uid"=>$me->uid, "isAdmin"=>($me->su!=0) ), + $this->db + ); + } + + +} // class Alternc_Api_Auth_Sharedsecret + diff --git a/lib/Alternc/Api/Response.php b/lib/Alternc/Api/Response.php index 3af46672..fe6debe1 100644 --- a/lib/Alternc/Api/Response.php +++ b/lib/Alternc/Api/Response.php @@ -6,6 +6,13 @@ */ class Alternc_Api_Response { + /** + * Error codes + */ + const ERR_DISABLED_ACCOUNT = 221801; + const ERR_INVALID_AUTH = 221802; + + /** * Result code. 0 means success * @@ -34,16 +41,30 @@ class Alternc_Api_Response { */ public $metadata; + + /** + * initialize a response object + * @param options any of the public above + */ + public function __constructor($options=array()) { + $os=array("code","message","content","metadata"); + foreach ($os as $o) { + if (isset($options[$o])) $this->$o=$options[$o]; + } + } + + /** * Formats response to json * * @return string */ - public function toJson (){ - + public function toJson (){ return json_encode(get_object_vars($this)); - } + + -} \ No newline at end of file +} // class Alternc_Api_Response + diff --git a/lib/Alternc/Api/Service.php b/lib/Alternc/Api/Service.php new file mode 100644 index 00000000..6b8ecd9e --- /dev/null +++ b/lib/Alternc/Api/Service.php @@ -0,0 +1,114 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if (isset($options["databaseAdapter"]) && $options["databaseAdapter"] instanceof PDO) { + $this->db=$options["databaseAdapter"]; + } else { + throw new \Exception("Missing required parameter databaseAdapter", self::ERR_INVALID_ARGUMENT); + } + + // Which login is allowed? + $this->allowedAuth=array(); + if (isset($options["loginAdapterList"]) && is_array($options["loginAdapterList"]) ) { + foreach($options["loginAdapterList"] as $lal) { + $this->allowedAuth[] = (string)$lal; + } + } + + // To which logger(s) shall we log to? + if (isset($options["loggerAdapter"])) { + if (!is_array($options["loggerAdapter"])) $options["loggerAdapter"]=array($options["loggerAdapter"]); + foreach($options["loggerAdapter"] as $la) { + if ($la instanceof Psr\Log\LoggerInterface) + $this->loggerList[]=$la; + } + } + + } // __construct + + + /** + * Authenticate into an AlternC server + * @param $auth hash with + * method: string describing the authentication name (in Alternc_Api_Auth_xxx) + * options: array list of parameters for the corresponding auth. + * if 'uid' is set in the option hash, the account MUST be an administrator one + * and as a result, the returned Api_Token will be set to this UID and not the admin one. + * @return Alternc_Api_Token an API Token + */ + function auth($auth) { + if (!isset($auth["method"]) || !is_string($auth["method"])) { + throw new \Exception("Missing required parameter method", self::ERR_INVALID_ARGUMENT); + } + if (!isset($auth["options"]) || !is_array($auth["options"])) { + throw new \Exception("Missing required parameter options", self::ERR_INVALID_ARGUMENT); + } + + if (count($this->allowedAuth) && !in_array($auth["method"],$this->allowedAuth)) { + throw new \Exception("Method not allowed", self::ERR_METHOD_DENIED); + } + + $adapterName = "Alternc_Api_Auth_".ucfirst(strtolower($auth["method"])); + $authAdapter = new $adapterName($this); + + return $authAdapter->auth($auth["options"]); + // table des tokens : token, expire, json_encode('uid','is_admin') + // return new Alternc_Api_Token(); + } + + + /** + * Manage an API Call + * @param Alternc_Api_Request $request The API call + * @return Alternc_Api_Response an API response + */ + function call($request) { + + return new Alternc_Api_Response(); + } + + + /** + * Getter for the databaseAdapter + * (used by authAdapter) + */ + function getDb() { + return $this->db; + } + + + +} // class Alternc_Api_Service + diff --git a/lib/Alternc/Api/Token.php b/lib/Alternc/Api/Token.php new file mode 100644 index 00000000..06c376c6 --- /dev/null +++ b/lib/Alternc/Api/Token.php @@ -0,0 +1,138 @@ +uid=$options["uid"]; + + if (isset($options["isAdmin"]) && is_bool($options["isAdmin"])) + $this->isAdmin=$options["isAdmin"]; + + } + + + /** + * Formats response to json + * + * @return string + */ + public function toJson (){ + return json_encode( + array("uid"=>$this->uid, + "isAdmin" => $this->isAdmin, + "token" => $this->token) + ); + } + + + /** + * Create a new token in the DB for the associated user/admin + * + * @return string the token (32 chars) + */ + public static function tokenGenerate($options,$db) { + if (!($db instanceof PDO)) { + throw new \Exception("No DB Object, can't create",self::ERR_DATABASE_ERROR); + } + if (!isset($options["uid"]) || !isset($options["isAdmin"])) { + throw new \Exception("Missing Arguments (uid,isAdmin)",self::ERR_MISSING_ARGUMENT); + } + + $token=new Alternc_Api_Token($options); + + do { + $token->token = $token->tokenRandom(); + $rows = $db->exec("INSERT IGNORE INTO token SET token=?, expire=DATE_ADD(NOW(), INTERVAL ? SECONDS), data=?", + array($token,$token->tokenDuration, $token->toJson()) + ); + } while ($rows==0); // prevent collisions + + return $token; + } + + + + /** + * Check and return a token + * @param $token string a 32-chars token + * @param $db PDO a PDO object for token table access + * + * @return Alternc_Api_Token object or NULL + */ + public static function tokenGet($token,$db) { + if (!($db instanceof PDO)) { + throw new \Exception("No DB Object, can't create",self::ERR_DATABASE_ERROR); + } + if (!is_string($token) || !preg_match("#^[a-zA-Z0-9]{32}$#",$token)) { + throw new \Exception("Invalid argument (token)",self::ERR_INVALID_ARGUMENT); + } + + foreach($db->query("SELECT * FROM token WHERE token=?", array($token)) as $tok) { + return new Alternc_Api_Token( json_decode($tok->data,true) ); + } + + return null; + } + + + /** + * Generate a new random token + * @return string + */ + public function tokenRandom(){ + $chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + $s=""; + for($i=0;$i<32;$i++) + $s.=substr($chars,rand(0,61),1); + return $s; + } + + +} // class Alternc_Api_Response +