Db::setCredentialPreprocessor()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace LmcUser\Authentication\Adapter;
4
5
use Interop\Container\ContainerInterface;
6
use Laminas\Authentication\Result as AuthenticationResult;
7
use Laminas\EventManager\EventInterface;
8
use Laminas\ServiceManager\ServiceManager;
9
use Laminas\Crypt\Password\Bcrypt;
10
use Laminas\Session\Container as SessionContainer;
11
use LmcUser\Entity\UserInterface;
12
use LmcUser\Mapper\UserInterface as UserMapperInterface;
13
use LmcUser\Options\ModuleOptions;
14
15
class Db extends AbstractAdapter
16
{
17
    /**
18
     * @var UserMapperInterface
19
     */
20
    protected $mapper;
21
22
    /**
23
     * @var callable
24
     */
25
    protected $credentialPreprocessor;
26
27
    /**
28
     * @var ServiceManager
29
     */
30
    protected $serviceManager;
31
32
    /**
33
     * @var ModuleOptions
34
     */
35
    protected $options;
36
37
    /**
38
     * Called when user id logged out
39
     *
40
     * @param AdapterChainEvent $e
41
     */
42
    public function logout(AdapterChainEvent $e)
0 ignored issues
show
Unused Code introduced by
The parameter $e is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
43
    {
44
        $this->getStorage()->clear();
45
    }
46
47
    /**
48
     * @param  AdapterChainEvent $e
49
     * @return bool
50
     */
51
    public function authenticate(AdapterChainEvent $e)
52
    {
53
        if ($this->isSatisfied()) {
54
            $storage = $this->getStorage()->read();
55
            $e->setIdentity($storage['identity'])
56
                ->setCode(AuthenticationResult::SUCCESS)
57
                ->setMessages(array('Authentication successful.'));
58
            return;
59
        }
60
61
        $identity   = $e->getRequest()->getPost()->get('identity');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Laminas\Stdlib\RequestInterface as the method getPost() does only exist in the following implementations of said interface: Laminas\Http\PhpEnvironment\Request, Laminas\Http\Request.

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...
62
        $credential = $e->getRequest()->getPost()->get('credential');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Laminas\Stdlib\RequestInterface as the method getPost() does only exist in the following implementations of said interface: Laminas\Http\PhpEnvironment\Request, Laminas\Http\Request.

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...
63
        $credential = $this->preProcessCredential($credential);
64
        /**
65
*
66
         *
67
 * @var UserInterface|null $userObject
68
*/
69
        $userObject = null;
70
71
        // Cycle through the configured identity sources and test each
72
        $fields = $this->getOptions()->getAuthIdentityFields();
73
        while (!is_object($userObject) && count($fields) > 0) {
74
            $mode = array_shift($fields);
75
76
            switch ($mode) {
77
                case 'username':
78
                    $userObject = $this->getMapper()->findByUsername($identity);
79
                    break;
80
                case 'email':
81
                    $userObject = $this->getMapper()->findByEmail($identity);
82
83
                    break;
84
            }
85
        }
86
87
        if (!$userObject) {
88
            $e->setCode(AuthenticationResult::FAILURE_IDENTITY_NOT_FOUND)
89
                ->setMessages(array('A record with the supplied identity could not be found.'));
90
            $this->setSatisfied(false);
91
            return false;
92
        }
93
94
        if ($this->getOptions()->getEnableUserState()) {
95
            // Don't allow user to login if state is not in allowed list
96
            if (!in_array($userObject->getState(), $this->getOptions()->getAllowedLoginStates())) {
97
                $e->setCode(AuthenticationResult::FAILURE_UNCATEGORIZED)
98
                    ->setMessages(array('A record with the supplied identity is not active.'));
99
                $this->setSatisfied(false);
100
                return false;
101
            }
102
        }
103
104
        $bcrypt = new Bcrypt();
105
        $bcrypt->setCost($this->getOptions()->getPasswordCost());
106
107
        if (!$bcrypt->verify($credential, $userObject->getPassword())) {
108
            // Password does not match
109
            $e->setCode(AuthenticationResult::FAILURE_CREDENTIAL_INVALID)
110
                ->setMessages(array('Supplied credential is invalid.'));
111
            $this->setSatisfied(false);
112
            return false;
113
        }
114
115
        // regen the id
116
        $session = new SessionContainer($this->getStorage()->getNameSpace());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Laminas\Authentication\Storage\StorageInterface as the method getNameSpace() does only exist in the following implementations of said interface: Laminas\Authentication\Storage\Session.

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...
117
        $session->getManager()->regenerateId();
118
119
        // Success!
120
        $e->setIdentity($userObject->getId());
121
        // Update user's password hash if the cost parameter has changed
122
        $this->updateUserPasswordHash($userObject, $credential, $bcrypt);
123
        $this->setSatisfied(true);
124
        $storage = $this->getStorage()->read();
125
        $storage['identity'] = $e->getIdentity();
126
        $this->getStorage()->write($storage);
127
        $e->setCode(AuthenticationResult::SUCCESS)
128
            ->setMessages(array('Authentication successful.'));
129
    }
130
131
    protected function updateUserPasswordHash(UserInterface $userObject, $password, Bcrypt $bcrypt)
132
    {
133
        $hash = explode('$', $userObject->getPassword());
134
        if ($hash[2] === $bcrypt->getCost()) {
135
            return;
136
        }
137
        $userObject->setPassword($bcrypt->create($password));
138
        $this->getMapper()->update($userObject);
139
        return $this;
140
    }
141
142
    public function preProcessCredential($credential)
143
    {
144
        $processor = $this->getCredentialPreprocessor();
145
        if (is_callable($processor)) {
146
            return $processor($credential);
147
        }
148
149
        return $credential;
150
    }
151
152
    /**
153
     * getMapper
154
     *
155
     * @return UserMapperInterface
156
     */
157
    public function getMapper()
158
    {
159
        if (null === $this->mapper) {
160
            $this->mapper = $this->getServiceManager()->get('lmcuser_user_mapper');
161
        }
162
163
        return $this->mapper;
164
    }
165
166
    /**
167
     * setMapper
168
     *
169
     * @param  UserMapperInterface $mapper
170
     * @return Db
171
     */
172
    public function setMapper(UserMapperInterface $mapper)
173
    {
174
        $this->mapper = $mapper;
175
176
        return $this;
177
    }
178
179
    /**
180
     * Get credentialPreprocessor.
181
     *
182
     * @return callable
183
     */
184
    public function getCredentialPreprocessor()
185
    {
186
        return $this->credentialPreprocessor;
187
    }
188
189
    /**
190
     * Set credentialPreprocessor.
191
     *
192
     * @param  callable $credentialPreprocessor
193
     * @return $this
194
     */
195
    public function setCredentialPreprocessor($credentialPreprocessor)
196
    {
197
        $this->credentialPreprocessor = $credentialPreprocessor;
198
        return $this;
199
    }
200
201
    /**
202
     * Retrieve service manager instance
203
     *
204
     * @return ServiceManager
205
     */
206
    public function getServiceManager()
207
    {
208
        return $this->serviceManager;
209
    }
210
211
    /**
212
     * Set service manager instance
213
     *
214
     * @param ContainerInterface $serviceManager
215
     */
216
    public function setServiceManager(ContainerInterface $serviceManager)
217
    {
218
        $this->serviceManager = $serviceManager;
0 ignored issues
show
Documentation Bug introduced by
$serviceManager is of type object<Interop\Container\ContainerInterface>, but the property $serviceManager was declared to be of type object<Laminas\ServiceManager\ServiceManager>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
219
    }
220
221
    /**
222
     * @param ModuleOptions $options
223
     */
224
    public function setOptions(ModuleOptions $options)
225
    {
226
        $this->options = $options;
227
    }
228
229
    /**
230
     * @return ModuleOptions
231
     */
232
    public function getOptions()
233
    {
234
        if ($this->options === null) {
235
            $this->setOptions($this->getServiceManager()->get('lmcuser_module_options'));
236
        }
237
238
        return $this->options;
239
    }
240
}
241