Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Acl #274

Open
wants to merge 5 commits into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/Jackalope/ObjectManager.php
Expand Up @@ -4,7 +4,10 @@
use ArrayIterator;
use InvalidArgumentException;

use Jackalope\Security\AccessControlList;
use Jackalope\Transport\NodeTypeFilterInterface;
use Jackalope\Transport\SetPolicyOperation;
use PHPCR\Security\AccessControlPolicyInterface;
use PHPCR\SessionInterface;
use PHPCR\NodeInterface;
use PHPCR\PropertyInterface;
Expand Down Expand Up @@ -151,6 +154,8 @@ class ObjectManager
*/
protected $nodesMove = array();

protected $policies = array();

/**
* Create the ObjectManager instance with associated session and transport
*
Expand Down Expand Up @@ -200,6 +205,7 @@ public function getNodeByPath($absPath, $class = 'Node', $object = null)

// do this even if we have item in cache, will throw error if path is deleted - sanity check
$fetchPath = $this->getFetchPath($absPath, $class);

if (!$object) {
// this is the first request, get data from transport
$object = $this->transport->getNode($fetchPath);
Expand Down Expand Up @@ -940,6 +946,10 @@ protected function executeBatch($type, $operations)
case Operation::REMOVE_PROPERTY:
$this->transport->deleteProperties($operations);
break;
case Operation::SET_POLICY:
$this->transport->setPolicy($operations);
break;
//TODO: operation for acl changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik there is no different operation, the acl is always entirely overwritten. right @Alfusainey ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but there is one for REMOVE_POLICY

default:
throw new \Exception('internal error: unknown operation "' . $type . '"');
}
Expand Down Expand Up @@ -1805,4 +1815,20 @@ public function purgeDisappearedNode($absPath, $keepChanges)
// objectsByPath and the calling parent node can forget it
return true;
}

public function getPolicies($path)
{
try {
return array($this->factory->get('Security\AccessControlList', array($this->session->getAccessControlManager(), $this->getNodeByPath($path . '/rep:policy', 'Security\AccessControlList'))));
} catch (ItemNotFoundException $e) {
return array();
}
}

public function setPolicy($absPath, AccessControlPolicyInterface $policy)
{
$operation = new SetPolicyOperation($absPath, $policy);

$this->operationsLog[] = $operation;
}
}
51 changes: 51 additions & 0 deletions src/Jackalope/Security/AccessControlEntry.php
@@ -0,0 +1,51 @@
<?php

namespace Jackalope\Security;

use PHPCR\Security\AccessControlEntryInterface;
use PHPCR\Security\PrincipalInterface;
use PHPCR\Security\PrivilegeInterface;

/**
* {@inheritDoc}
*/
class AccessControlEntry implements \IteratorAggregate, AccessControlEntryInterface
{
/**
* @var PrincipalInterface
*/
private $principal;

/**
* @var PrivilegeInterface[]
*/
private $privileges;

public function __construct(PrincipalInterface $principal, array $privileges)
{
$this->principal = $principal;
// TODO: validate privileges
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not needed, since the privilege instances will already be "validated"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what i meant is validate that its actually PrivilegeInterface instances, in case somebody tries to set them with strings or something like that.

$this->privileges = $privileges;
}

public function getIterator()
{
return new \ArrayIterator($this->privileges);
}

/**
* {@inheritDoc}
*/
public function getPrincipal()
{
return $this->principal;
}

/**
* {@inheritDoc}
*/
public function getPrivileges()
{
return $this->privileges;
}
}
76 changes: 76 additions & 0 deletions src/Jackalope/Security/AccessControlList.php
@@ -0,0 +1,76 @@
<?php

namespace Jackalope\Security;

use Jackalope\FactoryInterface;
use PHPCR\NodeInterface;
use PHPCR\Security\AccessControlEntryInterface;
use PHPCR\Security\AccessControlException;
use PHPCR\Security\AccessControlListInterface;
use PHPCR\Security\AccessControlManagerInterface;
use PHPCR\Security\PrincipalInterface;

/**
* {@inheritDoc}
*/
class AccessControlList extends AccessControlPolicy implements \IteratorAggregate, AccessControlListInterface
{
private $aceList = array();

/**
* @param FactoryInterface $factory
* @param NodeInterface $node
*/
public function __construct(FactoryInterface $factory, AccessControlManagerInterface $acm, NodeInterface $node = null)
{
$this->factory = $factory;
$this->acm = $acm;
$this->node = $node;
}

public function getIterator()
{
return new \ArrayIterator($this->getAccessControlEntries());
}
/**
* {@inheritDoc}
*/
public function getAccessControlEntries()
{
if (!$this->aceList && $this->node) {
foreach ($this->node->getNodes(null, 'rep:ACE') as $aceNode) {
$privileges = array();
foreach ($aceNode->getPropertyValue('privileges') as $priv) {
$privileges[] = $this->acm->privilegeFromName($priv);
}
$this->aceList[] = new AccessControlEntry(new Principal($aceNode->getProperty('principal')), $privileges);
}
}

return $this->aceList;
}

/**
* {@inheritDoc}
*/
public function addAccessControlEntry(PrincipalInterface $principal, array $privileges)
{
// $this->add
// TODO factory?
$this->aceList[] = new AccessControlEntry($principal, $privileges);
}

/**
* {@inheritDoc}
*/
public function removeAccessControlEntry(AccessControlEntryInterface $ace)
{
if ($key = array_search($ace, $this->aceList, true)) {
unset($this->aceList[$key]);

return;
}

throw new AccessControlException('Entry not found');
}
}
140 changes: 140 additions & 0 deletions src/Jackalope/Security/AccessControlManager.php
@@ -0,0 +1,140 @@
<?php

