Second ZfcRbac Assertion is not working | ZF2 - zend-framework2

I have added below code inside zfc_rbac.global.php:
return [
'zfc_rbac' => [
'assertion_map' => [
'isAuthorizedToAddUser' => 'Application\Assertions\WhoCanAddUser',
'isBranchOrOrgIdPresentIfNotAdmin' => 'Application\Assertions\BranchOrOrgIdPresentIfNotAdmin'
]
]]
And used it inside controller like below:
if (! $this->authorizationService->isGranted('isBranchOrOrgIdPresentIfNotAdmin')) {
throw new UnauthorizedException('You are not authorized to add this aaa!');
}
but its throwing the exception even if I return true from assert method. But if I replace isBranchOrOrgIdPresentIfNotAdmin with isAuthorizedToAddUser, its working fine. What could be wrong here. Second assertion class BranchOrOrgIdPresentIfNotAdmin is just the replica of WhoCanAddUser class. Below is my WhoCanAddUser assertion class.
namespace Application\Assertions;
use ZfcRbac\Assertion\AssertionInterface;
use ZfcRbac\Service\AuthorizationService;
use ZfcRbac\Exception\UnauthorizedException;
use Zend\Session\Container;
class WhoCanAddUser implements AssertionInterface
{
protected $notAuthorizedMessage = 'You are not authorized to add this user!';
public function __construct()
{
$this->org_session = new Container('org');
}
/**
* Check if this assertion is true
*
* #param AuthorizationService $authorization
* #param mixed $role
*
* #return bool
*/
public function assert(AuthorizationService $authorization, $role = null)
{
return true; //added this for testing if true is working and it worked, but second assertion is not working!
switch($authorization->getIdentity()->getRole()->getName()){
case 'admin':
return true;
break;
case 'owner':
if($role != 'member'){
throw new UnauthorizedException($this->notAuthorizedMessage);
}
return true;
break;
default:
throw new UnauthorizedException($this->notAuthorizedMessage);
break;
}
if($authorization->getIdentity()->getRole()->getName() != 'admin' && !$this->org_session->offsetExists('branchId')){
throw new \Zend\Session\Exception\RuntimeException('You need to be connected to an Organisation's branch before you can add members. Contact your Organisation Owner.');
}
}
}
Am I missing something that second assertion is not working at all.

Just found that, isBranchOrOrgIdPresentIfNotAdmin entry has to be inside permission table and have to assign that permission to lower level of role inside hierarchicalrole_permission table (that permission will be given to upper level of role as well in hierarchical way automatically) and it will work fine for all of them.

Related

Type Error exception while unit testing (zend-test, PHPUnit)

I am trying to test a simple controller action in Zend FrameWork and I am not 100% sure why my mocks are not working.
Original Action:
public function overviewAction()
{
$page = $this->params()->fromQuery('page', 1);
$count = 10;
$user = $this->authenticationService->getIdentity();
return new ViewModel([
'paginator' => $this->agentService->getAgentsOwnedByUser($page, $count, $user),
]);
}
My Test for this action
/**
* Set Rbac Role and route
*/
$url = "cp/agent";
$this->setRbacGuards(['admin']);
//Nb Rbac class code is here
/**
* Objects required in this test
*/
$user = $this->createMock(User::class);
$paginator = $this->createMock(Paginator::class);
/**
* Mock classes and their methods to be called
*/
$authentication = $this->createMock(AuthenticationService::class);
$authentication
->expects($this->once())
->method('getIdentity')
->will($this->returnValue($this->registerMockObject($user)));
$agentService = $this->createMock(AgentService::class);
$agentService
->expects($this->once())
->method('getAgentsOwnedByUser')
->will($this->returnValue($this->registerMockObject($paginator)));
$this->dispatch('/'.$url);
$this->assertResponseStatusCode(200);
The error message
There was 1 failure:
1) ControlPanelTest\Controller\AgentControllerTest::testAgentOverviewActionCanBeAccessed
Failed asserting response code "200", actual status code is "500"
Exceptions raised:
Exception 'TypeError' with message 'Argument 3 passed to
Admin\Service\AgentService::getAgentsOwnedByUser() must be an instance of Domain\User\User, null given
.
For completeness, Rbac Class
public function rbacGuards($roles)
{
/**
* Deal with Rbac Guards
*/
$roleService = $this->getApplicationServiceLocator()->get(RoleService::class);
$identityProvider = $this->prophesize(IdentityProviderInterface::class);
$identity = $this->prophesize(IdentityInterface::class);
// Here you use the setter to inject your mocked identity provider
$roleService->setIdentityProvider($identityProvider->reveal());
$identityProvider->getIdentity()->shouldBeCalled()->willReturn($identity->reveal());
$identity->getRoles()->shouldBeCalled()->willReturn($roles);
}
Prognosis
It seems the mocks are not being called...
In your example, you create $authentication mock, but you don't register it as property of class you are testing.
Thus, when overviewAction is using $this->authenticationService->getIdentity();, it is not using the mock you created.

