Completed
Push — master ( c3456a...b4bda7 )
by Damien
04:06 queued 38s
created

User::forceGet()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 10
cp 0
rs 9.408
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 */
6
7
namespace flipbox\saml\sp\services\login;
8
9
use craft\elements\User as UserElement;
10
use flipbox\saml\core\exceptions\InvalidMessage;
11
use flipbox\saml\sp\helpers\ProviderHelper;
12
use flipbox\saml\sp\helpers\UserHelper;
13
use flipbox\saml\sp\records\ProviderIdentityRecord;
14
use flipbox\saml\sp\Saml;
15
use LightSaml\Error\LightSamlException;
16
use LightSaml\Model\Assertion\Attribute;
17
use LightSaml\Model\Protocol\Response as SamlResponse;
18
use yii\base\UserException;
19
20
/**
21
 * Class User
22
 * @package flipbox\saml\sp\services
23
 */
24
class User
25
{
26
    use AssertionTrait;
27
28
    /**
29
     * @param SamlResponse $response
30
     * @return UserElement
31
     * @throws InvalidMessage
32
     * @throws UserException
33
     */
34
    public function getByResponse(\LightSaml\Model\Protocol\Response $response)
35
    {
36
37
        $assertion = $this->getFirstAssertion($response);
38
39
        if (! $assertion->getSubject()->getNameID()) {
40
            throw new LightSamlException('Name ID is missing.');
41
        }
42
43
        /**
44
         * Get username from the NameID
45
         *
46
         * @todo Give an option to map another attribute value to $username (like email)
47
         */
48
        $username = $assertion->getSubject()->getNameID()->getValue();
49
50
        return $this->find($username);
51
    }
52
53
    /**
54
     * @param ProviderIdentityRecord $identity
55
     * @return bool
56
     * @throws UserException
57
     * @throws \Throwable
58
     */
59
    public function login(\flipbox\saml\sp\records\ProviderIdentityRecord $identity)
60
    {
61
        if ($identity->getUser()->getStatus() !== UserElement::STATUS_ACTIVE) {
62
            if (! \Craft::$app->getUsers()->activateUser($identity->getUser())) {
63
                throw new UserException("Can't activate user.");
64
            }
65
        }
66
67
        if (\Craft::$app->getUser()->login(
68
            $identity->getUser(),
69
            /**
70
             * @todo read session duration from the response
71
             */
72
            \Craft::$app->getConfig()->getGeneral()->userSessionDuration
73
        )
74
        ) {
75
            $identity->lastLoginDate = new \DateTime();
0 ignored issues
show
Documentation introduced by
The property lastLoginDate does not exist on object<flipbox\saml\sp\r...ProviderIdentityRecord>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
76
        } else {
77
            throw new UserException("User login failed.");
78
        }
79
80
        return true;
81
    }
82
83
    /**
84
     * @param UserElement $user
85
     * @param SamlResponse $response
86
     * @throws UserException
87
     * @throws \Throwable
88
     * @throws \craft\errors\ElementNotFoundException
89
     * @throws \yii\base\Exception
90
     */
91
    public function sync(UserElement $user, \LightSaml\Model\Protocol\Response $response)
92
    {
93
        /**
94
         * enable and transform the user
95
         */
96
        $this->construct($user, $response);
97
98
        /**
99
         * Save
100
         */
101
        $this->save($user);
102
103
        /**
104
         * Sync groups depending on the plugin setting.
105
         */
106
        Saml::getInstance()->getUserGroups()->syncByAssertion($user, $this->getFirstAssertion($response));
107
    }
108
109
    /**
110
     * @param UserElement $user
111
     * @return bool
112
     * @throws UserException
113
     * @throws \Throwable
114
     * @throws \craft\errors\ElementNotFoundException
115
     * @throws \yii\base\Exception
116
     */
117
    protected function save(UserElement $user)
118
    {
119
        if (! \Craft::$app->getElements()->saveElement($user)) {
120
            Saml::error(
121
                'User save failed: ' . json_encode($user->getErrors())
122
            );
123
            throw new UserException("User save failed: " . json_encode($user->getErrors()));
124
        }
125
126
        return true;
127
    }
128
129
    /**
130
     * Response Based Methods
131
     */
132
133
    /**
134
     * @param UserElement $user
135
     * @param SamlResponse $response
136
     * @throws UserException
137
     * @throws \Throwable
138
     */
139
    protected function construct(UserElement $user, \LightSaml\Model\Protocol\Response $response)
140
    {
141
        /**
142
         * Is User Active?
143
         */
144
        if (! UserHelper::isUserActive($user)) {
145
            if (! Saml::getInstance()->getSettings()->enableUsers) {
146
                throw new UserException('User access denied.');
147
            }
148
            UserHelper::enableUser($user);
149
        }
150
151
        $assertion = $this->getFirstAssertion($response);
152
153
        if ($assertion->getFirstAttributeStatement()) {
154
            $this->transform($response, $user);
155
        } else {
156
            /**
157
             * There doesn't seem to be any attribute statements.
158
             * Try and use username for the email and move on.
159
             */
160
            \Craft::warning(
161
                'No attribute statements found! Trying to assign username as the email.',
162
                Saml::getInstance()->getHandle()
163
            );
164
            $user->email = $user->email ?: $user->username;
165
        }
166
    }
167
168
    /**
169
     * @param SamlResponse $response
170
     * @param UserElement $user
171
     * @return UserElement
172
     */
173
    protected function transform(
174
        SamlResponse $response,
175
        UserElement $user
176
    ) {
177
178
        $assertion = $response->getFirstAssertion();
179
180
        /**
181
         * Check the provider first
182
         */
183
        $attributeMap = ProviderHelper::providerMappingToKeyValue(
184
            $idpProvider = Saml::getInstance()->getProvider()->findByEntityId(
185
                $response->getIssuer()->getValue()
186
            )->one()
187
        ) ?:
188
            Saml::getInstance()->getSettings()->responseAttributeMap;
189
190
        /**
191
         * Loop thru attributes and set to the user
192
         */
193
        foreach ($assertion->getFirstAttributeStatement()->getAllAttributes() as $attribute) {
194
            if (isset($attributeMap[$attribute->getName()])) {
195
                $craftProperty = $attributeMap[$attribute->getName()];
196
                $this->assignProperty(
197
                    $user,
198
                    $attribute,
199
                    $craftProperty
200
                );
201
            }
202
        }
203
204
        return $user;
205
    }
206
207
    /**
208
     * @param User $user
209
     * @param Attribute $attribute
210
     * @param mixed $craftProperty
211
     */
212
    protected function assignProperty(
213
        UserElement $user,
214
        Attribute $attribute,
215
        $craftProperty
216
    ) {
217
218
        if (is_string($craftProperty) && property_exists($user, $craftProperty)) {
219
            Saml::debug(
220
                sprintf(
221
                    'Attribute %s is scalar and should set value "%s" to user->%s',
222
                    $attribute->getName(),
223
                    $attribute->getFirstAttributeValue(),
224
                    $craftProperty
225
                )
226
            );
227
            $user->{$craftProperty} = $attribute->getFirstAttributeValue();
228
        } elseif (is_callable($craftProperty)) {
229
            Saml::debug(
230
                sprintf(
231
                    'Attribute %s is handled with a callable.',
232
                    $attribute->getName()
233
                )
234
            );
235
            call_user_func($craftProperty, $user, $attribute);
236
        }
237
    }
238
239
    /**************************************************
240
     * Craft User Methods
241
     **************************************************/
242
243
    /**
244
     * @param $username
245
     * @return UserElement
246
     * @throws UserException
247
     */
248
    protected function find($username)
249
    {
250
        return $this->forceGet($username);
251
    }
252
253
    /**
254
     * @param $username
255
     * @return UserElement
256
     * @throws UserException
257
     */
258
    protected function forceGet($username)
259
    {
260
261
        /**
262
         * Is there a user that exists already?
263
         */
264
        if ($user = $this->getByUsernameOrEmail($username)) {
265
            /**
266
             * System check for whether we are allowed merge with this this user
267
             */
268
            if (! Saml::getInstance()->getSettings()->mergeLocalUsers) {
269
                //don't continue
270
                throw new UserException(
271
                    sprintf(
272
                        "User (%s) already exists.",
273
                        $username
274
                    )
275
                );
276
            }
277
        } else {
278
            /**
279
             * New UserElement
280
             */
281
            $user = new UserElement(
282
                [
283
                    'username' => $username
284
                ]
285
            );
286
        }
287
288
        return $user;
289
    }
290
291
    /**
292
     * @param $emailOrUsername
293
     * @return UserElement|null
294
     */
295
    protected function getByUsernameOrEmail($usernameOrEmail, bool $archived = false)
296
    {
297
298
        return UserElement::find()
299
            ->where(
300
                [
301
                    'or',
302
                    ['username' => $usernameOrEmail],
303
                    ['email' => $usernameOrEmail]
304
                ]
305
            )
306
            ->addSelect(['users.password', 'users.passwordResetRequired'])
307
            ->status(null)
308
            ->archived($archived)
309
            ->one();
310
    }
311
}
312