Completed
Push — master ( fd295c...9747c3 )
by Vitaly
06:30
created

Controller::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 4
1
<?php declare(strict_types=1);
2
/**
3
 * Created by PhpStorm.
4
 * User: egorov
5
 * Date: 17.07.2015
6
 * Time: 9:20
7
 */
8
namespace samsoncms\app\security;
9
10
use samson\activerecord\dbQuery;
11
use samson\activerecord\groupright;
12
use samsonframework\containerannotation\Inject;
13
use samsonframework\containerannotation\Injectable;
14
use samsonframework\containerannotation\InjectArgument;
15
use samsonframework\core\ResourcesInterface;
16
use samsonframework\core\SystemInterface;
17
use samsonframework\i18n\i18nInterface;
18
use samsonframework\orm\QueryInterface;
19
20
/**
21
 * SamsonCMS security controller
22
 * @package samsoncms\security
23
 */
24
class Controller extends \samsoncms\Application
25
{
26
    /** Application access right name pattern */
27
    const RIGHT_APPLICATION_KEY = '/^APPLICATION_(?<application>.*)/ui';
28
29
    /** @var array User group rights cache */
30
    protected $rightsCache = array();
31
32
    /** Application name */
33
    public $name = 'Права';
34
35
    /** Application description */
36
    public $description = 'Права доступа';
37
38
    /** Application icon*/
39
    public $icon = 'unlock';
40
41
    /** Identifier */
42
    public $id = 'security';
43
44
    public $dbGroupIdField = 'group_id';
45
46
    /** @var string Module identifier */
47
    protected $entity = '\samson\activerecord\group';
48
49
    /** @var string SamsonCMS application form class */
50
    protected $formClassName = '\samsoncms\app\security\Form';
51
52
    /** @var QueryInterface Database query instance */
53
    protected $query;
54
55
    /**
56
     * @var \samsonframework\i18n\i18nInterface
57
     * @Injectable
58
     */
59
    protected $i18n;
60
61
     //[PHPCOMPRESSOR(remove,start)]
62
    public function prepare()
63
    {
64
        $social = & $this->system->module('social');
65
        //db()->createField($this, $social->dbTable, 'dbGroupIdField', 'INT(11)');
66
67
        $adminUser        = '[email protected]';
68
69
           // Try to find generic user
70
        $admin = $this->query
71
            ->entity($social->dbTable)
72
            ->where($social->dbEmailField, $adminUser)
73
            ->first();
74
75
        // Add admin group id value
76
        if (isset($admin)&&($admin[$this->dbGroupIdField] != 1)) {
77
            $admin[$this->dbGroupIdField] = 1;
78
            $admin->save();
0 ignored issues
show
Bug introduced by
The method save cannot be called on $admin (of type array<string,integer>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
79
        }
80
        return parent::prepare();
81
    }
82
    //[PHPCOMPRESSOR(remove,end)]
83
84
    /**
85
     * Asynchronous change group right controller action
86
     * @param string $groupID Group identifier
87
     * @param string $rightID Right identifier
88
     * @return array Asynchronous response array
89
     */
90
    public function __async_change($groupID, $rightID)
0 ignored issues
show
Coding Style introduced by
Method name "Controller::__async_change" is not in camel caps format
Loading history...
91
    {
92
        $group = null;
93
        if($this->findEntityByID($groupID, $group)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
94
            $right = null;
95
            if($this->findEntityByID($rightID, $right, 'right')) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
96
                /** @var \samsonframework\orm\Record  Try to find this right for a specific group */
97
                $groupRight = null;
98
                if ($this->query->className('groupright')->cond('GroupID', $groupID)->cond('RightID', $rightID)->first($groupRight)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method className() does only exist in the following implementations of said interface: samson\activerecord\dbQuery.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
99
                    // Remove existing
100
                    $groupRight->delete();
0 ignored issues
show
Bug introduced by
The method delete cannot be called on $groupRight (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
101
                } else { // Create new
102
                    $groupRight = new groupright();
103
                    $groupRight->Active = 1;
104
                    $groupRight->GroupID = $groupID;
105
                    $groupRight->RightID = $rightID;
106
                    $groupRight->save();
107
                }
108
109
                return array('status' => '1');
110
            }
111
112
            return array('status' => '0', 'error' => 'Right #'.$rightID.' was not found');
113
        }
114
115
        return array('status' => '0', 'error' => 'Group #'.$rightID.' was not found');
116
    }
117
118
    /**
119
     * Core routing(core.routing) event handler
120
     * @param \samson\core\Core $core
121
     * @param boolean $securityResult
122
     * @return boolean True if security passed
123
     */
124
    public function handle(&$core, &$securityResult)
125
    {
126
        // Remove URL base from current URL, split by '/'
127
        $parts = explode(__SAMSON_BASE__, $_SERVER['REQUEST_URI']);
128
        $parts = array_values(array_filter($parts));
129
        $cmsUrl = isset($parts[0]) ? $parts[0] : '';
130
131
        if ($cmsUrl == $core->module('cms')->baseUrl) {
132
            // Get module identifier
133
            $module = isset($parts[1]) ? $parts[1] : '';
134
135
            // Get action identifier
136
            //$action = isset($parts[1]) ? $parts[1] : '';
137
            // Get parameter values collection
138
            //$params = sizeof($parts) > 2 ? array_slice($parts, 2) : array();
139
            $social = & $this->system->module('social');
140
141
            // If we have are authorized
142
            if ($social->authorized()) {
143
                /**@var \samson\activerecord\user Get authorized user object */
144
                $authorizedUser = $social->user();
145
146
                $dbTable = $social->dbTable;
147
                $groupIdField = $dbTable::$fieldIDs[$this->dbGroupIdField];
148
149
                // Try to load security group rights from cache
150
                $userRights = & $this->rightsCache[$authorizedUser->$groupIdField];
151
                if (!isset($userRights)) {
152
                    // Parse security group rights and store it to cache
153
                    $userRights = $this->parseGroupRights($authorizedUser->$groupIdField);
154
                }
155
156
                // Hide all applications except with access rights
157
                foreach (self::$loaded as $application) {
158
                    if (!in_array($application->id, $userRights['application'])
159
                        && !in_array(Right::APPLICATION_ACCESS_ALL, $userRights['application'])
160
                        && $authorizedUser->$groupIdField != 1
161
                    ) {
162
                        $application->hide = true;
163
                    }
164
                }
165
166
                // If we have full right to access all applications or admin
167
                if (in_array(Right::APPLICATION_ACCESS_ALL, $userRights['application']) || $authorizedUser->$groupIdField == 1) {
168
                    return $securityResult = true;
169
                } else if (in_array($module, $userRights['application'])) { // Try to find right to access current application
170
                    return $securityResult = true;
171
                } else if ($module == '' && in_array('template', $userRights['application'])) {// Main page(empty url)
172
                    return $securityResult = true;
173
                } else { // We cannot access this application
174
                    return $securityResult = false;
175
                }
176
            }
177
        } else {
178
            return $securityResult = true;
179
        }
180
181
    }
182
183
    /**
184
     * Parse application access right
185
     * @param string $rightName Right name
186
     * @return string Application name
187
     */
188
    private function matchApplicationAccessRight($rightName, &$applicationName)
189
    {
190
        // Parse application access rights
191
        $matches = array();
192
        if (preg_match(Right::APPLICATION_ACCESS_PATTERN, $rightName, $matches)) {
193
            // Return application name
194
            $applicationName = strtolower($matches['application']);
195
            return true;
196
        }
197
198
        return false;
199
    }
200
201
    /**
202
     * Clear all database security rights records that do not match current application list
203
     * @param array $accessibleApplications Collection of loaded applications
204
     */
205
    private function clearUnmatchedRights(array $accessibleApplications)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
206
    {
207
        // Go throw all rights and remove unnecessary
208
        foreach ($this->query->className('right')->exec() as $right) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method className() does only exist in the following implementations of said interface: samson\activerecord\dbQuery.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
209
            // Match application access rights
210
            $applicationID = '';
211
            if ($this->matchApplicationAccessRight($right->Name, $applicationID)) {
212
                // If there is no such application that access right exists
213
                if(!isset($accessibleApplications[$applicationID])) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
214
                    $right->delete();
215
                }
216
            }
217
        }
218
    }
