AbstractLoginModule::initialize()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 8
nop 4
dl 0
loc 22
ccs 0
cts 14
cp 0
crap 30
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * AppserverIo\Appserver\ServletEngine\Security\Auth\Spi\AbstractLoginModule
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2015 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/appserver
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Appserver\ServletEngine\Security\Auth\Spi;
22
23
use AppserverIo\Lang\String;
24
use AppserverIo\Lang\Reflection\ReflectionClass;
25
use AppserverIo\Collections\ArrayList;
26
use AppserverIo\Collections\MapInterface;
27
use AppserverIo\Collections\CollectionInterface;
28
use AppserverIo\Psr\Security\Auth\Subject;
29
use AppserverIo\Psr\Security\Acl\GroupInterface;
30
use AppserverIo\Psr\Security\Auth\Spi\LoginModuleInterface;
31
use AppserverIo\Psr\Security\Auth\Callback\NameCallback;
32
use AppserverIo\Psr\Security\Auth\Callback\PasswordCallback;
33
use AppserverIo\Psr\Security\Auth\Callback\CallbackHandlerInterface;
34
use AppserverIo\Appserver\ServletEngine\Security\SimpleGroup;
35
use AppserverIo\Appserver\ServletEngine\Security\SimplePrincipal;
36
use AppserverIo\Appserver\ServletEngine\Security\Utils\ParamKeys;
37
use AppserverIo\Appserver\ServletEngine\Security\Utils\SharedStateKeys;
38
39
/**
40
 * An abstract login module implementation.
41
 *
42
 * @author    Tim Wagner <[email protected]>
43
 * @copyright 2015 TechDivision GmbH <[email protected]>
44
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
45
 * @link      https://github.com/appserver-io/appserver
46
 * @link      http://www.appserver.io
47
 */