namespace Jackalope\Security;
use Jackalope\FactoryInterface;
use Jackalope\ObjectManager;
use Jackalope\Transport\AccessControlInterface;
use PHPCR\RepositoryException;
use PHPCR\Security\AccessControlException;
use PHPCR\Security\AccessControlManagerInterface;
use PHPCR\Security\AccessControlPolicyInterface;
use PHPCR\Security\PrivilegeInterface;

/**
* {@inheritDoc}
*/
class AccessControlManager implements AccessControlManagerInterface
{
/**
* @var FactoryInterface
*/
private $factory;

/**
* @var ObjectManager
*/
private $om;

/**
* @var AccessControlInterface
*/
private $transport;

/**
* @var PrivilegeInterface[]
*/
private $supportedPrivilegesByPath = array();

public function __construct(FactoryInterface $factory, ObjectManager $om, AccessControlInterface $transport)
{
$this->factory = $factory;
$this->om = $om;
$this->transport = $transport;
}

/**
* {@inheritDoc}
*/
public function getSupportedPrivileges($absPath = null)
{
if (!isset($this->supportedPrivilegesByPath[$absPath])) {
$this->supportedPrivilegesByPath[$absPath] = $this->transport->getSupportedPrivileges($absPath);
}

return $this->supportedPrivilegesByPath[$absPath];
}

/**
* {@inheritDoc}
*/
public function privilegeFromName($privilegeName)
{
foreach ($this->getSupportedPrivileges() as $privilege) {
if ($privilegeName === $privilege->getName()) {
return $privilege;
}

foreach ($privilege->getAggregatePrivileges() as $childPrivilege) {
if ($privilegeName === $childPrivilege->getName()) {
return $childPrivilege;
}
}
}

throw new AccessControlException($privilegeName);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve exception message

}

/**
* {@inheritDoc}
*/
public function hasPrivileges($absPath, array $privileges)
{
// TODO
}

/**
* {@inheritDoc}
*/
public function getPrivileges($absPath = null)
{
// TODO
}

/**
* {@inheritDoc}
*/
public function getPolicies($absPath)
{
return $this->om->getPolicies($absPath);
}

/**
* {@inheritDoc}
*/
public function getEffectivePolicies($absPath)
{
throw new RepositoryException('This method can not properly be implemented. Use getPolicies for this path');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently not implemented for JCR remoting in Jackrabbit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, and angela thinks it makes semantically no sense, as you might not have the right to read all policies that might affect you, and there is no defined behaviour for that case. the things that are releavant are:

  • what policies are applicable at a specific path?
  • do i have the permission to do something? this is SessionInterface::hasPermission

}

/**
* {@inheritDoc}
*/
public function getApplicablePolicies($absPath)
{
if (count($this->getPolicies($absPath))) {
return array();
}

return array(new AccessControlList($this->factory, $this));
}

/**
* {@inheritDoc}
*/
public function setPolicy($absPath, AccessControlPolicyInterface $policy)
{
if (!$policy instanceof AccessControlList) {
throw new \InvalidArgumentException('Invalid policy given');
}

$this->om->setPolicy($absPath, $policy);
}

/**
* {@inheritDoc}
*/
public function removePolicy($absPath, AccessControlPolicyInterface $policy)
{
// TODO: track this so its saved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need an operation for this

}
}
12 changes: 12 additions & 0 deletions src/Jackalope/Security/AccessControlPolicy.php
@@ -0,0 +1,12 @@
<?php

namespace Jackalope\Security;

use PHPCR\Security\AccessControlPolicyInterface;

/**
* {@inheritDoc}
*/
class AccessControlPolicy implements AccessControlPolicyInterface
{
}
26 changes: 26 additions & 0 deletions src/Jackalope/Security/NamedAccessControlPolicy.php
@@ -0,0 +1,26 @@
<?php

namespace Jackalope\Security;

use PHPCR\Security\NamedAccessControlPolicyInterface;

/**
* {@inheritDoc}
*/
class NamedAccessControlPolicy extends AccessControlPolicy implements NamedAccessControlPolicyInterface
{
private $name;

public function __construct($name)
{
$this->name = $name;
}

/**
* {@inheritDoc}
*/
public function getName()
{
return $this->name;
}
}
28 changes: 28 additions & 0 deletions src/Jackalope/Security/Principal.php
@@ -0,0 +1,28 @@
<?php

namespace Jackalope\Security;
use PHPCR\Security\PrincipalInterface;

/**
* {@inheritDoc}
*/
class Principal implements PrincipalInterface
{
/**
* @var string
*/
private $name;

public function __construct($name)
{
$this->name = $name;
}

/**
* {@inheritDoc}
*/
public function getName()
{
return $this->name;
}
}