219
220
    /**
221
     * Parse database application user group rights
222
     * @param integer $groupID Security group identifier
223
     * @return array Parsed user group rights
224
     */
225
    public function parseGroupRights($groupID)
226
    {
227
        /** @var array $parsedRights Parsed rights */
228
        $parsedRights = array('application' => array());
229
230
        /** @var \samsonframework\orm\Record[] $groupRights Collection of user rights */
231
        $groupRights = array();
232
        // Retrieve all user group rights
233
        if ($this->query->className('groupright')->join('right')->cond('GroupID', $groupID)->exec($groupRights)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method className() does only exist in the following implementations of said interface: samson\activerecord\dbQuery.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
234
            // Iterate all group rights
235
            foreach ($groupRights as $groupRight) {
236
                // If we have rights for this group
237
                if (isset($groupRight->onetomany['_right'])) {
0 ignored issues
show
Bug introduced by
The property onetomany does not seem to exist. Did you mean oneToMany?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
238
                    foreach ($groupRight->onetomany['_right'] as $userRight) {
0 ignored issues
show
Bug introduced by
The property onetomany does not seem to exist. Did you mean oneToMany?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
239
                        // Parse application access rights
240
                        $applicationID = '';
241
                        if ($this->matchApplicationAccessRight($userRight->Name, $applicationID)) {
242
                            $parsedRights['application'][] = $applicationID;
243
                        }
244
                    }
245
                }
246
            }
247
        }
248
249
        return $parsedRights;
250
    }
