1: <?php
2:
3: /*
4: * This file is part of the Symfony package.
5: *
6: * (c) Fabien Potencier <fabien@symfony.com>
7: *
8: * For the full copyright and license information, please view the LICENSE
9: * file that was distributed with this source code.
10: */
11:
12: namespace Symfony\Component\Security\Acl\Permission;
13:
14: /**
15: * This class allows you to build cumulative permissions easily, or convert
16: * masks to a human-readable format.
17: *
18: * <code>
19: * $builder = new MaskBuilder();
20: * $builder
21: * ->add('view')
22: * ->add('create')
23: * ->add('edit')
24: * ;
25: * var_dump($builder->get()); // int(7)
26: * var_dump($builder->getPattern()); // string(32) ".............................ECV"
27: * </code>
28: *
29: * We have defined some commonly used base permissions which you can use:
30: * - VIEW: the SID is allowed to view the domain object / field
31: * - CREATE: the SID is allowed to create new instances of the domain object / fields
32: * - EDIT: the SID is allowed to edit existing instances of the domain object / field
33: * - DELETE: the SID is allowed to delete domain objects
34: * - UNDELETE: the SID is allowed to recover domain objects from trash
35: * - OPERATOR: the SID is allowed to perform any action on the domain object
36: * except for granting others permissions
37: * - MASTER: the SID is allowed to perform any action on the domain object,
38: * and is allowed to grant other SIDs any permission except for
39: * MASTER and OWNER permissions
40: * - OWNER: the SID is owning the domain object in question and can perform any
41: * action on the domain object as well as grant any permission
42: *
43: * @author Johannes M. Schmitt <schmittjoh@gmail.com>
44: */
45: class MaskBuilder
46: {
47: const MASK_VIEW = 1; // 1 << 0
48: const MASK_CREATE = 2; // 1 << 1
49: const MASK_EDIT = 4; // 1 << 2
50: const MASK_DELETE = 8; // 1 << 3
51: const MASK_UNDELETE = 16; // 1 << 4
52: const MASK_OPERATOR = 32; // 1 << 5
53: const MASK_MASTER = 64; // 1 << 6
54: const MASK_OWNER = 128; // 1 << 7
55: const MASK_IDDQD = 1073741823; // 1 << 0 | 1 << 1 | ... | 1 << 30
56:
57: const CODE_VIEW = 'V';
58: const CODE_CREATE = 'C';
59: const CODE_EDIT = 'E';
60: const CODE_DELETE = 'D';
61: const CODE_UNDELETE = 'U';
62: const CODE_OPERATOR = 'O';
63: const CODE_MASTER = 'M';
64: const CODE_OWNER = 'N';
65:
66: const ALL_OFF = '................................';
67: const OFF = '.';
68: const ON = '*';
69:
70: private $mask;
71:
72: /**
73: * Constructor
74: *
75: * @param int $mask optional; defaults to 0
76: *
77: * @throws \InvalidArgumentException
78: */
79: public function __construct($mask = 0)
80: {
81: if (!is_int($mask)) {
82: throw new \InvalidArgumentException('$mask must be an integer.');
83: }
84:
85: $this->mask = $mask;
86: }
87:
88: /**
89: * Adds a mask to the permission
90: *
91: * @param mixed $mask
92: *
93: * @return MaskBuilder
94: *
95: * @throws \InvalidArgumentException
96: */
97: public function add($mask)
98: {
99: if (is_string($mask) && defined($name = 'static::MASK_'.strtoupper($mask))) {
100: $mask = constant($name);
101: } elseif (!is_int($mask)) {
102: throw new \InvalidArgumentException('$mask must be an integer.');
103: }
104:
105: $this->mask |= $mask;
106:
107: return $this;
108: }
109:
110: /**
111: * Returns the mask of this permission
112: *
113: * @return int
114: */
115: public function get()
116: {
117: return $this->mask;
118: }
119:
120: /**
121: * Returns a human-readable representation of the permission
122: *
123: * @return string
124: */
125: public function getPattern()
126: {
127: $pattern = self::ALL_OFF;
128: $length = strlen($pattern);
129: $bitmask = str_pad(decbin($this->mask), $length, '0', STR_PAD_LEFT);
130:
131: for ($i=$length-1; $i>=0; $i--) {
132: if ('1' === $bitmask[$i]) {
133: try {
134: $pattern[$i] = self::getCode(1 << ($length - $i - 1));
135: } catch (\Exception $notPredefined) {
136: $pattern[$i] = self::ON;
137: }
138: }
139: }
140:
141: return $pattern;
142: }
143:
144: /**
145: * Removes a mask from the permission
146: *
147: * @param mixed $mask
148: *
149: * @return MaskBuilder
150: *
151: * @throws \InvalidArgumentException
152: */
153: public function remove($mask)
154: {
155: if (is_string($mask) && defined($name = 'static::MASK_'.strtoupper($mask))) {
156: $mask = constant($name);
157: } elseif (!is_int($mask)) {
158: throw new \InvalidArgumentException('$mask must be an integer.');
159: }
160:
161: $this->mask &= ~$mask;
162:
163: return $this;
164: }
165:
166: /**
167: * Resets the PermissionBuilder
168: *
169: * @return MaskBuilder
170: */
171: public function reset()
172: {
173: $this->mask = 0;
174:
175: return $this;
176: }
177:
178: /**
179: * Returns the code for the passed mask
180: *
181: * @param int $mask
182: * @throws \InvalidArgumentException
183: * @throws \RuntimeException
184: * @return string
185: */
186: public static function getCode($mask)
187: {
188: if (!is_int($mask)) {
189: throw new \InvalidArgumentException('$mask must be an integer.');
190: }
191:
192: $reflection = new \ReflectionClass(get_called_class());
193: foreach ($reflection->getConstants() as $name => $cMask) {
194: if (0 !== strpos($name, 'MASK_')) {
195: continue;
196: }
197:
198: if ($mask === $cMask) {
199: if (!defined($cName = 'static::CODE_'.substr($name, 5))) {
200: throw new \RuntimeException('There was no code defined for this mask.');
201: }
202:
203: return constant($cName);
204: }
205: }
206:
207: throw new \InvalidArgumentException(sprintf('The mask "%d" is not supported.', $mask));
208: }
209: }
210: