]> git.defcon.no Git - hermes/commitdiff
Added general framework for API-key authentication. From this point, the API requires...
authorJon Langseth <jon.langseth@lilug.no>
Thu, 19 Jan 2012 23:47:29 +0000 (00:47 +0100)
committerJon Langseth <jon.langseth@lilug.no>
Thu, 19 Jan 2012 23:47:29 +0000 (00:47 +0100)
api/auth.php
api/config.php.sample
api/lib/auth_base.php
doc/sql-data/structure.sql

index 6a8188ecc12ac859c217db5b933223b3fd08c696..1513aae2d7585dd948c387f439e1a240abca605a 100644 (file)
@@ -19,6 +19,8 @@ if ( !$config['sql_link'] )
 }
 
 //*************************************************************************************        
+if ( ( $_SERVER['PATH_INFO'] == "/login" ) || ( $_SERVER['PATH_INFO'] == "/logout" ) )
+{
        switch ( $_SERVER['PATH_INFO'] )
        {
                case "/login":
@@ -46,7 +48,7 @@ if ( !$config['sql_link'] )
                        }
                        else if ( array_key_exists('api_key', $_GET) )
                        {
-                               if ( apikey_verify( sql_clean( $_GET['api_key'] ) ) == 1 )
+                               if ( verify_apikey( sql_clean( $_GET['api_key'] ) ) == 1 )
                                {
                                        $type = "key";
                                        $authid = $_GET['api_key'];
@@ -66,20 +68,6 @@ if ( !$config['sql_link'] )
                        $auth_key = update_authkey( $session_name, $authid );
                        print json_encode( array( 'response' => 'ok', 'session' => $session_name, 'auth_key' => $auth_key ));
                        break;
-               case "/ping":
-                       // API clients are required to periodically ping the server
-                       // The time between pings (interval) is 5 minutes?
-                       // A ping call refreshes cookie lifetimes, then
-                       // generates and stores a new auth_key
-                       // The ping required a valid session...
-                       // A successful ping returns a 'response' => 'pong'
-                       // along with the new auth_key.
-                       token_auth();
-                       $session_name = $_GET['session'];
-                       $authid = $_SESSION['authid'];
-                       $auth_key = update_authkey( $session_name, $authid );
-                       print json_encode( array( 'response' => 'pong', 'auth_key' => $auth_key ));
-                       break;
                case "/logout":
                        // De-authenticate/deauthorize the ongoing session.
                        // I.e. destroy session data, remove session cookies.
@@ -95,12 +83,91 @@ if ( !$config['sql_link'] )
                        else
                                print json_encode ( array( 'response' => 'ok') );
                        break;
-               case "/list_users":
-                       // List valid API user-acounts.
-                       // Fail with notauthorized if current authentication
-                       // does not have write access.
-                       // Should not return users from backend, 
-                       // but should only return users with authorization.
+               default:
+                       print json_encode ( array( 'response' => 'invalid') );
+       }
+}
+else
+{
+       token_auth();
+
+       switch ( $_SERVER['PATH_INFO'] )
+       {
+               case "/ping":
+                       // API clients are required to periodically ping the server
+                       // The time between pings (interval) is 5 minutes?
+                       // A ping call refreshes cookie lifetimes, then
+                       // generates and stores a new auth_key
+                       // The ping required a valid session...
+                       // A successful ping returns a 'response' => 'pong'
+                       // along with the new auth_key.
+                       $session_name = $_GET['session'];
+                       $authid = $_SESSION['authid'];
+                       $auth_key = update_authkey( $session_name, $authid );
+                       print json_encode( array( 'response' => 'pong', 'auth_key' => $auth_key ));
+                       break;
+               case "/new_apikey":
+                       // If the current authorization has write access, create
+                       // a new API key with requested access (ro/rw).
+                       if ( ! can_write() )
+                               simple_authfail();
+
+                       if ( array_key_exists('host_ip', $_GET )
+                               && array_key_exists('access', $_GET ))
+                       {
+                               $host = $_GET['host_ip'];
+                               $access = $_GET['access'];
+
+                               if (! preg_match("/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/", $host) || ! authlevel_value( $access ) )
+                               {
+                                       print json_encode ( array( 'response' => 'invalid', 'cause' => 'parameters' ) );
+                                       break;  
+                               }
+                               $level = authlevel_value( $access );
+                               $key = add_apikey( $host, $level );
+                               if ( ! $key )
+                               {
+                                       print json_encode( array( 'response' => 'failed', 'cause' => 'error', 'detail' => 'Database error.'));
+                                       break;
+                               }
+                               print json_encode( array( 'response' => 'ok', 'key' => $key, 'host' => $host, 'access' => authlevel_name( $level ) ) );
+                               break;
+                       }
+                       else print json_encode ( array( 'response' => 'invalid') );
+                       break;  
+               case "/remove_apikey":
+                       // If the current authorization has write access,
+                       // remove the given API key.
+                       if ( ! can_write() )
+                               simple_authfail();
+
+                       if ( array_key_exists('api_key', $_GET ) )
+                       {
+                               $key = sql_clean( $_GET['api_key'] );
+                               // Perform a key-verification, skipping host/remote-address check.
+                               if ( ! verify_apikey( $key, true ) )
+                               {
+                                       print json_encode( array ( 'response' => 'failed', 'cause' => 'nonexistant'));
+                                       break;
+                               }
+                               if ( ! remove_apikey( $key ) )
+                               {
+                                       print json_encode( array( 'response' => 'failed', 'cause' => 'error', 'detail' => 'Database error.'));
+                                       break;
+                               }
+                               print json_encode( array( 'response' => 'ok', 'key' => $key ) );
+                               break;
+                       }
+                       else print json_encode ( array( 'response' => 'invalid') );
+                       break;  
+               case "/list_apikeys":
+                       // List valid API keys.
+                       // Fail is current authorization does not have write access.
+                       if ( ! can_write() )
+                               simple_authfail();
+                       $list = list_apikeys();
+                       print json_encode( array( 'response' => 'ok', 'list' => $list ) );
+                       break;
                case "/authorize_user":
                        // Add or update a valid back-end user in authorization
                        // if the current authentication has write access.
@@ -108,30 +175,31 @@ if ( !$config['sql_link'] )
                        // needed parameters should be username and access level
                        // If the authorization does not exist, add it.
                        // If the user is already authorized, replace access level.
+               case "/remove_user":
+                       // If the current authentication has write access:
+                       // Remove authorization for the given users.
+                       // Delete user from backend if backend is read-write.
+               case "/list_users":
+                       // List valid API user-acounts.
+                       // Fail with notauthorized if current authentication
+                       // does not have write access.
+                       // Should not return users from backend, 
+                       // but should only return users with authorization.
                case "/add_user":
                        // Add user to backend if backend is read-write and
                        // the current authentication has write access.
+                       // The created user should be added to authorizations
+                       // with an access level of "limited_read (1)"
                case "/update_user":
                        // Update the given user in the backend, if the backend
                        // is read-write, and the current authentication has
                        // write access.
-               case "/remove_user":
-                       // Delete user from backend if backend is read-write
-                       // and the current authentication has write access.
-               case "/list_apikeys":
-                       // List valid API keys.
-                       // Fail is current authorization does not have write access.
-               case "/new_apikey":
-                       // If the current authorization has write access, create
-                       // a new API key with requested access (ro/rw).
-               case "/remove_apikey":
-                       // If the current authorization has write access,
-                       // remove the given API key.
                        print json_encode ( array( 'response' => 'notimplemented') );
                        break;
                default:
                        print json_encode ( array( 'response' => 'invalid') );
        }
+}
 //*************************************************************************************        
 mysql_close( $config['sql_link'] );
 ?>
index 477f4fd735e0d685c17ec8689d03a03ea62608f6..c09eb674bdf01523799d732520245bfab2385280 100644 (file)
@@ -15,6 +15,8 @@ function get_config()
                'provision_users_table' => 'users',
                'provision_phones_table' => 'phones',
                'provision_servers_table' => 'servers',
+               'apikeys_table' => 'apikeys',
+               'authorizations_table' => 'authorizations',
                'sessionkeys_table' => 'sessionkeys',
                'sessionkey_lifetime' => 5, // Minutes
                'numbers_table' => 'number_pool',
index 5f517714062682c59282baba9df03edc41d8b1cc..97e557f9d4e63e0878f0dd50b93549ef718296f1 100644 (file)
@@ -3,6 +3,35 @@ require_once('config.php');
 
 $config = get_config();
 
+function authlevel_value( $level )
+{
+       switch ( $level )
+       {
+               case 'limited_read':
+                       return 1;
+               case 'full_read':
+                       return 2;
+               case 'read_write':
+                       return 3;
+               default:
+                       return 0;
+       }
+}
+function authlevel_name( $level )
+{
+       switch ( $level )
+       {
+               case 1:
+                       return 'limited_read';
+               case 2:
+                       return 'full_read';
+               case 3:
+                       return 'read_write';
+               default:
+                       return 'no_access';
+       }
+}
+
 /*******************************
 * Load authentication plugin ..
 *******************************/
@@ -15,12 +44,6 @@ else
 {  print json_encode( array( 'response' => 'error', 'cause' => 'config-error' ) ); exit; }
 /*******************************/
 
-function apikey_verify( $key )
-{
-       if ( $key == "6327c08b70f9" ) return 1;
-       return false;
-}
-
 function new_key( $hex = false )
 {
        // Basically this is at the moment a slightly modified
@@ -32,14 +55,14 @@ function new_key( $hex = false )
         while ( strlen( $string ) < $length )
         {
                if ( $hex )
-                       $string .= substr(md5(rand().rand()), 0, $length);
+                       $string .= substr(md5(rand().rand()), 0, $length+1);
                else
                {
-                       $string .= crypt( substr(sha1(rand()), 0, $length) );
+                       $string .= crypt( substr(sha1(rand()), 0, $length+1) );
                        $string = preg_replace( '/\W/', '', $string);
                }
         }
-       return substr( $string, 0, $length );
+       return substr( $string, 1, $length );
 }
 
 function simple_authfail()
@@ -214,24 +237,146 @@ function remove_session ($name, $id = null )
        $_SESSION=array();
        session_destroy();
 
-       if ( $current_session != $name )
+       if ( $current_session && $current_session != $name )
        {
                session_id($current_sessid);
                session_start();
        }
 }
