Completed
Push — develop ( d8ba64...7d836d )
by Mohamed
08:56
created

Permission::canAccessContext()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 16
ccs 7
cts 8
cp 0.875
rs 9.2
cc 4
eloc 8
nc 5
nop 3
crap 4.0312
1
<?php
2
3
/*
4
 * This file is part of the Tinyissue package.
5
 *
6
 * (c) Mohamed Alsharaf <[email protected]>
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 Tinyissue\Http\Middleware;
13
14
use Closure;
15
use Tinyissue\Model\Project;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Tinyissue\Http\Middleware\Project.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
use Tinyissue\Model\User;
17
use Illuminate\Http\Request;
18
use Illuminate\Routing\Route;
19
use Illuminate\Contracts\Auth\Guard;
20
use Tinyissue\Model\Project as ProjectModel;
21
use Tinyissue\Model\Permission as PermissionModel;
22
use Illuminate\Database\Eloquent\Model as ModelAbstract;
23
24
/**
25
 * Permission is a Middleware class to for checking if current user has the permission to access the request.
26
 *
27
 * @author Mohamed Alsharaf <[email protected]>
28
 */
29
class Permission
30
{
31
    /**
32
     * The Guard implementation.
33
     *
34
     * @var Guard
35
     */
36
    protected $auth;
37
38
    /**
39
     * List of permissions that can be accessed by public users.
40
     *
41
     * @var array
42
     */
43
    protected $publicAccess = [
44
        'issue-view',
45
    ];
46
47
    /**
48
     * Ordered list of contexts.
49
     *
50
     * @var array
51
     */
52
    protected $contexts = [
53
        'comment',
54
        'attachment',
55
        'issue',
56
        'project',
57
    ];
58
59
    /**
60
     * Create a new filter instance.
61
     *
62
     * @param Guard $auth
63
     */
64 45
    public function __construct(Guard $auth)
65
    {
66 45
        $this->auth = $auth;
67 45
    }
68
69
    /**
70
     * Handle an incoming request.
71
     *
72
     * @param Request  $request
73
     * @param \Closure $next
74
     *
75
     * @return mixed
76
     */
77 44
    public function handle(Request $request, Closure $next)
78
    {
79 44
        $permission = $this->getPermission($request);
80
81
        // Can't access if public project disabled or user does not have access
82 44
        if (!$this->isInPublicProjectContext($request, $permission) && !$this->canAccess($request, $permission)) {
83 8
            abort(401);
84
        }
85
86 40
        return $next($request);
87
    }
88
89
    /**
90
     * Whether or not the current context is in public project.
91
     *
92
     * @param Request $request
93
     * @param string  $permission
94
     *
95
     * @return bool
96
     */
97 44
    protected function isInPublicProjectContext(Request $request, $permission)
98
    {
99
        /** @var ProjectModel|null $project */
100 44
        $project         = $request->route()->getParameter('project');
101 44
        $isPublicEnabled = app('tinyissue.settings')->isPublicProjectsEnabled();
102 44
        $isPublicAccess  = in_array($permission, $this->publicAccess);
103 44
        $isPublicProject = $project instanceof ProjectModel && $project->isPublic();
104
105 44
        return $isPublicEnabled && $isPublicAccess && $isPublicProject;
106
    }
107
108
    /**
109
     * Whether or not the user can access the current context.
110
     *
111
     * @param Request $request
112
     * @param string  $permission
113
     *
114
     * @return bool
115
     */
116 44
    protected function canAccess(Request $request, $permission)
117
    {
118 44
        $user = $this->auth->user();
119
120 44
        return !(!is_null($user) && (!$user->permission($permission) || !$this->canAccessContext($user, $request->route(), $permission)));
0 ignored issues
show
Compatibility introduced by
$user of type object<Illuminate\Contracts\Auth\Authenticatable> is not a sub-type of object<Tinyissue\Model\User>. It seems like you assume a concrete implementation of the interface Illuminate\Contracts\Auth\Authenticatable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Documentation introduced by
$request->route() is of type object|string, but the function expects a object<Illuminate\Routing\Route>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
121
    }
122
123
    /**
124
     * Whether or not the user has a valid permission in current context
125
     * e.g. can access the issue or the project.
126
     *
127
     * @param User   $user
128
     * @param Route  $route
129
     * @param string $permission
130
     *
131
     * @return bool
132
     */
133 41
    public function canAccessContext(User $user, Route $route, $permission)
134
    {
135
        // Can access all projects
136 41
        if ($user->permission(PermissionModel::PERM_PROJECT_ALL)) {
137 36
            return true;
138
        }
139
140
        // Can access the current context
141 10
        $context = $this->getCurrentContext($route);
142 10
        if (is_null($context)) {
143
            abort(500, 'Permission middleware added to un-supported context.');
144
        }
145 10
        $action  = $permission == PermissionModel::PERM_ISSUE_MODIFY ? 'canEdit' : 'canView';
146
147 10
        return $context->$action($user);
148
    }
149
150
    /**
151
     * Return the model object of the current context.
152
     * We check the lowest ( Comment ) first, to the highest ( Project ).
153
     *
154
     * @param Route $route
155
     *
156
     * @return ModelAbstract|null
157
     */
158 10
    protected function getCurrentContext(Route $route)
159
    {
160 10
        foreach ($this->contexts as $context) {
161 10
            $parameter = $route->getParameter($context);
162 10
            if ($parameter instanceof ModelAbstract) {
163 10
                return $parameter;
164
            }
165
        }
166
167
        return null;
168
    }
169
170
    /**
171
     * Returns the permission defined in route action.
172
     *
173
     * @param Request $request
174
     *
175
     * @return mixed
176
     */
177 44
    protected function getPermission(Request $request)
178
    {
179 44
        $actions = $request->route()->getAction();
180
181 44
        return $actions['permission'];
182
    }
183
}
184