diff --git a/src/Daemon.php b/src/Daemon.php index 3117289..db14ef4 100644 --- a/src/Daemon.php +++ b/src/Daemon.php @@ -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) { diff --git a/src/Data/Group/Group.php b/src/Data/Group/Group.php index 454bbe0..9f1a545 100644 --- a/src/Data/Group/Group.php +++ b/src/Data/Group/Group.php @@ -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, ]; diff --git a/src/Http/Controller/GroupsController.php b/src/Http/Controller/GroupsController.php index 8f0c31e..07a3f46 100644 --- a/src/Http/Controller/GroupsController.php +++ b/src/Http/Controller/GroupsController.php @@ -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(), diff --git a/src/Http/Controller/SlotsController.php b/src/Http/Controller/SlotsController.php index a92f157..10eb0b3 100644 --- a/src/Http/Controller/SlotsController.php +++ b/src/Http/Controller/SlotsController.php @@ -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; } diff --git a/src/Security/AccessChecker.php b/src/Security/AccessChecker.php index 6f0f6eb..440abbd 100644 --- a/src/Security/AccessChecker.php +++ b/src/Security/AccessChecker.php @@ -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) { diff --git a/src/Security/AceMatcher.php b/src/Security/AceMatcher.php index 137ea2c..fdf4842 100644 --- a/src/Security/AceMatcher.php +++ b/src/Security/AceMatcher.php @@ -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++) {