+function add_apikey ( $host, $level )
+{
+       global $config;
+       if ( !is_numeric($level) ) return false;
+
+       $key = new_key();
+
+       // Try to add the new key to authorizations first. If this
+       // fails, there will be the least amount of data to clean up ...
+       if ( ! update_authorization( $key, $level ) ) return false;
+
+       $query = sprintf("INSERT INTO %s ( host, apikey ) VALUES ( '%s', '%s' )",
+               $config['apikeys_table'],
+               sql_clean($host),
+               sql_clean($key));
+
+       if ( ! sql_dbexec( $config['provision_db'], $query ) ) return false;
+       return $key;
+}
+
+function remove_apikey( $key )
+{
+       global $config;
+       if ( ! verify_apikey( $key, true ) ) return false;
+       if ( ! remove_authorization( $key ) ) return false;
+
+       $query = sprintf("DELETE FROM %s WHERE apikey = '%s'",
+               $config['apikeys_table'],
+               sql_clean($key) );
+       if ( ! sql_dbexec( $config['provision_db'], $query ) ) return false;
+
+       return true;
+}
+
+function verify_apikey( $key, $skip_hostcheck = false )
+{
+       global $config;
+
+       $query = sprintf("SELECT host FROM %s WHERE apikey = '%s'",
+               $config['apikeys_table'],
+               sql_clean($key) );
+       $row = sql_dbquery_single( $config['provision_db'], $query );
+       if  (!$row) return false;
+       $host = $row['host'];
+
+       if ( $host && ( $skip_hostcheck ) )
+               return true;
+
+       if ( $host == $_SERVER['REMOTE_ADDR'] ) return true;
+       return false;
+
+}
+
+function list_apikeys ()
+{
+       global $config;
+       $query = sprintf("SELECT k.apikey AS apikey, k.host AS host, 
+                               a.access_level AS access_level 
+                               FROM %s k INNER JOIN %s a ON k.apikey = a.authid",
+                       $config['apikeys_table'], 
+                       $config['authorizations_table']);
+       $list = array();
+       $result = sql_dbquery( $config['provision_db'], $query);
+       if ( ! $result ) return $list; 
+       while ( $row = @mysql_fetch_assoc( $result ) )
+       {
+               array_push( $list, array(
+                       'api_key' => $row['apikey'],
+                       'host' => $row['host'],
+                       'level' => authlevel_name( $row['access_level'] )
+               ));
+       }
+       return $list;
+
+}
+function update_authorization( $authid, $level )
+{
+       global $config;
+       if ( !is_numeric($level) ) return false;
+       $query = sprintf("INSERT INTO %s ( authid, access_level ) VALUES ( '%s', %d )
+                       ON DUPLICATE KEY UPDATE access_level=%d",
+               $config['authorizations_table'],
+               sql_clean($authid),
+               $level, $level);
+       if ( ! sql_dbexec( $config['provision_db'], $query ) ) return false;
+       return true;
+}
 
