Completed
Push — master ( 182bdf...bfc4a1 )
by
unknown
13s queued 11s
created

Manager::hasPermission()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
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
        // Validate scopes ahead of all conditions
104
        //
105
        foreach ($scopes as $scope) {
106
            $this->checkScope($scope);
107
        }
108
109
        foreach ($scopes as $scope) {
110
            if ($personal = $this->getPermission($auth, $scope, false)) {
111
                // Personal permissions has priority
112
                // over role's ones.
113
                //
114
                return $personal->level;
115
            }
116
        }
117
118
        $general = 0;
119
        if (!$auth->relationLoaded('roles')) {
120
            $auth->load('roles.permissions');
121
        }
122
123
        foreach ($scopes as $scope) {
124
            foreach ($auth->roles as $r) {
125
                // Let's check if there's a given scope defined
126
                // as a permission in any of Authorizable roles.
127
                //
128
                if ($p = $this->getPermission($r, $scope, false)) {
129
                    ($p->level < $general) ?: $general = $p->level;
130
                }
131
            }
132
        }
133
134
        return $general;
135
    }
136
137
    /**
138
     * Return restrictions based on roles.
139
     *
140
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
141
     *
142
     * @return array
143
     *
144
     * @throws \InvalidArgumentException
145
     */
146
    public function considerRestriction(Authorizable $auth): array
147
    {
148
        $restrictions = [];
149
        $level        = 0;
150
151
        if (!$auth->relationLoaded('roles')) {
152
            $auth->load('roles.permissions');
153
        }
154
155
        foreach ($auth->roles as $r) {
156
            // Let's check if there're restrictions for a roles.
157
            ($level < $r->level) ?: $restrictions = $r->restrictions;
158
        }
159
160
        return is_array($restrictions) ? $restrictions : [];
161
    }
162
163
    /**
164
     * Detect which scope should be applied for given model.
165
     *
166
     * @param string $model
167
     *
168
     * @return string|null
169
     */
170
    public function detectScope(string $model)
171
    {
172
        return $this->config->get(
173
            'mapping.' . $model,
174
            $this->config->get('mapping_default')
175
        );
176
    }
177
178
    /**
179
     * Return Authorizable model reference.
180
     *
181
     * @return string
182
     */
183
    public function getAuthorizableModel(): string
184
    {
185
        return $this->config->get('authorizable');
186
    }
187
188
    /**
189
     * Return Authorizable migration config.
190
     *
191
     * @return array
192
     */
193
    public function getAuthorizableMigration(): array
194
    {
195
        if (is_null($this->authKeyName)) {
196
            $authorizableClass = $this->config->get('authorizable');
197
            $instance          = new $authorizableClass;
198
199
            if (!$instance instanceof Model) {
200
                throw new ConfigurationException(
201
                    sprintf('An instance of [authorizable] should be \Illuminate\Database\Eloquent\Model. %s given.', get_class($instance))
202
                );
203
            }
204
205
            $this->authKeyName = $instance->getKeyName();
206
            $this->authKeyType = $instance->getKeyType();
207
            $this->authTable   = $instance->getTable();
208
        }
209
210
        return [
211
            'key'   => $this->authKeyName,
212
            'type'  => $this->authKeyType,
213
            'table' => $this->authTable,
214
        ];
215
    }
216
217
    /**
218
     * Return Permission for given scope.
219
     *
220
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
221
     * @param string                                  $scope
222
     * @param bool                                    $checkScope
223
     *
224
     * @return \Mrluke\Privileges\Contracts\Permission|null
225
     *
226
     */
227
    public function getPermission(Permitable $subject, string $scope, bool $checkScope = true)
228
    {
229
        if($checkScope) {
230
            $this->checkScope($scope);
231
        }
232
233
        return $subject->permissions->where('scope', $scope)->first();
234
    }
235
236
    /**
237
     * Grant or update premission for a Permitable.
238
     *
239
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
240
     * @param string                                  $scope
241
     * @param int                                     $level
242
     *
243
     * @return void
244
     *
245
     * @throws \InvalidArgumentException
246
     */
247
    public function grantPermission(Permitable $subject, string $scope, int $level): void
248
    {
249
        $this->checkScopeAndLevel($scope, $level);
250
251
        $permission = $subject->permissions()->ofScope($scope)->first();
252
253
        $permission ? $permission->update(['level' => $level]) : $subject->permissions()->create([
254
            'scope' => $scope,
255
            'level' => $level,
256
        ]);
257
    }
258
259
    /**
260
     * Determine if there's a given scope Permission for a Permitable.
261
     *
262
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
263
     * @param string                                  $scope
264
     *
265
     * @return bool
266
     *
267
     * @throws \InvalidArgumentException
268
     */
269
    public function hasPermission(Permitable $subject, string $scope): bool
270
    {
271
        $this->checkScope($scope);
272
273
        return $subject->permissions->where('scope', $scope)->exists();
274
    }
275
276
    /**
277
     * Determine if Permitable has given Role assigned.
278
     *
279
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
280
     * @param mixed                                   $role
281
     *
282
     * @return bool
283
     */
284
    public function hasRole(Permitable $subject, $role): bool
285
    {
286
        return $subject->roles->where('id', $role->id)->exists();
287
    }
288
289
    /**
290
     * Regain permission for a Permitable.
291
     *
292
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
293
     * @param string                                  $scope
294
     *
295
     * @return void
296
     *
297
     * @throws \InvalidArgumentException
298
     */
299
    public function regainPermission(Permitable $subject, string $scope): void
300
    {
301
        $this->checkScope($scope);
302
303
        $subject->permissions()->ofScope($scope)->delete();
304
    }
305
306
    /**
307
     * Remove Authorizable's role.
308
     *
309
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
310
     * @param mixed                                     $role
311
     *
312
     * @return void
313
     */
314
    public function removeRole(Authorizable $auth, $role): void
315
    {
316
        if ($role instanceof Role) {
317
            $auth->roles()->detach($role->id);
318
        } elseif (is_integer($role)) {
319
            $auth->roles()->detach($role);
320
        } else {
321
            throw new InvalidArgumentException(
322
                sprintf('[role] parameter must be type of integer or instance of \Mrluke\Privileges\Contracts\Role. %s type given.', gettype($role))
323
            );
324
        }
325
    }
326
327
    /**
328
     * Return class attributes or setting.
329
     *
330
     * @param string $name
331
     *
332
     * @return mixed
333
     */
334
    public function __get(string $name)
335
    {
336
        if (is_null($this->authKeyName)) {
337
            $this->getAuthorizableMigration();
338
        }
339
340
        if (in_array($name, ['authKeyName', 'authKeyType', 'authTable'])) {
341
            return $this->{$name};
342
        }
343
344
        return $this->config->get($name, null);
345
    }
346
347
    /**
348
     * Check if the scope value.
349
     *
350
     * @param string $scope
351
     *
352
     * @return void
353
     *
354
     * @throws \InvalidArgumentException
355
     */
356
    private function checkScope(string $scope): void
357
    {
358
        $scope = explode(':', $scope);
359
360
        if (!in_array($scope[0], $this->config->get('scopes'))) {
361
            throw new InvalidArgumentException('Given [scope] is not allowed.');
362
        }
363
    }
364
365
    /**
366
     * Check if the scope & level values.
367
     *
368
     * @param string $scope
369
     * @param int    $level
370
     *
371
     * @return void
372
     *
373
     * @throws \InvalidArgumentException
374
     */
375
    private function checkScopeAndLevel(string $scope, int $level): void
376
    {
377
        $this->checkScope($scope);
378
379
        if ($level < 0 && $level > 4) {
380
            throw new InvalidArgumentException('Given [level] must be in range of 0-4.');
381
        }
382
    }
383
}
384