Passed
Push — master ( b177ff...5f7b57 )
by Łukasz
02:25
created

Detector::concernIpRestriction()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 4
nop 1
dl 0
loc 21
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
namespace Mrluke\Privileges;
4
5
use Exception;
6
use InvalidArgumentException;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Support\Str;
9
10
use Mrluke\Privileges\Contracts\Authorizable;
11
use Mrluke\Privileges\Manager;
12
13
/**
14
 * Detector is a class that provides full complex
15
 * method to determine Users privilege by its Role & external
16
 * conditions like: IP, Hours, Region etc.
17
 *
18
 * @author    Łukasz Sitnicki (mr-luke)
19
 * @link      http://github.com/mr-luke/privileges
20
 *
21
 * @category  Laravel
22
 * @package   mr-luke/privileges
23
 * @license   MIT
24
 * @version   1.0.0
25
 */
26
class Detector
27
{
28
    /**
29
     * Value return in case of allowed access.
30
     *
31
     * @var mixed
32
     */
33
    protected $allowed;
34
35
    /**
36
     * Value return in case of denid access.
37
     *
38
     * @var mixed
39
     */
40
    protected $denied;
41
42
    /**
43
     * Authorizable primary key name.
44
     *
45
     * @var string
46
     */
47
    protected $identifier;
48
49
    /**
50
     * Instance of privileges Manager.
51
     *
52
     * @var \Mrluke\Privileges\Manager
53
     */
54
    protected $manager;
55
56
    /**
57
     * Scope name.
58
     *
59
     * @var string
60
     */
61
    protected $scope;
62
63
    /**
64
     * Instance of Authorizable.
65
     *
66
     * @var \Mrluke\Privileges\Contracts\Authorizable
67
     */
68
    protected $subject;
69
70
    public function __construct(Manager $manager)
71
    {
72
        $this->manager    = $manager;
73
74
        $this->allowed    = $manager->allowed_value;
75
        $this->denied     = $manager->denied_value;
76
        $this->identifier = $manager->authKeyName;
77
    }
78
79
    /**
80
     * Determine if give Subject has resource.
81
     *
82
     * @param  \Illuminate\Database\Eloquent\Model  $model
83
     * @param  string  $relation
84
     * @return mixed
85
     */
86
    public function has(Model $model, string $relation = null)
87
    {
88
        $this->hasSubjectSet();
89
90
        // First we need to check restritions for given Role
91
        // to detect special Location, IP, Hours conditions.
92
        if (! $this->checkRestrictions()) return $this->denied;
93
94
        return $this->hasModel($model, $relation) ? $this->allowed: $this->denied;
95
    }
96
97
    /**
98
     * Determines if Subject has resource or has enough privilege.
99
     *
100
     * @param  \Illuminate\Database\Eloquent\Model  $model
101
     * @param  int $min
102
     * @param  string $relation
103
     * @return mixed
104
     */
105
    public function hasOrLevel(Model $model, int $min, string $relation = null)
106
    {
107
        $this->hasSubjectAndScopeSet();
108
109
        // First we need to check restritions for given Role
110
        // to detect special Location, IP, Hours conditions.
111
        if (! $this->checkRestrictions()) return $this->denied;
112
113
        if ($this->hasModel($model, $relation)) return $this->allowed;
114
115
        return $this->hasLevel($min) ? $this->allowed : $this->denied;
116
    }
117
118
    /**
119
     * Determines if Subject has access to resource.
120
     *
121
     * @param  int     $min
122
     * @param  boolean $this->denied
123
     * @return mixed
124
     */
125
    public function level(int $min)
126
    {
127
        $this->hasSubjectAndScopeSet();
128
129
        // First we need to check restritions for given Role
130
        // to detect special Location, IP, Hours conditions.
131
        if (! $this->checkRestrictions()) return $this->denied;
132
133
        return $this->hasLevel($min) ? $this->allowed : $this->denied;
134
    }
135
136
    /**
137
     * Determines if Subject is owner of model.
138
     *
139
     * @param  \Illuminate\Database\Eloquent\Model  $model
140
     * @param  boolean $this->denied
141
     * @param  string $foreign
142
     * @return mixed
143
     */
144
    public function owner(Model $model, string $foreign = null)
145
    {
146
        $this->hasSubjectSet();
147
148
        // First we need to check restritions for given Role
149
        // to detect special Location, IP, Hours conditions.
150
        if (! $this->checkRestrictions()) return $this->denied;
151
152
        return $this->isOwner($model, $foreign) ? $this->allowed : $this->denied;
153
    }
154
155
    /**
156
     * Determines if Subject is owner of model or has enough privilege.
157
     *
158
     * @param  \Illuminate\Database\Eloquent\Model  $model
159
     * @param  int $min
160
     * @param  boolean $this->denied
161
     * @param  string $foreign
162
     * @return mixed
163
     */
164
    public function ownerOrLevel(Model $model, int $min, string $foreign = null)
165
    {
166
        $this->hasSubjectAndScopeSet();
167
168
        // First we need to check restritions for given Role
169
        // to detect special Location, IP, Hours conditions.
170
        if (! $this->checkRestrictions()) return $this->denied;
171
172
        if ($this->isOwner($model, $foreign)) return $this->allowed;
173
174
        return $this->hasLevel($min) ? $this->allowed : $this->denied;
175
    }
176
177
    /**
178
     * Set scope that is checking.
179
     *
180
     * @param  string $scope
181
     * @return self
182
     */
183
    public function scope(string $scope): self
184
    {
185
        $this->scope = $scope;
186
187
        return $this;
188
    }
189
190
    /**
191
     * Determines if Subject and model shares instance.
192
     *
193
     * @param  \Illuminate\Database\Eloquent\Model  $model
194
     * @param  string $modelRelation
195
     * @param  string $relation
196
     * @param  boolean $this->denied
197
     * @return mixed
198
     */
199
    public function share(Model $model, string $modelRelation, string $relation)
200
    {
201
        $this->hasSubjectSet();
202
203
        // First we need to check restritions for given Role
204
        // to detect special Location, IP, Hours conditions.
205
        if (! $this->checkRestrictions()) return $this->denied;
206
207
        return $this->isSharing($model, $modelRelation, $relation) ?
208
            $this->allowed : $this->denied;
209
    }
210
211
    /**
212
     * Set user that needs to be checked.
213
     *
214
     * @param  \Mrluke\Privileges\Contracts\Authorizable $user
215
     * @return self
216
     */
217
    public function subject(Authorizable $auth): self
218
    {
219
        $this->subject = $auth;
220
221
        return $this;
222
    }
223
224
    /**
225
     * Check if there is any restrition for subject's role.
226
     *
227
     * @return bool
228
     */
229
    protected function checkRestrictions(): bool
230
    {
231
        $result = true;
232
        // Let's get restritions and check
233
        // if its present.
234
        if ($restrictions = $this->manager->considerRestriction($this->subject)) {
235
            // We need to check if subjects's IP address is allowed
236
            // by it's Role to perform the action.
237
            if (isset($restrictions['ip']))
238
            {
239
                $result = $this->concernIpRestriction($restrictions['ip'] ?? []);
240
            }
241
242
            // We need to check if access hour is correct.
243
            if (isset($restrictions['hours']) && $result)
244
            {
245
                $result = $this->concernTimeRestriction($restrictions['time'] ?? []);
246
            }
247
        }
248
249
        return $result;
250
    }
251
252
    /**
253
     * Checks if given level is enough.
254
     *
255
     * @param  int  $min
256
     * @return bool
257
     */
258
    protected function hasLevel(int $min): bool
259
    {
260
        $level = $this->manager->considerPermission($this->subject, $this->scope);
261
262
        return $level >= $min;
263
    }
264
265
    /**
266
     * Determine if give Subject has resource.
267
     *
268
     * @param  \Illuminate\Database\Eloquent\Model  $model
269
     * @param  string|null  $relation
270
     * @return bool
271
     */
272
    protected function hasModel(Model $model, $relation): bool
273
    {
274
        if (is_null($relation)) {
275
            // We need to detect foreign key of relation
276
            // to check if subject is an owner.
277
            $relation = Str::camel(Str::plural(class_basename($model)));
278
        }
279
        $foreign = $this->subject->$relation()->getRelatedPivotKeyName();
280
281
        return $this->subject->$relation()->where($foreign, $model->{$this->identifier})->exists();
282
    }
283
284
    /**
285
     * Check if Subject is owner of model.
286
     *
287
     * @param  \Illuminate\Database\Eloquent\Model  $model
288
     * @param  string|null $foreign
289
     * @return bool
290
     */
291
    protected function isOwner(Model $model, $foreign): bool
292
    {
293
        if (is_null($foreign)) {
294
            // We need to detect foreign key of relation
295
            // to check if subject is an owner.
296
            $class = Str::snake(class_basename($this->subject));
297
298
            $foreign = $model->$class()->getForeignKeyName();
299
        }
300
301
        return $this->subject->{$this->identifier} == $model->{$foreign};
302
    }
303
304
    /**
305
     * Chech if Subject and model shares instance.
306
     *
307
     * @param  \Illuminate\Database\Eloquent\Model  $model
308
     * @param  string $modelRelation
309
     * @param  string $relation
310
     * @return bool
311
     */
312
    protected function isSharing(Model $model, $modelRelation, $relation): bool
313
    {
314
        $foreign = $model->$modelRelation()->getForeignKeyName();
315
316
        return $this->subject->$relation()->where($foreign, $model->id)->exists();
317
    }
318
319
    /**
320
     * Check if detector is correctly set.
321
     *
322
     * @return void
323
     * @throws \InvalidArgumentException
324
     */
325
    protected function hasSubjectSet(): void
326
    {
327
        if (empty($this->subject)) {
328
            throw new InvalidArgumentException(
329
                'Setting subject is required. Use method subject() befor detection.'
330
            );
331
        }
332
    }
333
334
    /**
335
     * Check if detector is correctly set.
336
     *
337
     * @return void
338
     * @throws \InvalidArgumentException
339
     */
340
    protected function hasSubjectAndScopeSet(): void
341
    {
342
        $this->hasSubjectSet();
343
344
        if (empty($this->scope)) {
345
            throw new InvalidArgumentException(
346
                'Setting scope is required. Use method scope() befor detection.'
347
            );
348
        }
349
    }
350
351
    /**
352
     * Compare IPs from list to given one.
353
     *
354
     * @param  array $rules
355
     * @param  float $ip
356
     * @return bool
357
     */
358
    private function compareIPs(array $rules, float $ip): bool
359
    {
360
        foreach ($rules as $ip) {
361
            $result = ($ip == ip2long($ip)) ? true : false;
362
        }
363
364
        return $result ?? true;
365
    }
366
367
    /**
368
     * Check if given IP restrictions allows Authorizable to perform action.
369
     *
370
     * @param  array $restrictions
371
     * @return bool
372
     */
373
    private function concernIpRestriction(array $restrictions): bool
374
    {
375
        $ip     = ip2long(request()->ip());
376
        $rule   = $restrictions['rule'];
377
        $result = true;
378
379
        switch ($restrictions['type']) {
380
            case 'one':
381
                // Let's check if subject's IP is the same
382
                // as set one for Role.
383
                $result = $this->compareIPs($rule, $ip);
384
                break;
385
386
            case 'range':
387
                // Let's check if subject's IP is within
388
                // range set to Role.
389
                ($ip >= ip2long($rule[0]) && $ip <= ip2long($rule[1])) ?: $result = false;
390
                break;
391
        }
392
393
        return $result;
394
    }
395
396
    /**
397
     * Check if given Time restrictions allows Authorizable to perform action.
398
     *
399
     * @param  array $restrictions
400
     * @return bool
401
     */
402
    private function concernTimeRestriction(array $restrictions): bool
403
    {
404
        $now = now();
405
        $rule = $restrictions['rule'];
406
407
        switch ($restrictions['type']) {
408
            case 'day':
409
                return (in_array($now->dayOfWeekIso, $rule)) ? true : false;
410
411
            case 'hour':
412
                return ($now->hour >= $rule[0] && $now->hour <= $rule[1]) ? true : false;
413
        }
414
415
        return true;
416
    }
417
}
418