diff --git a/composer.json b/composer.json index 24cec2fb..8daaa988 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "symfony/debug-pack": "*", "symfony/maker-bundle": "^1.0", "symfony/profiler-pack": "*", - "symfony/test-pack": "*", + "symfony/test-pack": "^1.0", "symfony/web-server-bundle": "4.2.*" }, "config": { diff --git a/composer.lock b/composer.lock index e18d4593..d8abfd51 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "d55a659c246edf8300e41e8cd75301e5", + "content-hash": "2bd4b2e7f78e796f188a48280531c444", "packages": [ { "name": "doctrine/annotations", diff --git a/config/permissions.yaml b/config/permissions.yaml index 5131126b..9ded2599 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -195,7 +195,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co delete_logs: bit: 4 - device_parts: + devices_parts: operations: read: bit: 0 @@ -229,4 +229,3 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co bit: 6 - diff --git a/src/Entity/PermissionsEmbed.php b/src/Entity/PermissionsEmbed.php index 39e15ef4..a0d6ef62 100644 --- a/src/Entity/PermissionsEmbed.php +++ b/src/Entity/PermissionsEmbed.php @@ -72,8 +72,8 @@ class PermissionsEmbed public const PARTS_ORDER = 'parts_order'; public const GROUPS = 'groups'; public const USERS = 'users'; - public const DATABASE = 'system_database'; - public const CONFIG = 'system_config'; + public const DATABASE = 'database'; + public const CONFIG = 'config'; public const SYSTEM = 'system'; public const DEVICE_PARTS = 'devices_parts'; public const SELF = 'self'; @@ -83,175 +83,185 @@ class PermissionsEmbed * @var int * @ORM\Column(type="integer") */ - protected $system; + protected $system = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $groups; + protected $groups = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $users; + protected $users = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $self; + protected $self = 0; /** * @var int - * @ORM\Column(type="integer") + * @ORM\Column(type="integer", name="system_config") */ - protected $system_config; + protected $config = 0; /** * @var int - * @ORM\Column(type="integer") + * @ORM\Column(type="integer", name="system_database") */ - protected $system_database; + protected $database = 0; /** * @var int * @ORM\Column(type="bigint") */ - protected $parts; + protected $parts = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_name; + protected $parts_name = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_description; + protected $parts_description = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_instock; + protected $parts_instock = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_mininstock; + protected $parts_mininstock = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_footprint; + protected $parts_footprint = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_storelocation; + protected $parts_storelocation = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_manufacturer; + protected $parts_manufacturer = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_comment; + protected $parts_comment = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_order; + protected $parts_order = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_orderdetails; + protected $parts_orderdetails = 0; /** * @var int * @ORM\Column(type="smallint") */ - protected $parts_prices; + protected $parts_prices = 0; /** * @var int * @ORM\Column(type="smallint", name="parts_attachements") */ - protected $parts_attachments; + protected $parts_attachments = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $devices; + protected $devices = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $devices_parts; + protected $devices_parts = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $storelocations; + protected $storelocations = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $footprints; + protected $footprints = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $categories; + protected $categories = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $suppliers; + protected $suppliers = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $manufacturers; + protected $manufacturers = 0; /** * @var int * @ORM\Column(type="integer", name="attachement_types") */ - protected $attachment_types; + protected $attachment_types = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $tools; + protected $tools = 0; /** * @var int * @ORM\Column(type="integer") */ - protected $labels; + protected $labels = 0; + + /** + * Checks whether a permission with the given name is valid for this object. + * @param string $permission_name The name of the permission which should be checked for. + * @return bool True if the permission is existing on this object. + */ + public function isValidPermissionName(string $permission_name) : bool + { + return isset($this->$permission_name); + } /** * Returns the bit pair value of the given permission. @@ -263,6 +273,10 @@ class PermissionsEmbed */ public function getBitValue(string $permission_name, int $bit_n): int { + if(!$this->isValidPermissionName($permission_name)) { + throw new \InvalidArgumentException('No permission with the given name is existing!'); + } + $perm_int = $this->$permission_name; return static::readBitPair($perm_int, $bit_n); @@ -280,17 +294,60 @@ class PermissionsEmbed public function getPermissionValue(string $permission_name, int $bit_n): ?bool { $value = $this->getBitValue($permission_name, $bit_n); - if (self::ALLOW == $value) { + if (self::ALLOW === $value) { return true; } - if (self::DISALLOW == $value) { + if (self::DISALLOW === $value) { return false; } return null; } + /** + * Sets the value of the given permission and operation. + * @param string $permission_name The name of the permission, for which the bit pair should be written. + * @param int $bit_n The (lower) bit number of the bit pair, which should be written. + * @param bool|null $new_value The new value for the operation: + * True, if the given operation is allowed, false if disallowed + * and null if it should inherit from parent. + * @return PermissionsEmbed The instance itself. + */ + public function setPermissionValue(string $permission_name, int $bit_n, ?bool $new_value) : self + { + //Determine which bit value the given value is. + if($new_value === true) { + $bit_value = static::ALLOW; + } elseif($new_value === false) { + $bit_value = static::DISALLOW; + } else { + $bit_value = static::INHERIT; + } + + $this->setBitValue($permission_name, $bit_n, $bit_value); + + return $this; + } + + /** + * Sets the bit value of the given permission and operation. + * @param string $permission_name The name of the permission, for which the bit pair should be written. + * @param int $bit_n The (lower) bit number of the bit pair, which should be written. + * @param int $new_value The new (bit) value of the bit pair, which should be written. + * @return PermissionsEmbed The instance itself. + */ + public function setBitValue(string $permission_name, int $bit_n, int $new_value) : self + { + if(!$this->isValidPermissionName($permission_name)) { + throw new \InvalidArgumentException('No permission with the given name is existing!'); + } + + $this->$permission_name = static::writeBitPair($this->$permission_name, $bit_n, $new_value); + + return $this; + } + /** * Reads a bit pair from $data. * @@ -323,6 +380,7 @@ class PermissionsEmbed { Assert::lessThanEq($n, 31, '$n must be smaller than 32, because only a 32bit int is used! Got %s.'); Assert::lessThanEq($new, 3, '$new must be smaller than 3, because a bit pair is written! Got %s.'); + Assert::greaterThanEq($new, 0, '$new must not be negative, because a bit pair is written! Got %s.'); if (0 !== $n % 2) { throw new \InvalidArgumentException('$n must be dividable by 2, because we address bit pairs here!'); diff --git a/src/Security/Annotations/ColumnSecurity.php b/src/Security/Annotations/ColumnSecurity.php index 5bf09ad4..c56f1518 100644 --- a/src/Security/Annotations/ColumnSecurity.php +++ b/src/Security/Annotations/ColumnSecurity.php @@ -30,6 +30,7 @@ namespace App\Security\Annotations; use Doctrine\Common\Annotations\Annotation; +use \InvalidArgumentException; /** * @Annotation @@ -100,8 +101,9 @@ class ColumnSecurity return false; case 'datetime': $date = new \DateTime(); - return $date->setTimestamp(0); + default: + throw new InvalidArgumentException('Unknown type! You have to specify a placeholder!'); } } diff --git a/src/Services/PermissionResolver.php b/src/Services/PermissionResolver.php index 4e75e083..a7cac28d 100644 --- a/src/Services/PermissionResolver.php +++ b/src/Services/PermissionResolver.php @@ -179,8 +179,12 @@ class PermissionResolver */ public function listOperationsForPermission(string $permission): array { + if(!$this->isValidPermission($permission)) { + throw new \InvalidArgumentException(sprintf('A permission with that name is not existing! Got %s.', $permission)); + } $operations = $this->permission_structure['perms'][$permission]['operations']; + return array_keys($operations); } diff --git a/tests/Entity/PermissionsEmbedTest.php b/tests/Entity/PermissionsEmbedTest.php new file mode 100644 index 00000000..1690e5a3 --- /dev/null +++ b/tests/Entity/PermissionsEmbedTest.php @@ -0,0 +1,188 @@ + null + //Test both normal name and constants + + $this->assertNull( $embed->getPermissionValue(PermissionsEmbed::PARTS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::CONFIG, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::ATTACHMENT_TYPES, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::CATEGORIES, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::DATABASE, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::DEVICE_PARTS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::DEVICES, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::FOOTRPINTS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::GROUPS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::DATABASE, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::LABELS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::MANUFACTURERS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_ATTACHMENTS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_COMMENT, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_DESCRIPTION, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_FOOTPRINT, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_INSTOCK, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_MANUFACTURER, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_MININSTOCK, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_NAME, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_ORDER, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::PARTS_ORDERDETAILS, 0)); + $this->assertEquals(null, $embed->getPermissionValue(PermissionsEmbed::USERS, 0)); + + + //Set a value for testing to the part property + $reflection = new \ReflectionClass($embed); + $property = $reflection->getProperty('parts'); + $property->setAccessible(true); + + $property->setValue($embed, 0b11011000); // 11 01 10 00 + + //Test if function is working correctly + $this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS, 0)); + $this->assertFalse($embed->getPermissionValue(PermissionsEmbed::PARTS, 2)); + $this->assertTrue($embed->getPermissionValue(PermissionsEmbed::PARTS, 4)); + // 11 is reserved, but it should be also treat as INHERIT. + $this->assertNull($embed->getPermissionValue(PermissionsEmbed::PARTS, 6)); + + + } + + public function testGetBitValue() + { + + $embed = new PermissionsEmbed(); + + //Set a value for testing to the part property + $reflection = new \ReflectionClass($embed); + $property = $reflection->getProperty('parts'); + $property->setAccessible(true); + + $property->setValue($embed, 0b11011000); // 11 01 10 00 + + //Test if function is working correctly + $this->assertEquals(PermissionsEmbed::INHERIT ,$embed->getBitValue(PermissionsEmbed::PARTS, 0)); + $this->assertEquals(PermissionsEmbed::DISALLOW ,$embed->getBitValue(PermissionsEmbed::PARTS, 2)); + $this->assertEquals(PermissionsEmbed::ALLOW ,$embed->getBitValue(PermissionsEmbed::PARTS, 4)); + // 11 is reserved, but it should be also treat as INHERIT. + $this->assertEquals(0b11 ,$embed->getBitValue(PermissionsEmbed::PARTS, 6)); + } + + public function testInvalidPermissionName() + { + $embed = new PermissionsEmbed(); + //When encoutering an unknown permission name the class must throw an exception + $this->expectException(\InvalidArgumentException::class); + $embed->getPermissionValue('invalid', 0); + } + + public function testInvalidBit1() + { + $embed = new PermissionsEmbed(); + //When encoutering an negative bit the class must throw an exception + $this->expectException(\InvalidArgumentException::class); + $embed->getPermissionValue('parts', -1); + } + + public function testInvalidBit2() + { + $embed = new PermissionsEmbed(); + //When encoutering an odd bit number it must throw an error. + $this->expectException(\InvalidArgumentException::class); + $embed->getPermissionValue('parts', 1); + } + + public function testInvalidBit3() + { + $embed = new PermissionsEmbed(); + //When encoutering an too high bit number it must throw an error. + $this->expectException(\InvalidArgumentException::class); + $embed->getPermissionValue('parts', 32); + } + + public function getStatesBINARY() + { + return [ + 'ALLOW' => [PermissionsEmbed::ALLOW], + 'DISALLOW' => [PermissionsEmbed::DISALLOW], + 'INHERIT' => [PermissionsEmbed::INHERIT], + '0b11' => [0b11] + ]; + } + + public function getStatesBOOL() + { + return [ + 'ALLOW' => [true], + 'DISALLOW' => [false], + 'INHERIT' => [null], + '0b11' => [null] + ]; + } + + /** + * @dataProvider getStatesBINARY + */ + public function testsetBitValue($value) + { + $embed = new PermissionsEmbed(); + //Check if it returns itself, for chaining. + $this->assertEquals($embed, $embed->setBitValue(PermissionsEmbed::PARTS, 0, $value)); + $this->assertEquals($value, $embed->getBitValue(PermissionsEmbed::PARTS, 0)); + } + + /** + * @dataProvider getStatesBOOL + */ + public function testSetPermissionValue($value) + { + $embed = new PermissionsEmbed(); + //Check if it returns itself, for chaining. + $this->assertEquals($embed, $embed->setPermissionValue(PermissionsEmbed::PARTS, 0, $value)); + $this->assertEquals($value, $embed->getPermissionValue(PermissionsEmbed::PARTS, 0)); + + } + +} diff --git a/tests/Helpers/TreeViewNodeTest.php b/tests/Helpers/TreeViewNodeTest.php new file mode 100644 index 00000000..1fe6fdae --- /dev/null +++ b/tests/Helpers/TreeViewNodeTest.php @@ -0,0 +1,80 @@ +node1 = new TreeViewNode('Name'); + //Node 2 gets values for all arguments + $this->node2 = new TreeViewNode('Name', 'www.foo.bar', $sub_nodes); + + } + + public function testConstructor() + { + //A node without things should have null values on its properties: + $this->assertNull($this->node1->getHref()); + $this->assertNull($this->node1->getNodes()); + $this->assertEquals('Name', $this->node1->getText()); + + //The second node must have the given things as properties. + $this->assertEquals('Name',$this->node2->getText()); + $this->assertEquals('www.foo.bar', $this->node2->getHref()); + $this->assertNotEmpty($this->node2->getNodes()); + } + + +} \ No newline at end of file diff --git a/tests/Services/PermissionResolverTest.php b/tests/Services/PermissionResolverTest.php new file mode 100644 index 00000000..0c0a9d75 --- /dev/null +++ b/tests/Services/PermissionResolverTest.php @@ -0,0 +1,191 @@ +service = self::$container->get(PermissionResolver::class); + + //Set up a mocked user + $user_embed = new PermissionsEmbed(); + $user_embed->setPermissionValue('parts', 0, true) //read + ->setPermissionValue('parts', 2, false) //edit + ->setPermissionValue('parts', 4, null) //create + ->setPermissionValue('parts', 6, null) //move + ->setPermissionValue('parts', 8, null); //delete + + $this->user = $this->createMock(User::class); + $this->user->method('getPermissions')->willReturn($user_embed); + + //Set up a faked group + $group1_embed = new PermissionsEmbed(); + $group1_embed->setPermissionValue('parts', 6, true) + ->setPermissionValue('parts', 8, false) + ->setPermissionValue('parts', 10, null) + ->setPermissionValue('parts', 0, false) + ->setPermissionValue('parts', 2, true); + + $this->group = $this->createMock(Group::class); + $this->group->method('getPermissions')->willReturn($group1_embed); + + //Set this group for the user + $this->user->method('getGroup')->willReturn($this->group); + + //parent group + $parent_group_embed = new PermissionsEmbed(); + $parent_group_embed->setPermissionValue('parts', 12, true) + ->setPermissionValue('parts', 14, false) + ->setPermissionValue('parts', 16, null); + $parent_group = $this->createMock(Group::class); + $parent_group->method('getPermissions')->willReturn($parent_group_embed); + + $this->group->method('getParent')->willReturn($parent_group); + + } + + + public function getPermissionNames() + { + //List all possible operation names. + return [ + [PermissionsEmbed::PARTS], + [PermissionsEmbed::USERS], + [PermissionsEmbed::PARTS_ORDERDETAILS], + [PermissionsEmbed::PARTS_NAME], + [PermissionsEmbed::PARTS_ORDER], + [PermissionsEmbed::PARTS_MININSTOCK], + [PermissionsEmbed::PARTS_MANUFACTURER], + [PermissionsEmbed::PARTS_INSTOCK], + [PermissionsEmbed::DEVICES], + [PermissionsEmbed::PARTS_FOOTPRINT], + [PermissionsEmbed::PARTS_DESCRIPTION], + [PermissionsEmbed::PARTS_COMMENT], + [PermissionsEmbed::PARTS_ATTACHMENTS], + [PermissionsEmbed::MANUFACTURERS], + [PermissionsEmbed::LABELS], + [PermissionsEmbed::DATABASE], + [PermissionsEmbed::GROUPS], + [PermissionsEmbed::FOOTRPINTS], + [PermissionsEmbed::DEVICE_PARTS], + [PermissionsEmbed::CATEGORIES], + [PermissionsEmbed::PARTS_PRICES], + [PermissionsEmbed::ATTACHMENT_TYPES], + [PermissionsEmbed::CONFIG] + ]; + } + + /** + * @dataProvider getPermissionNames + */ + public function testListOperationsForPermission($perm_name) + { + $arr = $this->service->listOperationsForPermission($perm_name); + + //Every entry should not be empty. + $this->assertNotEmpty($arr); + } + + public function testInvalidListOperationsForPermission() + { + $this->expectException(\InvalidArgumentException::class); + //Must throw an exception + $this->service->listOperationsForPermission('invalid'); + } + + public function testisValidPermission() + { + $this->assertTrue($this->service->isValidPermission('parts')); + $this->assertFalse($this->service->isValidPermission('invalid')); + } + + public function testIsValidOperation() + { + $this->assertTrue($this->service->isValidOperation('parts', 'read')); + + //Must return false if either the permission or the operation is not existing + $this->assertFalse($this->service->isValidOperation('parts', 'invalid')); + $this->assertFalse($this->service->isValidOperation('invalid', 'read')); + $this->assertFalse($this->service->isValidOperation('invalid', 'invalid')); + } + + public function testDontInherit() + { + //Check with faked object + $this->assertTrue($this->service->dontInherit($this->user, 'parts', 'read')); + $this->assertFalse($this->service->dontInherit($this->user, 'parts', 'edit')); + $this->assertNull($this->service->dontInherit($this->user, 'parts', 'create')); + $this->assertNull($this->service->dontInherit($this->user, 'parts', 'move')); + $this->assertNull($this->service->dontInherit($this->user, 'parts', 'delete')); + } + + public function testInherit() + { + //Not inherited values should be same as dont inherit: + $this->assertTrue($this->service->Inherit($this->user, 'parts', 'read')); + $this->assertFalse($this->service->Inherit($this->user, 'parts', 'edit')); + //When thing can not be resolved null should be returned + $this->assertNull($this->service->Inherit($this->user, 'parts', 'create')); + + //Check for inherit from group + $this->assertTrue($this->service->inherit($this->user, 'parts', 'move')); + $this->assertFalse($this->service->inherit($this->user, 'parts', 'delete')); + $this->assertNull($this->service->inherit($this->user, 'parts', 'search')); + + //Check for inherit from group and parent group + $this->assertTrue($this->service->inherit($this->user, 'parts', 'all_parts')); + $this->assertFalse($this->service->inherit($this->user, 'parts', 'no_price_parts')); + $this->assertNull($this->service->inherit($this->user, 'parts', 'obsolete_parts')); + } + +} \ No newline at end of file