LoginController::throwIdpNotFoundWithUid()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 10
cp 0
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: dsmrt
5
 * Date: 1/10/18
6
 * Time: 11:52 AM
7
 */
8
9
namespace flipbox\saml\sp\controllers;
10
11
use Craft;
12
use flipbox\saml\core\controllers\messages\AbstractController;
13
use flipbox\saml\core\exceptions\InvalidMetadata;
14
use flipbox\saml\core\helpers\MessageHelper;
15
use flipbox\saml\core\services\bindings\Factory;
16
use flipbox\saml\core\validators\Response as ResponseValidator;
17
use flipbox\saml\sp\events\RelayState;
18
use flipbox\saml\sp\records\ProviderRecord;
19
use flipbox\saml\sp\Saml;
20
use flipbox\saml\sp\traits\SamlPluginEnsured;
21
use SAML2\AuthnRequest;
22
use SAML2\Response as SamlResponse;
23
use yii\base\Event;
24
use yii\web\HttpException;
25
26
class LoginController extends AbstractController
27
{
28
    use SamlPluginEnsured;
29
30
    /**
31
     * Happens before the RelayState is used to redirect the user to where they were
32
     * initially trying to go.
33
     */
34
    const EVENT_BEFORE_RELAYSTATE_REDIRECT = 'eventBeforeRelayStateRedirect';
35
    /**
36
     * Happens after the RelayState is created and before the AuthNRequest
37
     * is sent off to the IdP. Use this event if you want to modify the
38
     * RelyState before it's sent to the IdP.
39
     */
40
    const EVENT_AFTER_RELAYSTATE_CREATION = 'eventBeforeRelayStateCreation';
41
42
    protected $allowAnonymous = [
43
        'actionIndex',
44
        'actionRequest',
45
    ];
46
47
    public $enableCsrfValidation = false;
48
49
    /**
50
     * @param \yii\base\Action $action
51
     * @return bool
52
     */
53
    public function beforeAction($action)
54
    {
55
        if ($action->actionMethod === 'actionIndex' || $action->actionMethod === 'actionRequest') {
56
            return true;
57
        }
58
59
        return parent::beforeAction($action);
60
    }
61
62
    /**
63
     * @return \yii\web\Response
64
     * @throws HttpException
65
     * @throws \Exception
66
     * @throws \Throwable
67
     * @throws \craft\errors\ElementNotFoundException
68
     * @throws \flipbox\saml\core\exceptions\InvalidMessage
69
     * @throws \yii\base\Exception
70
     * @throws \yii\base\UserException
71
     */
72
    public function actionIndex()
73
    {
74
75
        /** @var SamlResponse $response */
76
        $response = Factory::receive();
77
78
        if (! $identityProvider = Saml::getInstance()->getProvider()->findByEntityId(
79
            MessageHelper::getIssuer($response->getIssuer())
80
        )->one()) {
81
            $this->throwIdpNotFoundWithResponse($response);
82
        }
83
84
        if (! $serviceProvider = Saml::getInstance()->getProvider()->findOwn()) {
85
            $this->throwSpNotFound();
86
        }
87
88
        $validator = new ResponseValidator(
89
            $identityProvider,
90
            $serviceProvider
91
        );
92
93
        $validator->validate($response);
94
        $settings = Saml::getInstance()->getSettings();
95
        // Transform to User START!
96
        Saml::getInstance()->getLogin()->transformToUser(
97
            $user = Saml::getInstance()->getUser()->getByResponse(
98
                $response,
99
                $serviceProvider,
100
                $identityProvider,
101
                $settings
102
            ),
103
            $response,
104
            $identityProvider,
105
            $serviceProvider,
106
            $settings
107
        );
108
        // Transform to User END!
109
110
        // User Group Start
111
        Saml::getInstance()->getUserGroups()->sync(
112
            $user,
113
            $response,
114
            $serviceProvider,
115
            $settings
116
        );
117
        // User Group End
118
119
        // Identity START
120
        $identity = Saml::getInstance()->getProviderIdentity()->getByUserAndResponse(
121
            $user,
122
            $response,
123
            $serviceProvider,
124
            $identityProvider
125
        );
126
127
        // LOGIN
128
        Saml::getInstance()->getLogin()->byIdentity($identity);
129
        // Identity END
130
131
        //get relay state but don't error!
132
        $relayState = $response->getRelayState() ?: \Craft::$app->request->getParam('RelayState');
133
        try {
134
            $redirect = $relayState;
135
            if (Saml::getInstance()->getSettings()->encodeRelayState) {
136
                $redirect = base64_decode($relayState);
137
            }
138
139
            Saml::info('RelayState: ' . $redirect);
140
        } catch (\Exception $e) {
141
            $redirect = \Craft::$app->getUser()->getReturnUrl();
0 ignored issues
show
Bug introduced by
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
142
        }
143
144
        Event::trigger(
145
            self::class,
146
            self::EVENT_BEFORE_RELAYSTATE_REDIRECT,
147
            $event = new RelayState([
148
                'idp' => $identityProvider,
149
                'sp' => $serviceProvider,
150
                'relayState' => $relayState,
151
                'redirect' => $redirect,
152
            ])
153
        );
154
155
        Craft::$app->user->removeReturnUrl();
156
        return $this->redirect($event->redirect);
157
    }
158
159
    /**
160
     * @param SamlResponse $response
161
     * @throws HttpException
162
     */
163
    protected function throwIdpNotFoundWithResponse(SamlResponse $response)
164
    {
165
        throw new HttpException(
166
            400,
167
            sprintf(
168
                'Identity Provider is not found. Possibly a configuration problem. Issuer/EntityId: %s',
169
                MessageHelper::getIssuer($response->getIssuer()) ?: 'IS NULL'
170
            )
171
        );
172
    }
173
174
    /**
175
     * @param SamlResponse $response
0 ignored issues
show
Bug introduced by
There is no parameter named $response. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
176
     * @throws HttpException
177
     */
178
    protected function throwIdpNotFoundWithUid($uid)
179
    {
180
        throw new HttpException(
181
            400,
182
            sprintf(
183
                'Identity Provider is not found with UID: %s',
184
                $uid
185
            )
186
        );
187
    }
188
189
    /**
190
     * @throws HttpException
191
     * @throws \craft\errors\SiteNotFoundException
192
     */
193
    protected function throwSpNotFound()
194
    {
195
        throw new HttpException(
196
            400,
197
            sprintf(
198
                'Service Provider is not found. Possibly a configuration problem. My Provider/Current EntityId: %s',
199
                Saml::getInstance()->getSettings()->getEntityId() ?: 'IS NULL'
200
            )
201
        );
202
    }
203
204
205
    /**
206
     * @param null $uid
207
     * @throws HttpException
208
     * @throws InvalidMetadata
209
     * @throws \craft\errors\SiteNotFoundException
210
     * @throws \yii\base\ExitException
211
     * @throws \yii\base\InvalidConfigException
212
     */
213
    public function actionRequest($uid = null)
214
    {
215
        //build uid condition
216
        $uidCondition = [];
217
        if ($uid) {
218
            $uidCondition = [
219
                'uid' => $uid,
220
            ];
221
        }
222
223
        /**
224
         * @var ProviderRecord $idp
225
         */
226
        if (! $idp = Saml::getInstance()->getProvider()->findByIdp(
227
            $uidCondition
228
        )->one()
229
        ) {
230
            $this->throwIdpNotFoundWithUid($uid);
231
        }
232
233
        if (! $sp = Saml::getInstance()->getProvider()->findOwn()) {
234
            $this->throwSpNotFound();
235
        }
236
237
        /**
238
         * @var $authnRequest AuthnRequest
239
         */
240
        $authnRequest = Saml::getInstance()->getAuthnRequest()->create(
241
            $sp,
242
            $idp
243
        );
244
245
        /**
246
         * Extra layer of security, save the id and check it on the return.
247
         */
248
        Saml::getInstance()->getSession()->setRequestId(
249
            $authnRequest->getID()
250
        );
251
252
253
        // Grab the return URL, or use a param sent as a default
254
        // TODO - seems like if there's a parameter sent, that should be used as an override.
255
        $relayState = Craft::$app->getUser()->getReturnUrl(
0 ignored issues
show
Bug introduced by
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
256
        // Is RelayState set on this request?
257
        // You can override the param within settings
258
            Craft::$app->request->getParam(
259
                Saml::getInstance()->getSettings()->relayStateOverrideParam,
260
                null
261
            )
262
        );
263
        if (Saml::getInstance()->getSettings()->encodeRelayState) {
264
            $relayState = base64_encode($relayState);
265
        }
266
267
        Event::trigger(
268
            self::class,
269
            self::EVENT_AFTER_RELAYSTATE_CREATION,
270
            $event = new RelayState([
271
                'idp' => $idp,
272
                'sp' => $sp,
273
                'relayState' => $relayState,
274
            ])
275
        );
276
277
        $authnRequest->setRelayState(
278
            $event->relayState
279
        );
280
281
        Factory::send(
282
            $authnRequest,
283
            $idp
284
        );
285
286
        Craft::$app->end();
287
    }
288
}
289