-function get_authorization()
+function remove_authorization( $authid )
 {
-       return 1;
+       global $config;
+       $query = sprintf("DELETE FROM %s WHERE authid = '%s'",
+               $config['authorizations_table'],
+               sql_clean($authid) );
+       //print $query . "\n\n";
+       if ( ! sql_dbexec( $config['provision_db'], $query ) ) return false;
+       return true;
 }
+
+
+function get_authorization( $type, $authid )
+{
+       global $config;
+
+       // If API-key is used, but key fails verification, write is impossible.
+       if ( ( $type == "key" ) && ( ! verify_apikey( $authid ) ) )
+               return false;
+
+       // If User-login is used, but backend is unable to provide info, fail.
+       if ( ( $type == "user" ) && ( ! authuser_getinfo( $authid ) ) )
+               return false;
+
+       // The only types of access control supported are "user" or "key".
+       if ( ($type != "user" ) && ($type != "key") )
+               return false;
+
+       $query = sprintf("SELECT access_level FROM %s  WHERE authid = '%s'",
+               $config['authorizations_table'],
+               sql_clean($authid) );
+       $row = sql_dbquery_single( $config['provision_db'], $query );
+       if  (!$row) return false;
+       $level = $row['access_level'];
+       return $level;
+}
+
 function can_write ( )
 {
        // Stub, to be called on any API nodes that write data in the DB.
        $authid = $_SESSION['authid'];
        $type   = $_SESSION['type'];
+       
        $level = get_authorization( $type, $authid );
-       return true;
+       if ( $level >= authlevel_value('read_write') ) return $level;
+       else return false;
 }
 
 ?>
index e2ee817d5c5bf0a36bc731de284f1dbc69b4dcbe..d077eda7d5968b1ee8f98455c55c442b55c1ee29 100644 (file)
@@ -1023,6 +1023,30 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `provision` /*!40100 DEFAULT CHARACTER
 
 USE `provision`;
 
+--
+-- Table structure for table `apikeys`
+--
+
+CREATE TABLE IF NOT EXISTS `apikeys` (
+  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
+  `host` varchar(64) NOT NULL,
+  `apikey` varchar(128) NOT NULL,
+  PRIMARY KEY  (`apikey`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `authorizations`
+--
+
+CREATE TABLE IF NOT EXISTS `authorizations` (
+  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
+  `authid` varchar(255) NOT NULL,
+  `access_level` int(11) NOT NULL,
+  PRIMARY KEY  (`authid`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
 --
 -- Table structure for table `number_pool`
 --