Passed
Push — master ( cca6a3...d8d955 )
by Tim
04:33
created

PassiveIdP   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 92
c 1
b 0
f 0
dl 0
loc 296
rs 10
wmc 27

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getByState() 0 5 1
A getById() 0 9 2
A getLogoutHandler() 0 17 3
A authenticate() 0 3 1
A postAuth() 0 35 5
A postAuthProc() 0 16 2
A __construct() 0 21 4
A handleAuthenticationRequest() 0 28 5
A getConfig() 0 3 1
A getId() 0 3 1
A finishLogout() 0 4 1
A isAuthenticated() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\adfs\IdP;
6
7
use Exception;
8
use SimpleSAML\{Auth, Configuration, Error, Session, Utils};
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\IdP\{IFrameLogoutHandler, LogoutHandlerInterface, TraditionalLogoutHandler};
11
use SimpleSAML\Metadata\MetaDataStorageHandler;
12
use Symfony\Component\HttpFoundation\{RedirectResponse, Response};
13
14
use function call_user_func;
15
use function substr;
16
use function time;
17
use function var_export;
18
19
/**
20
 * IdP class.
21
 *
22
 * This class implements the various functions used by IdP.
23
 *
24
 * @package simplesamlphp/simplesamlphp-module-adfs
25
 */
