Set permission for creating nodes in code - drupal

Can I set permissions to allow authenticated users to add nodes of a custom type? I need to do that in module i'm trying to create. As I can see hook_permission it's actually just for creating new permissions.

If your module name is say mymodule, then defien a your "create node" permission in hook_permission. Then implement hook_node_access to check and return permission for the content type you have implemented.
SAMPLE CODE.
NB: It won't work out of the box, you got to replace your module name, permission name and the content type name to get it to work. And don't forget to clear your cache, TWICE!.
/**
* Implements hook_permission().
*
*/
function mymodule_permission() {
// define your add permission.
// Naming of "array key" is important. We use that later.
return array(
'YOUR CONTENT NAME: add' => array(
'title' => t('Add Project Management Team'),
),
);
}
/**
* Implements hook_node_access().
*/
function mymodule_node_access($node, $op, $account = NULL) {
$type = is_string($node) ? $node : $node->type;
// make sure you are responding to content type defined by your module only.
if ($type == 'YOUR_CONTENT_TYPE_NAME_HERE') {
// If no account is specified, assume that the check is against the current logged in user
if (is_null($account)) {
global $user;
$account = $user;
}
if ($op == 'create' AND user_access('YOUR CONTENT NAME: add', $account)) {
return NODE_ACCESS_ALLOW;
}
}
return NODE_ACCESS_IGNORE;
}
References:
hook_permission().
hook_node_access().

Related

Drupal 8 Class 'Drupal\Core\Session\AccountInterface' not found

