Initial code for nested groups

This commit is contained in:
2025-03-16 21:36:36 +01:00
parent 16a647db82
commit 17a6588e95
6 changed files with 73 additions and 4 deletions

View File

@@ -63,14 +63,43 @@ class Daemon
$accessChecker = new AccessChecker();
$slotRepo = $em->getRepository(Slot::class);
$groupRepo = $em->getRepository(Group::class);
$routes = new RouteCollection();
$routes->addController(new Http\Controller\SlotsController($em->getRepository(Slot::class), $this->messageBus, $accessChecker));
$routes->addController(new Http\Controller\GroupsController($em->getRepository(Group::class), $em->getRepository(Slot::class)));
$routes->addController(new Http\Controller\SlotsController($slotRepo, $this->messageBus, $accessChecker));
$routes->addController(new Http\Controller\GroupsController($groupRepo, $slotRepo));
$routes->addController(new Http\Controller\SchemassController($em->getRepository(Schema::class)));
$this->server = new HttpServer(
new Http\Middleware\LoggingMiddleware($httpLogger ?? $logger),
new SecurityMiddleware(...$securityOpts),
function (ServerRequestInterface $request, ?callable $next) use ($slotRepo, $groupRepo) {
$paths = explode("/", ltrim($request->getUri()->getPath(), "/"));
$root = reset($paths);
if ($root === 'api') {
return $next($request);
}
$focus = null;
while ($key = array_shift($paths)) {
if (count($paths) > 0) {
// match group name
if (!$focus) {
$focus = $groupRepo->findOneBy([ 'groupId' => $key ]);
} else {
$groups = $focus->getGroups();
foreach ($groups as $g) {
if ($g->getGroupId() == $key) {
$focus = $g;
break;
}
}
}
} else {
// match slot or group name
}
}
},
new RoutingMiddleware($routes),
new RequestHandler(),
function (ServerRequestInterface $request) {

View File

@@ -21,6 +21,13 @@ class Group implements IteratorAggregate, JsonSerializable
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'groups')]
private ?Group $parent = null;
#[ORM\OneToMany(targetEntity: Group::class, mappedBy: 'parent')]
#[ORM\OrderBy([ 'groupId' => 'asc' ])]
private Collection $groups;
#[ORM\Column(type: 'string', length: 96, unique: true)]
private string $groupId;
@@ -39,6 +46,7 @@ class Group implements IteratorAggregate, JsonSerializable
string $name,
)
{
$this->groups = new ArrayCollection();
$this->groupId = $groupId;
$this->name = $name;
$this->slots = new ArrayCollection();
@@ -55,6 +63,11 @@ class Group implements IteratorAggregate, JsonSerializable
return $this->groupId;
}
public function getParent(): ?Group
{
return $this->parent;
}
public function getIterator(): Traversable
{
return new ArrayIterator($this->props);
@@ -81,6 +94,8 @@ class Group implements IteratorAggregate, JsonSerializable
return [
'_id' => $this->id,
'_group' => $this->groupId,
'_parent' => $this->parent ? $this->parent->getGroupId() : null,
'_groups' => array_map(fn($g) => $g->getGroupId(), iterator_to_array($this->groups)),
'_name' => $this->name,
...$this->props,
];

View File

@@ -56,6 +56,7 @@ class GroupsController extends Controller
return Response::json([
'_group' => $groupObj->getGroupId(),
'_parent' => $groupObj->getParent()?->getGroupId(),
'_name' => $groupObj->getName(),
'_members' => array_map(
fn(Slot $slot) => $slot->getSlotId(),

View File

@@ -62,6 +62,7 @@ class SlotsController extends Controller
#[Route(path:"/api/slotdb/v1/slots", methods:["POST"])]
public function createSlots(ServerRequestInterface $request)
{
$acl = $request->getAttribute('acl');
$data = json_decode($request->getBody());
$slots = [];
@@ -71,6 +72,9 @@ class SlotsController extends Controller
$props = $item->props ?? [];
if (!$this->slots->hasSlot($slotId)) {
$slot = new Slot($slotId, $slotName);
// check access (for 'c'?)
if (!$this->accessChecker->checkSlotAccess($slot, $acl, 'c')) continue;
// TODO filter properties according to ACL
$slot->setProperties($props);
$slots[] = $slot;
}

View File

@@ -56,6 +56,26 @@ class AccessChecker
return false;
}
public function filterReadableProperties(Slot $slot, array $acl, array $props): array
{
$filtered = [];
foreach ($props as $prop=>$value) {
if ($this->checkPropertyAccess($slot, $prop, $acl, 'r'))
$filtered[$prop] = $value;
}
return $filtered;
}
public function filterWritableProperties(Slot $slot, array $acl, array $props): array
{
$filtered = [];
foreach ($props as $prop=>$value) {
if ($this->checkPropertyAccess($slot, $prop, $acl, 'w'))
$filtered[$prop] = $value;
}
return $filtered;
}
private function getCompiledMatcher(string $ace): AceMatcher
{
return $this->aclCache->get($ace, function (CacheItemInterface $item) use ($ace) {

View File

@@ -34,10 +34,10 @@ class AceMatcher
$this->prop = null;
}
}
public function matches(string $slot, string $group, ?string $prop, string $access): bool
public function matches(string $slot, ?string $group, ?string $prop, string $access): bool
{
// printf("match: slot=%s|%s group=%s|%s prop=%s|%s access=%s|%s\n", $slot, $this->slot, $group, $this->group, $prop, $this->prop, $access, $this->perms);
if ($this->group && ($this->group != $group)) return false;
if ($group && $this->group && ($this->group != $group)) return false;
if ($this->slot && (!fnmatch($this->slot, $slot))) return false;
if ($prop && $this->prop && (!fnmatch($this->prop, $prop))) return false;
for ($n = 0; $n < strlen($access); $n++) {