26
27
class PassiveIdP
28
{
29
    /**
30
     * A cache for resolving IdP id's.
31
     *
32
     * @var array
33
     */
34
    private static array $idpCache = [];
35
36
    /**
37
     * The identifier for this IdP.
38
     *
39
     * @var string
40
     */
41
    private string $id;
42
43
    /**
44
     * The configuration for this IdP.
45
     *
46
     * @var \SimpleSAML\Configuration
47
     */
48
    private Configuration $config;
49
50
    /**
51
     * The global configuration.
52
     *
53
     * @var \SimpleSAML\Configuration
54
     */
55
    private Configuration $globalConfig;
56
57
    /**
58
     * Our authsource.
59
     *
60
     * @var \SimpleSAML\Auth\Simple
61
     */
62
    private Auth\Simple $authSource;
63
64
65
    /**
66
     * Initialize an IdP.
67
     *
68
     * @param \SimpleSAML\Configuration $config The configuration
69
     * @param string $id The identifier of this IdP.
70
     *
71
     * @throws \SimpleSAML\Error\Exception If the IdP is disabled or no such auth source was found.
72
     */
73
    private function __construct(Configuration $config, string $id)
74
    {
75
        $this->id = $id;
76
77
        $this->globalConfig = $config;
78
        $metadata = MetaDataStorageHandler::getMetadataHandler($this->globalConfig);
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\Metadata\Meta...r::getMetadataHandler() has too many arguments starting with $this->globalConfig. ( Ignorable by Annotation )

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

78
        /** @scrutinizer ignore-call */ 
79
        $metadata = MetaDataStorageHandler::getMetadataHandler($this->globalConfig);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
79
80
        if (substr($id, 0, 5) === 'adfs:') {
81
            if (!$this->globalConfig->getOptionalBoolean('enable.adfs-idp', false)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->globalConfig->get...nable.adfs-idp', false) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
82
                throw new Error\Exception('enable.adfs-idp disabled in config.php.');
83
            }
84
            $this->config = $metadata->getMetaDataConfig(substr($id, 5), 'adfs-idp-hosted');
85
        } else {
86
            throw new Exception("Protocol not implemented.");
87
        }
88
89
        $auth = $this->config->getString('passiveAuth');
90
        if (Auth\Source::getById($auth) !== null) {
91
            $this->authSource = new Auth\Simple($auth);
92
        } else {
93
            throw new Error\Exception('No such "' . $auth . '" auth source found.');
94
        }
95
    }
96
97
98
    /**
99
     * Retrieve the ID of this IdP.
100
     *
101
     * @return string The ID of this IdP.
102
     */
103
    public function getId(): string
104
    {
105
        return $this->id;
106
    }
107
108
109
    /**
110
     * Retrieve an IdP by ID.
111
     *
112
     * @param \SimpleSAML\Configuration $config The Configuration
113
     * @param string $id The identifier of the IdP.
114
     *
115
     * @return \SimpleSAML\Module\adfs\IdP\PassiveIdP The IdP.
116
     */
117
    public static function getById(Configuration $config, string $id): PassiveIdP
118
    {
119
        if (isset(self::$idpCache[$id])) {
120
            return self::$idpCache[$id];
121
        }
122
123
        $idp = new self($config, $id);
124
        self::$idpCache[$id] = $idp;
125
        return $idp;
126
    }
127
128
129
    /**
130
     * Retrieve the IdP "owning" the state.
131
     *
132
     * @param \SimpleSAML\Configuration $config The Configuration.
133
     * @param array &$state The state array.
134
     *
135
     * @return \SimpleSAML\Module\adfs\IdP\PassiveIdP The IdP.
136
     */
137
    public static function getByState(Configuration $config, array &$state): PassiveIdP
138
    {
139
        Assert::notNull($state['core:IdP']);
140
141
        return self::getById($config, $state['core:IdP']);
142
    }
143
144
145
    /**
146
     * Retrieve the configuration for this IdP.
147
     *
148
     * @return Configuration The configuration object.
149
     */
150
    public function getConfig(): Configuration
151
    {
152
        return $this->config;
153
    }
154
155
156
    /**
157
     * Is the current user authenticated?
158
     *
159
     * @return boolean True if the user is authenticated, false otherwise.
160
     */
161
    public function isAuthenticated(): bool
162
    {
163
        return $this->authSource->isAuthenticated();
164
    }
165
166
167
    /**
168
     * Called after authproc has run.
169
     *
170
     * @param array $state The authentication request state array.
171
     */
172
    public static function postAuthProc(array $state): void
173
    {
174
        Assert::isCallable($state['Responder']);
175
176
        if (isset($state['core:SP'])) {
177
            $session = Session::getSessionFromRequest();
178
            $session->setData(
179
                'core:idp-ssotime',
180
                $state['core:IdP'] . ';' . $state['core:SP'],
181
                time(),
182
                Session::DATA_TIMEOUT_SESSION_END,
183
            );
184
        }
185
186
        call_user_func($state['Responder'], $state);
187
        Assert::true(false);
188
    }
189
190
191
    /**
192
     * The user is authenticated.
193
     *
194
     * @param array $state The authentication request state array.
195
     *
196
     * @throws \SimpleSAML\Error\Exception If we are not authenticated.
197
     */
198
    public static function postAuth(array $state): Response
199
    {
200
        $idp = PassiveIdP::getByState(Configuration::getInstance(), $state);
201
202
        if (!$idp->isAuthenticated()) {
203
            throw new Error\Exception('Not authenticated.');
204
        }
205
206
        $state['Attributes'] = $idp->authSource->getAttributes();
207
208
        if (isset($state['SPMetadata'])) {
209
            $spMetadata = $state['SPMetadata'];
210
        } else {
211
            $spMetadata = [];
212
        }
213
214
        if (isset($state['core:SP'])) {
215
            $session = Session::getSessionFromRequest();
216
            $previousSSOTime = $session->getData('core:idp-ssotime', $state['core:IdP'] . ';' . $state['core:SP']);
217
            if ($previousSSOTime !== null) {
218
                $state['PreviousSSOTimestamp'] = $previousSSOTime;
219
            }
220
        }
221
222
        $idpMetadata = $idp->getConfig()->toArray();
223
224
        $pc = new Auth\ProcessingChain($idpMetadata, $spMetadata, 'idp');
225
226
        $state['ReturnCall'] = ['\SimpleSAML\Module\adfs\IdP\PassiveIdP', 'postAuthProc'];
227
        $state['Destination'] = $spMetadata;
228
        $state['Source'] = $idpMetadata;
229
230
        $pc->processState($state);
231
232
        return self::postAuthProc($state);
0 ignored issues
show
Bug introduced by
Are you sure the usage of self::postAuthProc($state) targeting SimpleSAML\Module\adfs\I...siveIdP::postAuthProc() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug Best Practice introduced by
The expression return self::postAuthProc($state) returns the type void which is incompatible with the type-hinted return Symfony\Component\HttpFoundation\Response.
Loading history...
233
    }
234
235
236
    /**
237
     * Authenticate the user.
238
     *
239
     * This function authenticates the user.
240
     *
241
     * @param array &$state The authentication request state.
242
     */
243
    private function authenticate(array &$state): Response
244
    {
245
        return $this->authSource->login($state);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->authSource->login($state) targeting SimpleSAML\Auth\Simple::login() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug Best Practice introduced by
The expression return $this->authSource->login($state) returns the type void which is incompatible with the type-hinted return Symfony\Component\HttpFoundation\Response.
Loading history...
246
    }
247
248
249
    /**
250
     * Process authentication requests.
251
     *
252
     * @param array &$state The authentication request state.
253
     */
254
    public function handleAuthenticationRequest(array &$state): Response
255
    {
256
        Assert::notNull($state['Responder']);
257
258
        $state['core:IdP'] = $this->id;
259
260
        if (isset($state['SPMetadata']['entityid'])) {
261
            $spEntityId = $state['SPMetadata']['entityid'];
262
        } elseif (isset($state['SPMetadata']['entityID'])) {
263
            $spEntityId = $state['SPMetadata']['entityID'];
264
        } else {
265
            $spEntityId = null;
266
        }
267
268
        $state['core:SP'] = $spEntityId;
269
        $state['IdPMetadata'] = $this->getConfig()->toArray();
270
        $state['ReturnCallback'] = ['\SimpleSAML\Module\adfs\IdP\PassiveIdP', 'postAuth'];
271
272
        try {
273
            return $this->authenticate($state);
274
        } catch (Error\Exception $e) {
275
            Auth\State::throwException($state, $e);
276
        } catch (Exception $e) {
277
            $e = new Error\UnserializableException($e);
278
            Auth\State::throwException($state, $e);
279
        }
280
281
        throw new Exception('Should never happen.');
282
    }
283
284
285
    /**
286
     * Find the logout handler of this IdP.
287
     *
288
     * @return \SimpleSAML\IdP\LogoutHandlerInterface The logout handler class.
289
     *
290
     * @throws \Exception If we cannot find a logout handler.
291
     */
292
    public function getLogoutHandler(): LogoutHandlerInterface
293
    {
294
        // find the logout handler
295
        $logouttype = $this->getConfig()->getOptionalString('logouttype', 'traditional');
296
        switch ($logouttype) {
297
            case 'traditional':
298
                $handler = TraditionalLogoutHandler::class;
299
                break;
300
            case 'iframe':
301
                $handler = IFrameLogoutHandler::class;
302
                break;
303
            default:
304
                throw new Error\Exception('Unknown logout handler: ' . var_export($logouttype, true));
305
        }
306
307
        /** @var \SimpleSAML\IdP\LogoutHandlerInterface */
308
        return new $handler($this);
309
    }
310
311
312
    /**
313
     * Finish the logout operation.
314
     *
315
     * This function will never return.
316
     *
317
     * @param array &$state The logout request state.
318
     */
319
    public function finishLogout(array &$state): Response
320
    {
321
        Assert::notNull($state['Responder']);
322
        return call_user_func($state['Responder'], $state);
323
    }
324
}
325