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

Using Different Databases with Zend Framework 1.10.4+

Jeroen Keppens wrote a very good article about using multiple databases that I defenitely found very useful;

A while ago I wrote about a custom application resource for loading multiple DBs. I received a lot of questions and decided it was time for a follow-up on how to use multiple DBs in ZF.

One of the first experiments I did with Application Resources was writing a application resource for loading multiple databases. We generally connect to multiple databases in reporting applications or in scripts where we want to aggregate data from the main database to an offsite database. Since that post I received a lot of questions on how to integrate this in the models and how to join different databases in queries using zend framework. In this post I’ll explore how to do both. Since in the meantime someone has used the idea and added a multiple db resource to the Zend Framework, I will use the “official” application resource loader instead of my own.

This tutorial was made with Zend Framework 1.10.4.

via Jeroen Keppens

Akrabat_Db_Schema_Manager: table prefix support

Rob Allen posts; I’ve updated Akrabat_Db_Schema_Manager so that it now supports table prefixes.

It uses the application.ini key of resources.db.table_prefix as I couldn’t think of a better one 🙂 and then uses that for the schema_version table’s name and also makes it available in your change objects.

For example, if application.ini contains resources.db.table_prefix = “myapp”, then the manager will create the table myapp_schema_version to store the current version of the schema. In your change classes, you can then do this:

001-Users.php:

class Users extends Akrabat_Db_Schema_AbstractChange
{
function up()
{
$tableName = $this->_tablePrefix . 'users';
$sql = "
CREATE TABLE IF NOT EXISTS $tableName (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(50) NOT NULL,
password varchar(75) NOT NULL,
role varchar(200) NOT NULL DEFAULT 'user',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
$this->_db->query($sql);

$data = array();
$data['username'] = 'admin';
$data['password'] = sha1('password');
$data['role'] = 'admin';
$this->_db->insert($tableName, $data);
}

function down()
{
$tableName = $this->_tablePrefix . 'users';
$sql= "DROP TABLE IF EXISTS $tableName";
$this->_db->query($sql);
}

}

which will create a table called myapp_users. Note that you are responsible for using the prefix property as the change classes cannot enforce what you do within the up() and down() methods. It also follows that you’ll have to ensure that your models also use the correct prefix.

I have also made a change to the provider (Akrabat_Tool_DatabaseSchemaProvider) so that it loads the correct application.ini file based on the data in the project’s profile. This shouldn’t affect anyone using Akrabat_Db_Schema_Manager, except that we no longer define APPLICATION_ENV and APPLICATION_PATH for you.

Enjoy!

via Rob Allen’s DevNotes.

MySQL does support preparing some DDL statements, However…

Bill Karwin gives some insight into some work arounds when creating functions, triggers and procedures using Zend Framework;

MySQL does support preparing some DDL statements, even in older versions. See http://dev.mysql.com/doc/refman/5.1/en/sql-syntax-prepared-statements.html
for lists of what statements can be prepared.

However, some DDL statements are still not supported as prepared statements, for example CREATE FUNCTION, CREATE TRIGGER, CREATE PROCEDURE.

DELIMITER is not supported as an executable statement at all, whether you prepare it or whether you do an immediate execute. Statements like DELIMITER, PAGER, SOURCE, CONNECT, and QUIT and others are builtins of the mysql command-line client. These commands are not recognized by the MySQL server.

You need to set the DELIMITER only if you’re running the CREATE FUNCTION statement in an SQL script. The default API for SQL statements does not support multiple statements per call. So you don’t have to delimit statements and you don’t have to change the delimiter.

So Nils’s solution should be the following:

1. Don’t worry about DELIMITER, you don’t need it.

2. You must DROP and CREATE in two separate statements.

3. Bypass the default ZF query method. Go directly to the
PDO::query() method when you execute a statement that isn’t preparable. You can access the PDO object using the getConnection() method of your ZF Db adapter:

$db->getConnection()->query( $drop_function_statement );
$db->getConnection()->query( $create_function_statement );

Regards,
Bill Karwin

Proposal for Zend_Db_NestedSet – Hierarchical data as a nested set

Graham Anderson writes a very interesting proposal; If you are interested in an implementation of storing and retrieving hierarchical data as a nested set, please take a few minutes to review my new proposal[1].

I dusted off some old code and poked and prodded a little until it behaved somewhat as expected, there’s a functioning prototype on GitHub[2] with some basic instructions in the README.

As you probably guessed the algorithm is modified pre-order traversal, and the current working functionality is as follows

  • Store single trees or multiple trees in same table
  • Add, move & delete individual tree nodes or tree branches
  • operate on result set nodes(getPath(),getSiblings(),getDescendants(), etc )
  • Result-set as multi-dimensional associative array (Zend_Navigation)
  • Result-set as recursive iterator

Cheers the noo,
Graham

  1. http://framework.zend.com/wiki/display/ZFPROP/Zend_Db_NestedSet+-+Graham+Anderson
  2. http://github.com/gnanderson/ZF_NestedSet

Features « DataGrid for Zend Framework

Zend dataGrid now has it’s own domain and releases version 0.5 , Check out the Docs and the Demo.

Some of the features:

  • Create a datagrid using a Zend_Db_Select instance, Arrays, XML, CSV or JSON files
  • Takes a Zend_Db_Select instance to perform the query
  • User interface controls to perform operations to insert, update and delete table records with support for data validation and filtering
  • Template based presentation
  • Filter data by user selected fields Automatic pagination of results
  • Support for extra listing column fields List sorting by field
  • Configurable field titles
  • Support for SQL aggregation expressions (MAX, COUNT, MIN, AVG, etc…)
  • Internationalization support
  • Export results in multiple formats: XML, HTML table, PDF, MSExcel, MSWord, CSV, Open Office Spreadsheet and text document etc..
  • Cache support
  • Plug-ins to format content: date, bool, etc..
  • Ajax support
  • Form fields customization
  • Fields Decorators
  • Callback functions
  • ….

via: Features « DataGrid for Zend Framework.