48
abstract class AbstractLoginModule implements LoginModuleInterface
49
{
50
51
    /**
52
     * The Subject to update after a successful login.
53
     *
54
     * @var \AppserverIo\Psr\Security\Auth\Subject
55
     */
56
    protected $subject;
57
58
    /**
59
     * The callback handler to obtain username and password.
60
     *
61
     * @var \AppserverIo\Psr\Security\Auth\Callback\CallbackHandlerInterface
62
     */
63
    protected $callbackHandler;
64
65
    /**
66
     * Used the share the login state between multiple modules.
67
     *
68
     * @var \AppserverIo\Collections\MapInterface
69
     */
70
    protected $sharedState;
71
72
    /**
73
     * The login module parameters.
74
     *
75
     * @var \AppserverIo\Collections\MapInterface
76
     */
77
    protected $params;
78
79
    /**
80
     * Flag that the shared state credential should be used.
81
     *
82
     * @var boolean
83
     */
84
    protected $useFirstPass = false;
85
86
    /**
87
     * Flag indicating if the login phase succeeded. Subclasses that override the
88
     * login method must set this to true on successful completion of login.
89
     *
90
     * @var boolean
91
     */
92
    protected $loginOk = false;
93
94
    /**
95
     * The unauthenticated login identity.
96
     *
97
     * @var \AppserverIo\Psr\Security\PrincipalInterface
98
     */
99
    protected $unauthenticatedIdentity;
100
101
    /**
102
     * The class name used to create a principal.
103
     *
104
     * @var \AppserverIo\Lang\String
105
     */
106
    protected $principalClassName;
107
108
    /**
109
     * Initialize the login module. This stores the subject, callbackHandler and sharedState and options
110
     * for the login session. Subclasses should override if they need to process their own options. A call
111
     * to parent::initialize() must be made in the case of an override.
112
     *
113
     * The following parameters can by default be passed from the configuration.
114
     *
115
     * passwordStacking:        If this is set to "useFirstPass", the login identity will be taken from the
116
     *                          appserver.security.auth.login.name value of the sharedState map, and the proof
117
     *                          of identity from the appserver.security.auth.login.password value of the sharedState map
118
     * principalClass:          A Principal implementation that support a constructor taking a string argument for the princpal name
119
     * unauthenticatedIdentity: The name of the principal to asssign and authenticate when a null username and password are seen
120
     *
121
     * @param \AppserverIo\Psr\Security\Auth\Subject                           $subject         The Subject to update after a successful login
122
     * @param \AppserverIo\Psr\Security\Auth\Callback\CallbackHandlerInterface $callbackHandler The callback handler that will be used to obtain the user identity and credentials
123
     * @param \AppserverIo\Collections\MapInterface                            $sharedState     A map shared between all configured login module instances
124
     * @param \AppserverIo\Collections\MapInterface                            $params          The parameters passed to the login module
125
     *
126
     * @return void
127
     */
128
    public function initialize(Subject $subject, CallbackHandlerInterface $callbackHandler, MapInterface $sharedState, MapInterface $params)
129
    {
130
131
        // initialize the passed parameters
132
        $this->params = $params;
133
        $this->subject = $subject;
134
        $this->sharedState = $sharedState;
135
        $this->callbackHandler = $callbackHandler;
136
137
        // query whether or not we have password stacking activated or not
138
        if ($params->exists(ParamKeys::PASSWORD_STACKING) && $params->get(ParamKeys::PASSWORD_STACKING) === 'useFirstPass') {
139
            $this->useFirstPass = true;
140
        }
141
142
        // check for a custom principal implementation
143
        if ($params->exists(ParamKeys::PRINCIPAL_CLASS)) {
144
            $this->principalClassName = new String($params->get(ParamKeys::PRINCIPAL_CLASS));
145
        }
146
147
        // check for unauthenticatedIdentity option.
148
        if ($params->exists(ParamKeys::UNAUTHENTICATED_IDENTITY)) {
149
            $this->unauthenticatedIdentity = $this->createIdentity($params->get(ParamKeys::UNAUTHENTICATED_IDENTITY));
150
        }
151
    }
152
153
    /**
154
     * Flag that the shared state credential should be used.
155
     *
156
     * @return boolean TRUE if the shared state credential should be used, else FALSE
157
     */
158
    public function getUseFirstPass()
159
    {
160
        return $this->useFirstPass;
161
    }
162
163
    /**
164
     * Looks for servlet_engine.authentication.login_module.login_name and servlet_engine.authentication.login_module.login_password
165
     * values in the sharedState map if the useFirstPass option was true and returns TRUE if they exist. If they do not or are NULL
166
     * this method returns FALSE.
167
     *
168
     * Note that subclasses that override the login method must set the loginOk var to TRUE if the login succeeds in order for the
169
     * commit phase to populate the Subject. This implementation sets loginOk to TRUE if the login() method returns TRUE, otherwise,
170
     * it sets loginOk to FALSE. Perform the authentication of username and password.
171
     *
172
     * @return boolean TRUE if the login credentials are available in the sharedMap, else FALSE
173
     * @throws \AppserverIo\Psr\Security\Auth\Login\LoginException Is thrown if an error during login occured
174
     */
175
    public function login()
176
    {
177
178
        // initialize the login state
179
        $this->loginOk = false;
180
181
        // query whether or not we should use the shared state
182
        if ($this->useFirstPass) {
183
            $name = $this->sharedState->get(SharedStateKeys::LOGIN_NAME);
184
            $password = $this->sharedState->get(SharedStateKeys::LOGIN_PASSWORD);
185
186
            // if we've a username and a password login has been successful
187
            if ($name && $password) {
188
                $this->loginOk = true;
189
                return true;
190
            }
191
        }
192
193
        // return FALSE if login has not been successful
194
        return false;
195
    }
196
197
    /**
198
     * Remove the user identity and roles added to the Subject during commit.
199
     *
200
     * @return boolean Always TRUE
201
     * @throws \AppserverIo\Psr\Security\Auth\Login\LoginException Is thrown if an error during login occured
202
     */
203
    public function logout()
204
    {
205
206
        // load the user identity and the subject's principals
207
        $identity = $this->getIdentity();
208
        $principals = $this->subject->getPrincipals();
209
210
        // remove the user identity from the subject
211
        foreach ($principals as $key => $principal) {
212
            if ($identity->equals($principal)) {
213
                $principals->remove($key);
0 ignored issues
show
Bug introduced by
The method remove() does not exist on AppserverIo\Collections\CollectionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in AppserverIo\Collections\SetInterface or AppserverIo\Collections\MapInterface or AppserverIo\Properties\PropertiesInterface or AppserverIo\Collections\SortedMapInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

213
                $principals->/** @scrutinizer ignore-call */ 
214
                             remove($key);
Loading history...
214
            }
215
        }
216
217
        // return TRUE on success
218
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by AppserverIo\Psr\Security...duleInterface::logout() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
219
    }
220
221
    /**
222
     * Method to commit the authentication process (phase 2). If the login
223
     * method completed successfully as indicated by loginOk == true, this
224
     * method adds the getIdentity() value to the subject getPrincipals() Set.
225
     * It also adds the members of each Group returned by getRoleSets()
226
     * to the subject getPrincipals() Set.
227
     *
228
     * @return true always.
229
     * @throws \AppserverIo\Psr\Security\Auth\Login\LoginException If login can't be committed'
230
     */
231
    public function commit()
232
    {
233
234
        // we can only commit if the login has been successful
235
        if ($this->loginOk === false) {
236
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type true.
Loading history...
237
        }
238
239
        // add the identity to the subject's principals
240
        $principals = $this->subject->getPrincipals();
241
        $principals->add($this->getIdentity());
0 ignored issues
show
Bug introduced by
The method add() does not exist on AppserverIo\Collections\CollectionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in AppserverIo\Collections\AbstractCollection or AppserverIo\Collections\SetInterface or AppserverIo\Collections\MapInterface or AppserverIo\Collections\AbstractMap or AppserverIo\Properties\PropertiesInterface or AppserverIo\Collections\AbstractMap or AppserverIo\Collections\SortedMapInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

241
        $principals->/** @scrutinizer ignore-call */ 
242
                     add($this->getIdentity());
Loading history...
242
243
        // load the groups
244
        $roleSets = $this->getRoleSets();
245
246
        // iterate over the groups and add them to the subject
247
        for ($g = 0; $g < sizeof($roleSets); $g++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
248
            // initialize group, name and subject group
249
            $group = $roleSets[$g];
250
            $name = $group->getName();
251
            $subjectGroup = $this->createGroup($name, $principals);
252
253
            /* if ($subjectGroup instanceof NestableGroup) {
254
                // a NestableGroup only allows Groups to be added to it so we need to add a SimpleGroup to subjectRoles to contain the roles
255
                $tmp = new SimpleGroup('Roles');
256
                $subjectGroup->addMember($tmp);
257
                $subjectGroup = $tmp;
258
            } */
259
260
            // copy the group members to the Subject group
261
            foreach ($group->getMembers() as $member) {
262
                $subjectGroup->addMember($member);
263
            }
264
        }
265
266
        // return TRUE if we succeed
267
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by AppserverIo\Psr\Security...duleInterface::commit() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
268
    }
269
270
    /**
271
     * Method to abort the authentication process (phase 2).
272
     *
273
     * @return boolean Alaways TRUE
274
     * @throws \AppserverIo\Psr\Security\Auth\Login\LoginException Is thrown if abort has not been successfully
275
     */
276
    public function abort()
277
    {
278
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by AppserverIo\Psr\Security...oduleInterface::abort() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
279
    }
280
281
    /**
282
     * Called by login() to acquire the username and password strings for
283
     * authentication. This method does no validation of either.
284
     *
285
     * @return array Array with name and password, e. g. array(0 => $name, 1 => $password)
286
     * @throws \AppserverIo\Psr\Security\Auth\Login\LoginException Is thrown if name and password can't be loaded
287
     */
288
    public function getUsernameAndPassword()
289
    {
290
291
        // create and initialize an ArrayList for the callback handlers
292
        $list = new ArrayList();
293
        $list->add($nameCallback = new NameCallback());
294
        $list->add($passwordCallback = new PasswordCallback());
295
296
        // handle the callbacks
297
        $this->callbackHandler->handle($list);
298
299
        // return an array with the username and callback
300
        return array($nameCallback->getName(), $passwordCallback->getPassword());
301
    }
302
303
    /**
304
     * Utility method to create a Principal for the given username. This
305
     * creates an instance of the principalClassName type if this option was
306
     * specified. If principalClassName was not specified, a SimplePrincipal
307
     * is created.
308
     *
309
     * @param \AppserverIo\Lang\String $name The name of the principal
310
     *
311
     * @return \AppserverIo\Psr\Security\PrincipalInterface The principal instance
312
     * @throws \Exception Is thrown if the custom principal type cannot be created
313
     */
314
    public function createIdentity(String $name)
315
    {
316
317
        //initialize the principal
318
        $principal = null;
319
320
        // query whether or not a principal class name has been specified
321
        if ($this->principalClassName == null) {
322
            $principal = new SimplePrincipal($name);
323
        } else {
324
            $reflectionClass = new ReflectionClass($this->principalClassName->__toString());
325
            $principal = $reflectionClass->newInstanceArgs(array($name));
326
        }
327
328
        // return the principal instance
329
        return $principal;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $principal also could return the type AppserverIo\Lang\Object which is incompatible with the documented return type AppserverIo\Psr\Security\PrincipalInterface.
Loading history...
330
    }
331
332
    /**
333
     * Find or create a Group with the given name. Subclasses should use this
334
     * method to locate the 'Roles' group or create additional types of groups.
335
     *
336
     * @param \AppserverIo\Lang\String                     $name       The name of the group to create
337
     * @param \AppserverIo\Collections\CollectionInterface $principals The list of principals
338
     *
339
     * @return \AppserverIo\Psr\Security\Acl\GroupInterface A named group from the principals set
340
     */
341
    protected function createGroup(String $name, CollectionInterface $principals)
342
    {
343
344
        // initialize the group
345
        /** \AppserverIo\Psr\Security\Acl\GroupInterface $roles */
346
        $roles = null;
347
348
        // iterate over the passed principals
349
        foreach ($principals as $principal) {
350
            // query whether we found a group or not, proceed if not
351
            if (($principal instanceof GroupInterface) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
352
                continue;
353
            }
354
355
            // the principal is a group
356
            $grp = $principal;
357
358
            // if the group already exists, stop searching
359
            if ($grp->getName()->equals($name)) {
360
                $roles = $grp;
361
                break;
362
            }
363
        }
364
365
        // if we did not find a group create one
366
        if ($roles == null) {
367
            $roles = new SimpleGroup($name);
368
            $principals->add($roles);
369
        }
370
371
        // return the group
372
        return $roles;
373
    }
374
375
    /**
376
     * Return's the unauthenticated identity.
377
     *
378
     * @return \AppserverIo\Psr\Security\PrincipalInterface The identity instance
379
     */
380
    public function getUnauthenticatedIdentity()
381
    {
382
        return $this->unauthenticatedIdentity;
383
    }
384
385
    /**
386
     * Overriden by subclasses to return the Principal that corresponds to
387
     * the user primary identity.
388
     *
389
     * @return \AppserverIo\Psr\Security\PrincipalInterface The user identity
390
     */
391
    abstract protected function getIdentity();
392
393
    /**
394
     * Overriden by subclasses to return the Groups that correspond to the
395
     * to the role sets assigned to the user. Subclasses should create at
396
     * least a Group named "Roles" that contains the roles assigned to the user.
397
     * A second common group is "CallerPrincipal" that provides the application
398
     * identity of the user rather than the security domain identity.
399
     *
400
     * @return array Array containing the sets of roles
401
     * @throws \AppserverIo\Psr\Security\Auth\Login\LoginException Is thrown if password can't be loaded
402
     */
403
    abstract protected function getRoleSets();
404
}
405