zend-authentication - setting identity to custom object with rbac roles loaded

In a ZF2 project i am using the AuthenticationService to validate a users log in credentials. This is working fine, except it only stores in the session a string containing the users name.
What i would like would be for subsequent calls to AuthenticationService::getIdentity to return a custom Identity object, that is populated with the users database id, roles and permissions (popualted from an RBAC service), so that the object in the session is a bit more useful.
I am able to create this object, but am unsure of the best way to keep it in the session; ideally i would like to override the entry with the key Zend_Auth, but this does not seem to be working.
My code so far:
<?php
namespace Authentication\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Authentication\AuthenticationService;
use Authentication\Form\Login\LoginForm;
use Zend\Form\Form;
use Authentication\Model\Identity\AuthenticatedIdentity;
class AuthenticationController extends AbstractActionController
{
/**
*
* #var AuthenticationService
*/
protected $authenticationService;
/**
*
* #var LoginForm
*/
protected $loginForm;
/**
*
* #param AuthenticationService $authenticationService
* #param LoginForm $loginForm
*/
public function __construct(AuthenticationService $authenticationService, LoginForm $loginForm){
$this->authenticationService = $authenticationService;
$this->loginForm = $loginForm;
}
public function indexAction(){
$form = $this->loginForm;
$viewModel = new ViewModel();
$viewModel->setVariables([
'loginForm' => $form
]);
if($this->getRequest()->isPost() === false){
return $viewModel;
}
$form->setData($this->getRequest()->getPost());
if($form->isValid() === false){
return $viewModel;
}
$data = $form->getData();
$authenticationAdapter = $this->authenticationService->getAdapter();
$authenticationAdapter->setIdentity($data['credentials']['username'])
->setCredential($data['credentials']['password']);
$authenticationResult = $this->authenticationService->authenticate($authenticationAdapter);
if($authenticationResult->isValid() === false){
$viewModel->setVariable('validCredentials', false);
return $viewModel;
}
/**
* Create a user model and save it to the session.
*/
$authenticationResultRow = $authenticationAdapter->getResultRowObject(null, ['password']);
$permissions = $this->rbacService->getPermissionsForUser($authenticationResultRow->user_id);
$roles = $this->rbacService->getRolesForUser($authenticationResultRow->user_id);
$identity = new AuthenticatedIdentity(
$authenticationResult->getIdentity(),
'admin',
$permissions,
$roles
);
$identity->setUserId($authenticationResultRow->user_id);
//how to store this Identity object in session so AuthenticationService will return it?
return $this->redirect()->toRoute('dashboard');
}
}
Check out https://github.com/zendframework/zend-authentication/blob/master/src/AuthenticationService.php#L75 and https://github.com/zendframework/zend-authentication/blob/master/src/Storage/StorageInterface.php
You can write the AuthenticatedIdentity object directly to the storage like so:
$this->authenticationService->getStorage()->write($identity);
However, I would advice against doing so because:
If the user's permissions/roles change during the session he/she would have to log out and back in to see any changes which is not very user-friendly.
Your AuthenticatedIdentity object and all objects it contains need to be serializable, which can become problematic to maintain.
I would (and do) fetch the user object and/or roles when needed, either from DB or some form of cache but don't store it in the session.

Yii2 Call to a member function load() on a non-object