251
252
    /** Application initialization */
253
    public function init(array $params = array())
254
    {
255
        // Find all applications that needs access rights to it
256
        $accessibleApplications = array(
257
            'template' => $this->i18n->translate('Главная страница'),   // Main application
258
            Right::APPLICATION_ACCESS_ALL => Right::APPLICATION_ACCESS_ALL // All application
259
        );
260
261
        // Iterate all loaded applications
262
        foreach (self::$loaded as $application) {
263
            // Iterate only applications with names
264
            $accessibleApplications[$application->id] = $application->name;
265
        }
266
267
        // Iterate all applications that needs access rights
268
        foreach ($accessibleApplications as $accessibleApplicationID => $accessibleApplicationName) {
269
            // Try to find this right in db
270
            if (!$this->query->className('right')->cond('Name', Right::APPLICATION_ACCESS_TEMPLATE.$accessibleApplicationID)->first()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method className() does only exist in the following implementations of said interface: samson\activerecord\dbQuery.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
271
                && isset($accessibleApplicationName{0}) // Name not empty
272
            ) {
273
                $right = new Right();
274
                $right->Name = Right::APPLICATION_ACCESS_TEMPLATE.strtoupper($accessibleApplicationID);
275
                $right->Description = $accessibleApplicationID != Right::APPLICATION_ACCESS_ALL
276
                    ? $this->i18n->translate('Доступ к приложению').' "'.$accessibleApplicationName.'"'
277
                    : $this->i18n->translate('Полный доступ ко всем приложениям');
278
                $right->Active = 1;
279
                $right->save();
280
            }
281
        }
282
283
        // Subscribe to core security event
284
        \samsonphp\event\Event::subscribe('core.security', array($this, 'handle'));
285
    }
286
}
287