Instant ACLs
with Zend Framework 2

Zend Framework Day – Turin, Italy – 07/02/2014
@stefanovalle
http://www.mvlabs.it/
http://friuli.grusp.org/
AUTHENTICATION
AUTHORIZATION
5
Two step process
Two step process

WHO
WHAT
Two step process

WHO

Authentication
“a process that ensures and confirms
a user’s identity”

WHAT
Definitions from http://www.techopedia.com
Two step process

WHO
WHAT

Authentication
“a process that ensures and confirms
a user’s identity”

Authorization
“a security mechanism used to determine
user/client privileges or access levels
related to system resources”
Definitions from http://www.techopedia.com
11
In ZF2

WHO

Zend/Authentication
In ZF2

WHO

Zend/Authentication
Authenticate against:
• DB table
• LDAP
• HTTP
• And more…
In ZF2

WHAT

Zend/Permissions/Acl
Zend/Permissions/Rbac
In ZF2

WHAT

Zend/Permissions/Acl
Zend/Permissions/Rbac

Role

Permission

Identity
Resource
SEEMS TOUGH…
JUST AS IT SHOULD!
CONSEQUENCES COULD BE UNDERESTIMATED
How does ZF2 help?

19
‘NUFF TALK. TIME FOR ACTION…
We need to add/edit
conferences through
a restricted area

1ST NEED
THE
ADMINISTRATOR

NEEDS TO BE
RECOGNIZED
THE
ADMINISTRATOR

NEEDS TO BE
RECOGNIZED
IDENTIFIED
HEAD TO OFFICIAL MODULES’ WEBSITE
OH, LOOK WHAT WE JUST GOT!
Installing and enabling ZfcUser
// composer.json
"require": {
"zf-commons/zfc-user-doctrine-orm": "0.1.*"
}

28
Installing and enabling ZfcUser
// composer.json
"require": {
"zf-commons/zfc-user-doctrine-orm": "0.1.*"
}

let’s suppose we use the Doctrine ORM

29
Installing and enabling ZfcUser
// composer.json
"require": {
"zf-commons/zfc-user-doctrine-orm": "0.1.*"
}

// config/application.config.php
<?php
return array(
'modules' => array(
// ...
'ZfcBase',
'ZfcUser',
'ZfcUserDoctrineORM',
),
);
30
Back to our user…

31
How shall we represent him?
We need a class
class Systemuser {
private $id;
private $name;
private $city;
private $birthday;
private $username;

private $password;
}
With some mandatory fields
class Systemuser {
private $id;
private $name;
private $city;
private $birthday;
private $username;

private $password;
}
Implementing an interface
class Systemuser
implements
ZfcUserEntityUserInterface {
private $id;
private $name;
private $city;
private $birthday;

private $username;
private $password;
}
Let’s configure ZfcUser
// config/autoload/zfcuser.global.php
/** ZfcUser Configuration */
$settings = array(
/** User Model Entity Class */
'user_entity_class' => 'ApplicationEntitySystemuser',

/** Start configuration for ZfcUserDoctrineORM */
'enable_default_entities' => false,

);

36
Yay, here’s our working login form!

37
Yay, here’s our working login form!

Available at:
http://myaddress/user/login

38
Yay, it works!

39
ZfcUser also allows to:
•
•

•
•

•

40

Customize login form
Customize User entity fields
Quickly implement a registration form
Interact with either Zend/DB or Doctrine
out of the box
Do much more stuff…
ZfcUser also allows to:
•
•

•
•

•

41

Customize login form
Customize User entity fields
Quickly implement a registration form
Interact with either Zend/DB or Doctrine
out of the box
Do much more stuff…
Remember the two steps?

WHO
WHAT
Remember the two steps?

WHO
WHAT
We need an admin panel!

44
We need an admin panel!

Welcome ZfcAdmin!
provides a ready to use /admin route

45
hubme.in has an admin panel!
hubme.in has an admin panel!
Are we done yet?

48
What if
a malicious user…
What if
a malicious user…
What if
a malicious user…
…hits this url:
http://myawesomewebsite/admin/conferences
What if
a malicious user…
…hits this url:
http://myawesomewebsite/admin/conferences

accessible to everyone!
What if
a malicious user…
…hits this url:
http://myawesomewebsite/admin/conferences