I have standard code in controller for User class
public function actionEdit($username)
{
...
$model = User::findByUsername($username);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
And if I edit user I get error
Call to a member function load() on a non-object
which is pointing at $model->load
Why is that ?
UPDATE
var_dump on $model shows NULL
which is strange
because I have the same function used in view action and it works perfectly
public function actionView($username){
$model = User::findByUsername($username);
if($model){
UPDATE2
I've made some changes now the code is like this, no errors
but the logic goes that there is no load and save, cause if goes to else section and edit is loaded again not view.
$model = User::findByUsername($username);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'username' => $model->username]);
} else {
return $this->render('edit', [
'model' => $model,
}
Use
var_dump($model);
To see what exacly $model is. Propably it's not a model, check what's result of function findByUsername()
make sure your function is check and find one, like
protected function findByUsername($username)
{
if (($model = User::find()->where(['username' => $username])->one()) !== null) {
return $model;
} else {
throw new NotFoundHttpException('not found.');
}
}

Laravel Validator AND / require a field ONLY if both conditions are met

Using Laravel 5.1, is there a way to require a field ONLY if both conditions are met?
In the code example below, in the rules, I have field2_en and field2_de. I put 2 required_if rules for each field, the field becomes required if 1 of the 2 OR both required_if rules is/are met. It's not what I want. Here is what I want:
I would like field2_en to be required ONLY if language = en AND
radiobutton = 2
I would like field2_de to be required ONLY if language = de AND
radiobutton = 2
AddRequest.php
namespace App\Http\Requests\Post;
use App\Http\Requests\Request;
class AddRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return auth();
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'language' => ['required', 'in:en,de'],
'field1_en' => ['required_if:language,en'],
'field1_de' => ['required_if:language,de'],
'radiobutton' => ['required', 'in:1,2'],
'field2_en' => ['required_if:language,en', 'required_if:radio,2'],
'field2_de' => ['required_if:language,de', 'required_if:radio,2']
];
}
}
I have been able to do it with sometimes() like below or with extend, but I wanted to know if there was other ways of doing it since I am using laravel-jsvalidation plugin and it doesn't work with such sometimes conditional validations.
protected function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->sometimes('field2_en', 'required|min:2', function($input)
{
return ($input->language == 'en' && $input->radiobutton == '2');
});
...
return $validator;
}

RavenDB and IDocumentStoreListener

I am trying to add an autoincrement to a simple model via an IDocumentStoreListener. I have found that the documentation regarding implementation of this feature is fairly sparse (any pointers would be greatly appreciated). I have been trying to follow this blog post but it appears to be out of date. When i try to implement
store = new EmbeddableDocumentStore
{
RunInMemory = true
}
.RegisterListener(new AuditableEntityListener(() => "Test User"))
.Initialize();
I get a build error stating "Cannot convert lambda expression to type Raven.Client.IDocumentStore because it is not a delegate type".
I managed to get it to build by using this code
store = new EmbeddableDocumentStore
{
RunInMemory = true
}
.RegisterListener(new AuditableEntityListener(store ))
.Initialize();
The code for the listener is as follows
public class PublicIdStoreListener : IDocumentStoreListener
{
HiLoKeyGenerator generator;
IDocumentStore store;
public PublicIdStoreListener(IDocumentStore store)
{
this.store = store;
generator = new HiLoKeyGenerator(store, "verifications", 1024);
}
public void AfterStore(string key, object entityInstance, RavenJObject metadata)
{
throw new NotImplementedException();
}
public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
var verification = entityInstance as VerifyAccountModel;
if (verification.PublicId == "0")
{
verification.PublicId = generator.GenerateDocumentKey(store.Conventions, entityInstance);
}
return false;
}
}
However, when i run the application it hits the PublicIdStoreListener when any document is stored, not just the VerifyAccountModel, which causes the application to throw an exception.
I was wondering if anyone can point me in the right direction on this as I am confused as to how this is actually supposed to be implemented. Thanks in advance.
EDIT
I updated the documentlistener to the following
public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
if (entityInstance.GetType() == new VerifyAccountModel().GetType())
{
var verification = entityInstance as VerifyAccountModel;
if (verification.PublicId == "0")
{
verification.PublicId = generator.GenerateDocumentKey(store.Conventions, entityInstance);
}
}
return true;
}
UPDATE
I figured out that i cant attach the store via RegisterListener in the same line that it is instantiated. It has to be done afterwards otherwise the store is still null when passed in. Thank you for your help.
I am not sure if there's a way to register the listener to only fire for certain types, but you can certainly structure your code to only process VerifyAccountModel entities.
var verification = entityInstance as VerifyAccountModel;
if (verification == null)
return false; // We can't do anything, just let it pass through
Also, my understanding is that you should return true when you make a change, false if no change was made. This determines whether the entity needs to be re-serialized. If that is correct, the whole thing might be restructured as follows.
var verification = entityInstance as VerifyAccountModel;
if (verification != null && verification.PublicId == "0")
{
verification.PublicId = generator.GenerateDocumentKey(store.Conventions, entityInstance);
return true; // change made, re-serialize
}
return false; // no change made

Resources