adding Alternc_Api_Token and Alternc_Api_Auth_SharedSecret + Alternc_Api_Auth_Interface

This commit is contained in:
Benjamin Sonntag 2014-09-18 12:01:34 +02:00
parent e36cdb7293
commit 57c1077ebb
6 changed files with 390 additions and 4 deletions

22
api/api.sql Normal file
View File

@ -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';

View File

@ -0,0 +1,23 @@
<?php
/**
* Authentication API used by server to authenticate a user using a
* specific method.
*/
interface Alternc_Api_Auth_Interface {
/**
* contructor :
* $service is an Alternc_Api_Service object having a getDb() method
*/
function __constructor($service);
/**
* auth takes options specific to the auth itself
* returns an Alternc_Api_Token object
*/
function auth($options);
}

View File

@ -0,0 +1,68 @@
<?php
/**
* Authentication API used by server to authenticate a user using a
* SHARED SECRET (ApiKey)
*/
class Alternc_Api_Auth_Sharedsecret implements Alternc_Api_Auth_Interface {
private $db; // PDO object
const ERR_INVALID_ARGUMENT = 1111801;
/**
* Constructor of the Shared Secret Api Auth
*
* @param $service an Alternc_Api_Service object
* @return create the object
*/
function __constructor($service) {
if (!($service instanceof Alternc_Api_Service))
throw new \Exception("Invalid argument (service)",ERR_INVALID_ARGUMENT);
$this->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

View File

@ -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));
}
}
} // class Alternc_Api_Response

114
lib/Alternc/Api/Service.php Normal file
View File

@ -0,0 +1,114 @@
<?php
/**
* Service API used by server to export API methods
*/
class Alternc_Api_Service {
private $db; // PDO object
private $loggerList; // List of loggers
private $allowedAuth; // list of allowed authenticators
const ERR_INVALID_ARGUMENT = 111801;
const ERR_METHOD_DENIED = 111802;
/**
* Constructor of the Api Service Wrapper
*
* @param $options an hash with
* databaseAdapter: an already initialized PDO object
* see http://php.net/PDO
* loginAdapterList: (not mandatory) list of allowed authentication adapters (their codename)
* see Alternc/Api/Auth/*
* loggerAdapter: (not mandatory), a PSR3-Interface-compliant class or a list of it.
* see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md for more information
*
* @return create the object
*/
function __construct($options) {
// What DB shall we connect to?
// Note: it MUST be in this mode : $dbh->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

138
lib/Alternc/Api/Token.php Normal file
View File

@ -0,0 +1,138 @@
<?php
/**
* Standard Token object for the AlternC API
*
*/
class Alternc_Api_Token {
const ERR_DATABASE_ERROR=112001;
const ERR_INVALID_ARGUMENT=112002;
const ERR_MISSING_ARGUMENT=112003;
/**
* AlternC User-Id
*
* @var int
*/
public $uid;
/**
* Is this an admin account ?
*
* @var boolean
*/
public $isAdmin;
/**
* The Token itself
*
* @var string
*/
public $token;
/**
* how long (seconds) is a token valid
*
* @var int
*/
public static $tokenDuration = 2678400; // default is a month
/**
* initialize a token object
* @param options any of the public above
* may contain a dbAdapter, in that case create() will be available
*/
public function __constructor($options=array()) {
if (isset($options["uid"]) && is_int($options["uid"]))
$this->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