nothing’s protecting
our private area
What if
a malicious user…
…hits this url:
http://myawesomewebsite/admin/conferences

nothing’s protecting
our private area

Login form could be
bypassed!
No worries!
/*
* On each action
*/

<?php
public function indexAction() {
if (!$this->zfcUserAuthentication()->hasIdentity())
{
return $this->redirect()->toRoute('home');
}
}

55
No worries!
/*
* On each action
*/

<?php
public function indexAction() {
if (!$this->zfcUserAuthentication()->hasIdentity())
{
return $this->redirect()->toRoute('home');
}
}

56

in EACH action
of EACH controller
WHAAAT?
IN EACH ACTION???
SOMEONE HELP US!
ZENDPERMISSIONSACL
Remember? There were two steps…

WHO
WHAT
Using Zend/Permissions/Acl
<?php
use ZendPermissionsAclAcl;
use ZendPermissionsAclRoleGenericRole as Role;
use ZendPermissionsAclResourceGenericResource as Resource;
$acl = new Acl();
$acl->addRole(new Role('guest'))
->addRole(new Role('admin'));

$acl->addResource(new
$acl->addResource(new
$acl->addResource(new
$acl->addResource(new

$acl->allow('guest',
$acl->allow('admin',
$acl->allow('admin',
$acl->allow('admin',

62

Resource('someResource'));
Resource('adminarea'));
Resource('adminconferencearea'));
Resource('adminsettingsarea'));

'someResource');
'adminarea');
'adminconferencearea ');
'adminsettingsarea ');
Welcome BjyAuthorize!
… a facade for ZendPermissionsAcl
that will ease its usage with modules
and applications …

From https://github.com/bjyoungblood/BjyAuthorize
63
Welcome BjyAuthorize!
… a facade for ZendPermissionsAcl
that will ease its usage with modules
and applications …

From https://github.com/bjyoungblood/BjyAuthorize
64
OUR EASIER WAY
How does it work?

66
Standard ZendMvc app workflow

From https://github.com/bjyoungblood/BjyAuthorize
67
With BjyAuthorize enabled

From https://github.com/bjyoungblood/BjyAuthorize
68
With BjyAuthorize enabled

From https://github.com/bjyoungblood/BjyAuthorize
69
With BjyAuthorize enabled

From https://github.com/bjyoungblood/BjyAuthorize
70
With BjyAuthorize enabled

+ control over resources

From https://github.com/bjyoungblood/BjyAuthorize
71
Installing and enabling BjyAuthorize
// composer.json
"require": {
"bjyoungblood/bjy-authorize": "1.4.*"
}

// config/application.config.php
<?php
return array(
'modules' => array(
// ...
'BjyAuthorize',
),
);

72
Configuring BjyAuthorize
// config/autoload/bjyauthorize.global
return array(

'bjyauthorize' => array(
'default_role' => 'guest',
'identity_provider' =>
'BjyAuthorizeProviderIdentityAuthenticationIdentityProvider',
'role_providers' => array(
'BjyAuthorizeProviderRoleConfig' => array(
'guest' => array(),
'admin' => array(),
),
),
), );

73
Configuring BjyAuthorize
// config/autoload/bjyauthorize.global
return array(

'bjyauthorize' => array(

A new concept: the Role

'default_role' => 'guest',
'identity_provider' =>
'BjyAuthorizeProviderIdentityAuthenticationIdentityProvider',
'role_providers' => array(
'BjyAuthorizeProviderRoleConfig' => array(
'guest' => array(),
'admin' => array(),
),
),
), );

74
Guards on routes
http://myawesomewebsite/
Allowed to all users

75
Guards on routes
http://myawesomewebsite/
Allowed to all users

http://myawesomewebsite/admin/...
Restricted area! For admins only

76
Guards on controller actions
class ConferencesController {
public function listAction() {
// code...
}

public function manageAction() {
// code...
}

}

77
Guards on controller actions
class ConferencesController {
public function listAction() {
// code...
}
Allowed

public function manageAction() {
// code...
}

}

78

to all users
Guards on controller actions
class ConferencesController {
public function listAction() {
// code...
}
Allowed

to all users

public function manageAction() {
// code...
}

}

79

