Zend AMF Authentication & Authorization

dkozar evolved a working method to Authenticate and Authorize a Flex based app datas service call using Zend AMF, he writes;

I’ve been struggling with it, and figured it all out – so, perhaps it could help others.

The authentication is called on the server only if credentials supplied from the client (via the remote procedure call headers). This snippet illustrates the setup of custom auth (these are the last 6 lines of gateway.php script):

// Handle request
$auth = new My_Amf_Auth(); // authentication
$server->setAuth($auth);
$acl = new Zend_Acl(); // authorization
$server->setAcl($acl);
echo $server->handle();

Now, your custom auth should extend Zend_Amf_Auth_Abstract. Since I want to authenticate users from a database, I bring the Zend_Auth_Adapter_DbTable to play. But since I cannot extend both Zend_Amf_Auth_Abstract and Zend_Auth_Adapter_DbTable, I use a composition:

< ?php require_once ('Zend/Amf/Auth/Abstract.php'); /** * AMF auth class by Danko Kozar, dankokozar.com * @author dkozar * */ class My_Amf_Auth extends Zend_Amf_Auth_Abstract { function __construct() { } public function authenticate() { $adapter = My_Db_Adapter::getInstance(); $adapter->setIdentity($this->_username);
$adapter->setCredential($this->_password);

// the adapter call
// you can wrap it into try.. catch and process DB connection errors
$result = Zend_Auth::getInstance()->authenticate($adapter);

return $result;
}
}

Here’s the adapter class:

< ?php /** * DB table adapter auth class for AMF by Danko Kozar, dankokozar.com * @author dkozar * Singleton */ class My_Db_Adapter extends Zend_Auth_Adapter_DbTable { protected static $_instance = null; /** * private! * @param My_Db_Adapter $adapter */ public function __construct(Zend_Db_Adapter_Abstract $adapter = null) { if (!$adapter) $adapter = new Zend_Db_Adapter_Mysqli( array( 'dbname' => 'test',
'username' => 'root',
'password' => '')
);

parent::__construct($adapter);

$this
->setTableName('users')
->setIdentityColumn('username')
->setCredentialColumn('password')
;

// just for testing
// $this
// ->setIdentity('username')
// ->setCredential('password')
// ;
}

/**
* @return My_Db_Adapter
*/
public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}

public function authenticate() {

$_authResult = parent::authenticate();

// NOTE: The point is that $result->_identity is an OBJECT (of type stdClass), NOT string
// with Zend_Auth_Adapter_DbTable it is internally accomplished by calling its getResultRowObject() method
// It constructs the stdClass with properties named after table attributes

// $user = new stdClass();
// $user->role = "administrator";
// $user->username = $_authResult->getIdentity();

$identity = $this->getResultRowObject();

$result = new Zend_Auth_Result($_authResult->getCode(), $identity);

return $result;
}
}

MyService.php class. Here it is:


< ?php /** * PHP service class with authorization * by Danko Kozar, dankokozar.com * @author dkozar * */ class MyService { /** * from zend docs: * If the ACL object is set, and the class being called defines initAcl() method, * this method will be called with the ACL object as an argument. * This method can create additional ACL rules and return TRUE, * or return FALSE if no access control is required for this class. * * @param Zend_Acl $acl * @return boolean */ public function initAcl($acl) { $acl->addRole(new Zend_Acl_Role("administrator"));
$acl->addRole(new Zend_Acl_Role("user"));

//acl "allow" method takes 3 parameters (role, resource - class name, privileges - it's function name in this class)

// administrator
$acl->allow('administrator', 'MyService', 'helloWorld');
$acl->allow('administrator', 'MyService', 'getData');

// user
$acl->allow('user', 'MyService', 'helloWorld');
$acl->deny('user', 'MyService', 'getData');

//returning true to signal that we want to check privileges before accessing methods of this class
//in my tests if we don't return anything it will treat it like we will return false so better return true or false
//your intentions will be clear
return true;
}

/**
* Hello world method
*/
public function helloWorld(){
return "Hello world from MyService service";
}

/**
*
* Returns data
* @return [int]
*/
function getData()
{
$arr = array(1, 2, 3);
return $arr;
}
}
?>

Note that the authorization is being built dynamically inside the initAcl method.

On the Flex side I have an auto-generated class (MyService) which extends another auto-generated class (_Super_MyService).

The point is that the outer one is auto-generated only once (initially), and you can modify it, without worrying to be overwritten on service regeneration.

There’s a protected property _serviceControl (which is of type RemoteObject) which could be tweaked if needed.

I’m tweaking it by of setting the endpoint (with string read from a client side config in preInitializeService() method). Plus, I’m adding 2 more methods, which expose setCredentials and setRemoteCredentials methods of _serviceControl, so I can acces it from my code.


package services.myservice
{
public class MyService extends _Super_MyService
{
/**
* Override super.init() to provide any initialization customization if needed.
*/
protected override function preInitializeService():void
{
super.preInitializeService();

// Initialization customization goes here
_serviceControl.endpoint = "http://localhost/myapp/gateway.php";
}

public function setCredentials(username:String, password:String, charset:String=null):void
{
_serviceControl.setCredentials(username, password, charset);
}

public function setRemoteCredentials(username:String, password:String, charset:String=null):void
{
_serviceControl.setRemoteCredentials(username, password, charset);
}
}
}


So, before calling MyService methods, I’m setting the credentials with setCredentials() method and this runs the authentication on the PHP side:


private var service:MyService;
....
service = new MyService(); // ServiceLocator.getInstance().getHTTPService("presetLoader");
service.setCredentials("user1", "pass1");
var token:AsyncToken = service.getData();

The authentication via Zend_Amf_Server is, by the way, OPTIONAL! Meaning, with no credentials supplied, Zend_Amf_Server will NOT RUN IT. Thus you should rely on Zend_Acl (e.g. roles) to so your permissions and security!

Finally, here’s the MySQL DB table I’ve been using for authentication:

--
-- Table structure for table `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(32) DEFAULT NULL,
`role` varchar(45) DEFAULT NULL,
`firstname` varchar(50) DEFAULT NULL,
`lastname` varchar(50) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

--
-- Dumping data for table `users`
--
INSERT INTO `users` (`id`, `username`, `password`, `role`, `firstname`, `lastname`, `email`) VALUES
(1, 'user1', 'pass1', 'administrator', 'Danko', 'Kozar', NULL);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cheers!
Danko

Adobe Forums

Leave a Reply

Your email address will not be published. Required fields are marked *