Passed
Pull Request — master (#4)
by
unknown
12:26
created

Manager::considerPermission()   B

Complexity

Conditions 9
Paths 34

Size

Total Lines 32
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 9
eloc 13
c 2
b 0
f 1
nc 34
nop 2
dl 0
loc 32
rs 8.0555
1
<?php
2
3
namespace Mrluke\Privileges;
4
5
use Illuminate\Database\Eloquent\Model;
6
use InvalidArgumentException;
7
use Mrluke\Configuration\Contracts\ArrayHost as Host;
8
use Mrluke\Configuration\Exceptions\ConfigurationException;
9
use Mrluke\Privileges\Contracts\Authorizable;
10
use Mrluke\Privileges\Contracts\Permitable;
11
use Mrluke\Privileges\Contracts\Role;
12
13
/**
14
 * Manager is a class that provides complex methods
15
 * to assign Authorizable to permissions and role.
16
 *
17
 * @author    Łukasz Sitnicki (mr-luke)
18
 * @link      http://github.com/mr-luke/privileges
19
 *
20
 * @category  Laravel
21
 * @package   mr-luke/privileges
22
 * @license   MIT
23
 * @version   1.0.0
24
 *
25
 * @property  mixed  $allowed_value
26
 * @property  string $authKeyName
27
 * @property  mixed  $denied_value
28
 */
29
class Manager
30
{
31
    /**
32
     * Authorizable primary key name.
33
     *
34
     * @var string
35
     */
36
    protected $authKeyName;
37
38
    /**
39
     * Authorizable primary key type.
40
     *
41
     * @var string
42
     */
43
    protected $authKeyType;
44
45
    /**
46
     * Authorizable table name.
47
     *
48
     * @var string
49
     */
50
    protected $authTable;
51
52
    /**
53
     * Configuration instance.
54
     *
55
     * @var \Mrluke\Configuration\Contracts\ArrayHost
56
     */
57
    protected $config;
58
59
    public function __construct(Host $config)
60
    {
61
        $this->config = $config;
62
    }
63
64
    /**
65
     * Assign Authorizable to given role.
66
     *
67
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
68
     * @param mixed                                     $role
69
     *
70
     * @return void
71
     */
72
    public function assignRole(Authorizable $auth, $role): void
73
    {
74
        if ($role instanceof Role) {
75
            $auth->roles()->syncWithoutDetaching([$role->id]);
76
        } elseif (is_integer($role)) {
77
            $auth->roles()->syncWithoutDetaching([$role]);
78
        } elseif (is_array($role)) {
79
            $auth->roles()->syncWithoutDetaching($role);
80
        } else {
81
            throw new InvalidArgumentException(
82
                sprintf('[role] parameter must be type of integer or instance of \Mrluke\Privileges\Contracts\Role. %s type given.', gettype($role))
83
            );
84
        }
85
    }
86
87
    /**
88
     * Return permission level based on personal's & role's permission.
89
     *
90
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
91
     * @param string|array                              $scopes
92
     *
93
     * @return int
94
     *
95
     * @throws \InvalidArgumentException
96
     */
97
    public function considerPermission(Authorizable $auth, $scopes): int
98
    {
99
        if (!is_array($scopes)) {
100
            $scopes = [$scopes];
101
        }
102
103
        foreach ($scopes as $scope) {
104
            if ($personal = $this->getPermission($auth, $scope)) {
105
                // Personal permissions has priority
106
                // over role's ones.
107
                //
108
                return $personal->level;
109
            }
110
        }
111
112
        $general = 0;
113
        if (!$auth->relationLoaded('roles')) {
114
            $auth->load('roles.permissions');
115
        }
116
117
        foreach ($scopes as $scope) {
118
            foreach ($auth->roles as $r) {
119
                // Let's check if there's a given scope defined
120
                // as a permission in any of Authorizable roles.
121
                //
122
                if ($p = $this->getPermission($r, $scope)) {
123
                    ($p->level < $general) ?: $general = $p->level;
124
                }
125
            }
126
        }
127
128
        return $general;
129
    }
130
131
    /**
132
     * Return restrictions based on roles.
133
     *
134
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
135
     *
136
     * @return array
137
     *
138
     * @throws \InvalidArgumentException
139
     */
140
    public function considerRestriction(Authorizable $auth): array
141
    {
142
        $restrictions = [];
143
        $level        = 0;
144
145
        if (!$auth->relationLoaded('roles')) {
146
            $auth->load('roles.permissions');
147
        }
148
149
        foreach ($auth->roles as $r) {
150
            // Let's check if there're restrictions for a roles.
151
            ($level < $r->level) ?: $restrictions = $r->restrictions;
152
        }
153
154
        return is_array($restrictions) ? $restrictions : [];
155
    }
156
157
    /**
158
     * Detect which scope should be applied for given model.
159
     *
160
     * @param string $model
161
     *
162
     * @return string|null
163
     */
164
    public function detectScope(string $model)
165
    {
166
        return $this->config->get(
167
            'mapping.' . $model,
168
            $this->config->get('mapping_default')
169
        );
170
    }
171
172
    /**
173
     * Return Authorizable model reference.
174
     *
175
     * @return string
176
     */
177
    public function getAuthorizableModel(): string
178
    {
179
        return $this->config->get('authorizable');
180
    }
181
182
    /**
183
     * Return Authorizable migration config.
184
     *
185
     * @return array
186
     */
187
    public function getAuthorizableMigration(): array
188
    {
189
        if (is_null($this->authKeyName)) {
190
            $authorizableClass = $this->config->get('authorizable');
191
            $instance          = new $authorizableClass;
192
193
            if (!$instance instanceof Model) {
194
                throw new ConfigurationException(
195
                    sprintf('An instance of [authorizable] should be \Illuminate\Database\Eloquent\Model. %s given.', get_class($instance))
196
                );
197
            }
198
199
            $this->authKeyName = $instance->getKeyName();
200
            $this->authKeyType = $instance->getKeyType();
201
            $this->authTable   = $instance->getTable();
202
        }
203
204
        return [
205
            'key'   => $this->authKeyName,
206
            'type'  => $this->authKeyType,
207
            'table' => $this->authTable,
208
        ];
209
    }
210
211
    /**
212
     * Return Permission for given scope.
213
     *
214
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
215
     * @param string                                  $scope
216
     *
217
     * @return \Mrluke\Privileges\Contracts\Permission|null
218
     *
219
     * @throws \InvalidArgumentException
220
     */
221
    public function getPermission(Permitable $subject, string $scope)
222
    {
223
        $this->checkScope($scope);
224
225
        return $subject->permissions->where('scope', $scope)->first();
226
    }
227
228
    /**
229
     * Grant or update premission for a Permitable.
230
     *
231
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
232
     * @param string                                  $scope
233
     * @param int                                     $level
234
     *
235
     * @return void
236
     *
237
     * @throws \InvalidArgumentException
238
     */
239
    public function grantPermission(Permitable $subject, string $scope, int $level): void
240
    {
241
        $this->checkScopeAndLevel($scope, $level);
242
243
        $permission = $subject->permissions()->ofScope($scope)->first();
244
245
        $permission ? $permission->update(['level' => $level]) : $subject->permissions()->create([
246
            'scope' => $scope,
247
            'level' => $level,
248
        ]);
249
    }
250
251
    /**
252
     * Determine if there's a given scope Permission for a Permitable.
253
     *
254
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
255
     * @param string                                  $scope
256
     *
257
     * @return bool
258
     *
259
     * @throws \InvalidArgumentException
260
     */
261
    public function hasPermission(Permitable $subject, string $scope): bool
262
    {
263
        $this->checkScope($scope);
264
265
        return $subject->permissions->where('scope', $scope)->exists();
266
    }
267
268
    /**
269
     * Determine if Permitable has given Role assigned.
270
     *
271
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
272
     * @param mixed                                   $role
273
     *
274
     * @return bool
275
     */
276
    public function hasRole(Permitable $subject, $role): bool
277
    {
278
        return $subject->roles->where('id', $role->id)->exists();
279
    }
280
281
    /**
282
     * Regain permission for a Permitable.
283
     *
284
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
285
     * @param string                                  $scope
286
     *
287
     * @return void
288
     *
289
     * @throws \InvalidArgumentException
290
     */
291
    public function regainPermission(Permitable $subject, string $scope): void
292
    {
293
        $this->checkScope($scope);
294
295
        $subject->permissions()->ofScope($scope)->delete();
296
    }
297
298
    /**
299
     * Remove Authorizable's role.
300
     *
301
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
302
     * @param mixed                                     $role
303
     *
304
     * @return void
305
     */
306
    public function removeRole(Authorizable $auth, $role): void
307
    {
308
        if ($role instanceof Role) {
309
            $auth->roles()->detach($role->id);
310
        } elseif (is_integer($role)) {
311
            $auth->roles()->detach($role);
312
        } else {
313
            throw new InvalidArgumentException(
314
                sprintf('[role] parameter must be type of integer or instance of \Mrluke\Privileges\Contracts\Role. %s type given.', gettype($role))
315
            );
316
        }
317
    }
318
319
    /**
320
     * Return class attributes or setting.
321
     *
322
     * @param string $name
323
     *
324
     * @return mixed
325
     */
326
    public function __get(string $name)
327
    {
328
        if (is_null($this->authKeyName)) {
329
            $this->getAuthorizableMigration();
330
        }
331
332
        if (in_array($name, ['authKeyName', 'authKeyType', 'authTable'])) {
333
            return $this->{$name};
334
        }
335
336
        return $this->config->get($name, null);
337
    }
338
339
    /**
340
     * Check if the scope value.
341
     *
342
     * @param string $scope
343
     *
344
     * @return void
345
     *
346
     * @throws \InvalidArgumentException
347
     */
348
    private function checkScope(string $scope): void
349
    {
350
        $scope = explode(':', $scope);
351
352
        if (!in_array($scope[0], $this->config->get('scopes'))) {
353
            throw new InvalidArgumentException('Given [scope] is not allowed.');
354
        }
355
    }
356
357
    /**
358
     * Check if the scope & level values.
359
     *
360
     * @param string $scope
361
     * @param int    $level
362
     *
363
     * @return void
364
     *
365
     * @throws \InvalidArgumentException
366
     */
367
    private function checkScopeAndLevel(string $scope, int $level): void
368
    {
369
        $this->checkScope($scope);
370
371
        if ($level < 0 && $level > 4) {
372
            throw new InvalidArgumentException('Given [level] must be in range of 0-4.');
373
        }
374
    }
375
}
376