I am trying to write a custom php script in my Drupal site root that checks if the user is logged in. To check this I import bootstrap.inc. However when I do this it throws me this error
This is the code of the php script in my site root:
<?php
require_once './core/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
global $user;
var_dump($user->uid);
?>
Anyone has a solution to this?
To bootstrap Drupal 8, you need different code. Drupal 8 doesn't have any drupal_bootstrap() function, so the code you are using would throw a PHP error.
You can use authorize.php as guideline to write your own script.
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$autoloader = (require_once 'autoload.php');
try {
$request = Request::createFromGlobals();
$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
$kernel->prepareLegacyRequest($request);
} catch (HttpExceptionInterface $e) {
$response = new Response('', $e->getStatusCode());
$response
->prepare($request)
->send();
exit;
}
\Drupal::moduleHandler()
->addModule('system', 'core/modules/system');
\Drupal::moduleHandler()
->addModule('user', 'core/modules/user');
\Drupal::moduleHandler()
->load('system');
\Drupal::moduleHandler()
->load('user');
$account = \Drupal::service('authentication')
->authenticate($request);
if ($account) {
\Drupal::currentUser()
->setAccount($account);
if (\Drupal::currentUser()->isAuthenticated() {
// The user is logged-in.
}
}
I fixed this by using a complete different approach. I wrote a module which sets a cookie on the moment that the user logs in to drupal (I use the hook_user_login for this). When the user logs out I delete that cookie (I use the hook_user_logout for this). This is the code of my test.module:
/**
* #param $account
*/
function intersoc_content_user_login($account)
{
setcookie("user", "loggedin", time() + (86400 * 30),"/");
}
/**
* #param $account
*/
function intersoc_content_user_logout($account)
{
if (isset($_COOKIE['user']))
{
unset($_COOKIE['user']);
setcookie('user', '', time() - 3600, '/'); //Clearing cookie
}
}
Then in my custom script in the site root I check if the cookie is set. When the cookie exists => The user is logged in. If the cookie doesn't exist then the user isn't logged in. The isLoggedIn() function below:
/**
* #return bool which indicates if the user is logged in or not
*/
private function isLoggedIn()
{
if(isset($_COOKIE["user"]))
{
return TRUE;
}
else
{
return FALSE;
}
}
It isn't the most beautiful solution, but it works!!!

In Symfony2 how can I get a users full list of roles

I would like to pass the authenticated users list of roles to my front end apps, so I can use the same access control structure in the front and back end.
I was looking in the security / authentication classes as that is where the isGranted function are for me to do this
$this->container->get('security.context')->isGranted('ROLE_SUPER_ADMIN')
I can't find anything to get a list of roles though, is this not a supported feature?
nb: I don't want the entire role hierarchy, just the list of roles for the authenticated user
I ended up adding a new repository function and a service method to get this info.
MyProject/UserBundle/Entity/Repository/UserRepository
public function getRoles($userId)
{
$queryBuilder = $this->createQueryBuilder('u');
$queryBuilder
->select('u.id, u.roles AS user_roles, g.roles AS group_roles')
->leftJoin('u.groups', 'g')
->andWhere('u.id = :user_id')
->setParameter('user_id', $userId);
return $queryBuilder->getQuery()->getArrayResult();
}
MyProject/UserBundle/Service/UserService
public function getUserRoles($user)
{
$groupRoles = $this->repository->getRoles($user->getId());
$roles = array('user_roles' => array(), 'group_roles' => array());
foreach ($groupRoles as $groupRole) {
$roles['user_roles'] = array_merge($roles['user_roles'], $groupRole['user_roles']);
$roles['group_roles'] = array_merge($roles['group_roles'], $groupRole['group_roles']);
}
return $roles;
}
This gives me an array like this
"roles":{
"user_roles":[],
"group_roles":["ROLE_ADMIN","ROLE_ONE","ROLE_TWO","ROLE_BEST"]
}
Assuming you're using the Symfony security component, the user interface which your user class implements has this already included:
$user = $this->get('security.token_storage')->getToken()->getUser();
var_dump($user->getRoles());
http://api.symfony.com/3.1/Symfony/Component/Security/Core/User/UserInterface.html#method_getRoles

Symfony 2: Form Permissions Authorization Strategy

I have a Symfony application with five different entities (what they are doesn't really matter).
For each of these entities, a registered user must either have NONE, READ, EDIT, DELETE permissions. The sticky part for me to grasp is that each user can have different permissions for each entity; User A can edit Entity A, but can only view Entity B, etc.
Now on each user's options page, an admin should be able to see his permissions for each form. Radio buttons should be displayed with the four options for each form. Something like:
Entity A: O NONE O READ X EDIT O DELETE
Entity B: O NONE X READ O EDIT O DELETE
...
I know my choices are basically between creating some type of Voter system or an Access Control List.
At first I just started by listing all of the roles currently in the system within my UserType:
$builder
...
->add('roles', 'choice', array(
'choices' => $this->roles,
'choices_as_values' => true,
'label' => 'Roles',
'expanded' => true,
'multiple' => true,
'mapped' => true,
))
;
but I'm feeling like this isn't going to be very effective in the long run. And either way, this also displays other system roles that have nothing to do with access control to specific entities (such as ROLE_USER, ROLE_ADMIN, etc.)
I'm not looking for a complete solution or anything like that, I'm just having a really hard time getting started and seeing the big picture on how to make this happen. (And yes, I am aware of the Symfony documentation...sometimes that stuff just doesn't make a ton of sense at first).
PROGRESS UPDATE
I decided on Access Control List.
First, when a new entity is created, I use the standard ACL creation strategy as mentioned in the Symfony Documentation:
public function postAvrequestAction(Request $request){
$entity = new AvRequest();
$form = $this->get('form.factory')->createNamed('', new AvRequestType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$serializer = $this->get('serializer');
$serialized = $serializer->serialize($entity, 'json');
// creating the ACL
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = ObjectIdentity::fromDomainObject($entity);
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$tokenStorage = $this->get('security.token_storage');
$users = $em->getRepository('AppBundle:User')->findAll();
//$tokenStorage->getToken()->getUser();
foreach($users as $user){
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access based on owner's overall permissions for this type of entity
$acl->insertObjectAce($securityIdentity, 0);
$aclProvider->updateAcl($acl);
}
return new Response($serialized, 201);
}
return new JsonResponse(array(
'errors' => $this->getFormErrors($form)
));
}
Next, I created a service with all of the necessary dependencies to update a user's permissions for each entity:
#services.yml
services:
user_service:
class: AppBundle\Resources\Services\UserService
arguments: [ #doctrine.orm.entity_manager, #service_container, #security.authorization_checker, #security.acl.provider ]
The service has the function:
/**
* ACLs grant user permission on every instance of each entity.
* In order to edit permissions across all of these entites for each user,
* first iterate over all entities.
* For each entity, update the permission for the specified user.
*
* #param \AppBundle\Entity\User $user The user object whose permissions should be updated
* #param String $entity The entity whose permissions should be updated (e.g. 'AppBundle:AvRequest')
* #param int $permission The bitmask value of the permission level (e.g. MaskBuilder::MASK_VIEW (=4))
*
* #return null
*/
public function editPermission(User $user, $entity, $permission){
$allEntities = $this->em->getRepository($entity)->findAll();
foreach($allEntities as $oneEntity){
// locate the ACL
$objectIdentity = ObjectIdentity::fromDomainObject($oneEntity);
$acl = $this->aclProvider->findAcl($objectIdentity);
// update user access
$objectAces = $acl->getObjectAces();
foreach($objectAces as $i => $ace) {
$acl->updateObjectAce($i, $permission);
}
}
}
This function goes through every instance of the entity and gives it the same permission level for the specified user.
The next step that I haven't quite figured out yet is setting a master permission level for a user on an entity as described up top with my radio buttons. I need to be able to go to the user's profile page, see a radio list of the user's permissions for each entity type, submit the radio button value and then run the editPermission() function on save.
You are looking for Access Control Lists. It is easy to set permission by user or group of users.
Add access level by user:
$builder = new MaskBuilder();
$builder
->add('view')
->add('edit')
->add('delete')
->add('undelete')
;
$mask = $builder->get(); // int(29)
$identity = new UserSecurityIdentity('johannes', 'AppBundle\Entity\User');
$acl->insertObjectAce($identity, $mask);
Specify min access level by entity:
public function addCommentAction(Post $post)
{
$comment = new Comment();
// ... setup $form, and submit data
if ($form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($comment);
$entityManager->flush();
// creating the ACL
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = ObjectIdentity::fromDomainObject($comment);
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$tokenStorage = $this->get('security.token_storage');
$user = $tokenStorage->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
}
}
public function editCommentAction(Comment $comment)
{
$authorizationChecker = $this->get('security.authorization_checker');
// check for edit access
if (false === $authorizationChecker->isGranted('EDIT', $comment)) {
throw new AccessDeniedException();
}
// ... retrieve actual comment object, and do your editing here
}

fosuserbundle ldap configuration for strange use case

I'm trying to create a fosuserbundle for a quite strange use case, which is mandatory requirement, so no space to diplomacy.
Use case is as follow:
users in a mongo db table populated by jms messages -no registration form
users log in by ldap
user record not created by ldap, after a successful login username is checked against mongodb document
Considering that ldap could successfully log in people that exhist in ldap but cannot access site (but login is still successful), what could be the best way to perform such authentication chain?
I was thinking about some possible options:
listen on interactive login event, but imho there's no way to modify an onSuccess event
create a custom AuthenticationListener to do another check inside onSuccess method
chain authentication using scheb two-factor bundle
any hint?
I've used Fr3DLdapBundle which can be incorporate with FOSUserBundle quite easily (I'm using the 2.0.x version, I have no idea if the previous ones will do the same or be as easy to set up).
In the LdapManager (by default) it creates a new user if one is not already on the database which is not what I wanted (and doesn't seem to be what you want) so I have added my own manager that checks for the presence of the user in the database and then deals with the accordingly.
use FR3D\LdapBundle\Ldap\LdapManager as BaseLdapManager;
.. Other use stuff ..
class LdapManager extends BaseLdapManager
{
protected $userRepository;
protected $usernameCanonicalizer;
public function __construct(
LdapDriverInterface $driver,
$userManager,
array $params,
ObjectRepository $userRepository,
CanonicalizerInterface $usernameCanonicalizer
) {
parent::__construct($driver, $userManager, $params);
$this->userRepository = $userRepository;
$this->usernameCanonicalizer = $usernameCanonicalizer;
}
/**
* {#inheritDoc}
*/
public function findUserBy(array $criteria)
{
$filter = $this->buildFilter($criteria);
$entries = $this->driver->search(
$this->params['baseDn'], $filter, $this->ldapAttributes
);
if ($entries['count'] > 1) {
throw new \Exception('This search can only return a single user');
}
if ($entries['count'] == 0) {
return false;
}
$uid = $entries[0]['uid'][0];
$usernameCanonical = $this->usernameCanonicalizer->canonicalize($uid);
$user = $this->userRepository->findOneBy(
array('usernameCanonical' => $usernameCanonical)
);
if (null === $user) {
throw new \Exception('Your account has yet to be set up. See Admin.');
}
return $user;
}

Check if a role is granted for a specific user in Symfony2 ACL

I want to check if a role is granted for a specific user in Symfony2 (not the logged user).
I know that I can check it for the logged user by:
$securityContext = $this->get('security.context');
if (false === $securityContext->isGranted('VIEW', $objectIdentity)) {
//do anything
}
but if I'm the logged user and I wand to check other user if isGranted ??
The "VIEW" is a permission, not a role.
The best way to check if a user has a right (be it a role or permission) would be to access the AccessDecisionManager. Something like:
$token = new UsernamePasswordToken($user, 'none', 'none', $user->getRoles());
$attributes = is_array($attributes) ? $attributes : array($attributes);
$this->get('security.access.decision_manager')->decide($token, $attributes, $object);
See original answer here: https://stackoverflow.com/a/22380765/971254 for details.
You just need to create a custom security context that will take a user object and generate a UserSecurityIdentity out of it. Here are the steps:
Create a new service in YourApp/AppBundle/Resources/config.yml
yourapp.security_context:
class: YourApp\AppBundle\Security\Core\SecurityContext
arguments: [ #security.acl.provider ]
Create a custom Security Context Class like this:
namespace YourApp\AppBundle\Security\Core;
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
use Symfony\Component\Security\Acl\Exception\NoAceFoundException;
use YourApp\AppBundle\Document\User;
/**
* Allows ACL checking against a specific user object (regardless of whether that user is logged in or not)
*
*/
class SecurityContext
{
public function __construct(MutableAclProviderInterface $aclProvider)
{
$this->aclProvider = $aclProvider;
}
public function isGranted($mask, $object, User $user)
{
$objectIdentity = ObjectIdentity::fromDomainObject($object);
$securityIdentity = UserSecurityIdentity::fromAccount($user);
try {
$acl = $this->aclProvider->findAcl($objectIdentity, array($securityIdentity));
} catch (AclNotFoundException $e) {
return false;
}
if (!is_int($mask)) {
$builder = new MaskBuilder;
$builder->add($mask);
$mask = $builder->get();
}
try {
return $acl->isGranted(array($mask), array($securityIdentity), false);
} catch (NoAceFoundException $e) {
return false;
}
}
}
Now you can inject that service where needed, or use it from a controller like this:
$someUser = $this->findSomeUserFromYourDatabase();
if ($this->get('yourapp.security_context')->isGranted('VIEW', $article, $someUser) {
// ...
}
Checking roles for another user can not be done via the SecurityContext as this will always hold the current user's session token. Your task can be achieved for example via the getRoles method, if the user you need to check implements the UserInterface.
$otherUser = $this->get('doctrine')->... // fetch the user
if( $otherUser instanceof \Symfony\Component\Security\Core\User\UserInterface )
{
$roles = $otherUser->getRoles();
// your role could be VIEW or ROLE_VIEW, check the $roles array above.
if ( in_array( 'VIEW' , $roles ) )
{
// do something else
}
}
If your user entity implement the FosUserBundle UserInterFace, that has a dedicated method hasRole. In that case you could use a one-liner:
$otherUser = $this->get('doctrine')->... // fetch the user
if( $otherUser instanceof \FOS\UserBundle\Model\UserInterface )
{
// your role could be VIEW or ROLE_VIEW, check the proper role names
if ( $otherUser->hasRole( 'VIEW' ) )
{
// do something else
}
}

Resources