Restricted area! For admins only
Guards on controller actions
array(
'controller' => 'ZfcAdminControllerAdminController',
'roles' => array('admin')
)

80
Where should
guards be placed?

81
Inside each module configuration
// module/Conferences/config/module.config.php
return array(
'bjyauthorize' => array(
'guards' => array(

'BjyAuthorizeGuardController' => array(
//...
),
), ),

82
Inside each module configuration
// module/Conferences/config/module.config.php
return array(
'bjyauthorize' => array(

Taking advantage of ZF2
configuration merge
'BjyAuthorizeGuardController' => array(

'guards' => array(

//...
),
), ),

83
It works!
It works!

User could be redirected
to whatever url we want
Dude, forgot to tell
ya! …we got 2
fellas!

2ND NEED
Two different roles

The reader
87
Two different roles

The reader
88
Two different roles

The reader
89

The editor
Two different roles
Can only view
conference info

The reader
90

Can view
conferences +
create, edit and
delete info

The editor
What we want

Only editor should
see these icons
Until now…
'bjyauthorize' => array(
// ...
'role_providers' => array(
'BjyAuthorizeProviderRoleConfig' => array(
'guest' => array(),
'admin' => array(),
),
),

)

Static role list

93
Until now…
'bjyauthorize' => array(
// ...
'role_providers' => array(
'BjyAuthorizeProviderRoleConfig' => array(
'guest' => array(),
'admin' => array(),
),
),

)

More flexibility wouldn’t hurt…

94
BjyAuthorize config changes
// config/autoload/bjyauthorize.global
return array(

'bjyauthorize' => array(
'role_providers' => array(
'BjyAuthorizeProviderRoleObjectRepositoryProvider' => array(
'role_entity_class' => 'ApplicationEntityRole',
'object_manager' => 'doctrine.entity_manager.orm_default', ),
),

), );

From array to class (persisted on db)
95
Let’s map the actions
New concept: the Resource
something
something
upon which
someone
something
upon which
someone
could perform
an action
ENTITY

IDENTITY / ROLE

PRIVILEGE
On BjyAuthorize…
'resource_providers' => array(
'BjyAuthorizeProviderResourceConfig' => array(
'Conference' => array(),
),
),
'rule_providers' => array(
'BjyAuthorizeProviderRuleConfig' => array(
'allow' => array(
// allow editors to edit conferences
array(array('editor'), 'Conference', array('edit')),
),
),

102
On views…
//Conferences/view/…/index.phtml
<?php if ($this->isAllowed($event, 'edit')) { ?>
<a href="someurl">Remove</a><br />
<a href="someurl">Edit</a>
<?php } ?>

103
Views, routes and
controllers are safe

104
Views, routes and
controllers are safe
Is this enough?
105
Another controller, another action
//Conferences/Controller/AnotherAdminController.php
class AnotherAdminController extends AbstractActionController {

public function someCrazyAction() {
//...

$this->conferenceService->updateConference($myConference);
}
}

What prevents this?

106
107

SERVICE

CONTROLLER

ROUTE

Choose your protection level
Conference service
//Conferences/Service/ConferenceService.php
namespace ConferencesService;

class ConferenceService {
public function getConference($id) { ... }
public function getConferenceList($someCriteria) { ... }
public function updateConference($myConf) { ... }
public function deleteConference($myConf) { ... }
}

108
Conference service
//Conferences/Service/ConferenceService.php
namespace ConferencesService;

class ConferenceService {
public function getConference($id) { ... }
public function getConferenceList($someCriteria) { ... }
public function updateConference($myConf) { ... }
public function deleteConference($myConf) { ... }
}

Only to allowed users!

109
Let’s inject the Authorize class
//Conferences/Service/ConferenceServiceFactory.php
namespace ConferencesService;

class ConferenceServiceFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator)
{
//...
$authorize = $serviceLocator->get('BjyAuthorizeServiceAuthorize');
return new ConferenceService(..., $authorize);

}
}

110
Updated conference service
//Conferences/Service/ConferenceService.php
namespace ConferencesService;

class ConferenceService {
//...
public function updateConference($myConf) {
if (!$this->authorize->isAllowed($myConf, 'edit')) {
throw new UnAuthorizedException();
}
// other code...
} // the same for deleteConference method }

111
112

SERVICE

CONTROLLER

ROUTE

Now our service is secured
We’ll outsource the
management of
foreign conferences

3RD NEED
Based on their country
How database changes
class Systemuser {
//...

private $country;
}

class Conference {
//...
private $country;
}

116
Create an Assertion
use ZendPermissionsAclAssertionAssertionInterface;
class CheckUserCountry implements AssertionInterface {

// ...
public function assert(Acl $acl,
RoleInterface $role = null,
ResourceInterface $resource = null,
$privilege = null) {
// ...

}
}

117
Create an Assertion
use ZendPermissionsAclAssertionAssertionInterface;
class CheckUserCountry implements AssertionInterface {

// ...
public function assert(Acl $acl,
RoleInterface $role = null,
ResourceInterface $resource = null,
$privilege = null) {
return $resource->getCountry() ==
$this->loggedUser->getCountry();
}
}

118
Create an Assertion
use ZendPermissionsAclAssertionAssertionInterface;
class CheckUserCountry implements AssertionInterface {

// ...

Injected through constructor

public function assert(Acl $acl,
RoleInterface $role = null,
ResourceInterface $resource = null,
$privilege = null) {
return $resource->getCountry() ==
$this->loggedUser->getCountry();
}
}

119
Create an Assertion
use ZendPermissionsAclAssertionAssertionInterface;
class CheckUserCountry implements AssertionInterface {

// ...
public function assert(Acl $acl,
RoleInterface $role = null,
ResourceInterface $resource = null,
$privilege = null) {
return $resource->getCountry() ==
$this->loggedUser->getCountry();
}
}

120

1 LoC… AWESOME!!
Update rule with the new assertion
'rule_providers' => array(
'BjyAuthorizeProviderRuleConfig' => array(
'allow' => array(
// role check through assertion
array(array('editor'),
'Conference',
array('edit'),
'assertion.CheckUserCountry'),
),
),

121
The reader

122
The reader

The editor

123
In the same way we could:
•

•

•

124

Restrict access to user owned
onferences only
or conferences owned by a group the
user is belonging to
…and much more!
Cool. How'bout the
admin menu
though?

4TH NEED
Navigation menu

126
Configure Zend/Navigation
// module/Conferences/config/module.config.php
return array(

'navigation' => array(
'admin' => array(
'conferences' => array(
'label' => 'Conferences',
'route' => 'zfcadmin/conferences',
'resource' => 'Conference',
'privilege' => 'view',
),
),
),
), );

127
Configure Zend/Navigation
// module/Settings/config/module.config.php
return array(

'navigation' => array(
'admin' => array(
'settings' => array(
'label' => 'Settings',
'route' => 'zfcadmin/settings',
'resource' => 'Setting',
'privilege' => 'view',
),
),
),
), );

128
How menu looks like for admins
How menu looks like for other users
FINAL NOTES
PLUGGABLE COMPONENTS
PLUGGABLE COMPONENTS

CLEAN ARCHITECTURE
PLUGGABLE COMPONENTS

CLEAN ARCHITECTURE

COMPLEX ACL LOGIC IN A FEW MINUTES
Thank you for your attention!

Stefano Valle
@stefanovalle
s.valle@mvassociati.it
Questions?

Stefano Valle
@stefanovalle
s.valle@mvassociati.it
Photo Credits
From Flickr:
http://www.flickr.com/photos/cbpphotos/8652042987
http://www.flickr.com/photos/disa4ever/9409743179
http://www.flickr.com/photos/ben_salter/6169305845
http://www.flickr.com/photos/elzey/3481161467
http://www.flickr.com/photos/morris278/8022505933
A-Team members’ photos:
http://5gta.com/gta-5-info/gta-5-the-a-team-similarities.html/
http://www.legendarytv.com/the_a-team/the_a-team_lance_legault.asp
http://www.fanpop.com/clubs/the-a-team/images
http://dwightschultz.freeforums.org/dwight-photo-s-t8.html
http://docmanhattan.blogspot.it/2010/10/vita-mort-immortalita-e-miracoli-di-mr.html
http://www.starsky-iom.com/forum/viewtopic.php?f=8&t=58
http://www.thea-teamonline.com/
And others form iStockPhoto

137

Instant ACLs with Zend Framework 2