Passed
Push — master ( 1b1a3a...b67f9d )
by Tim
02:17
created

Adfs   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 146
dl 0
loc 251
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
F metadata() 0 178 24
A prp() 0 32 5
A __construct() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\adfs\Controller;
6
7
use SAML2\Constants;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\Error as SspError;
11
use SimpleSAML\IdP;
12
use SimpleSAML\Locale\Translate;
13
use SimpleSAML\Logger;
14
use SimpleSAML\Module;
15
use SimpleSAML\Module\adfs\IdP\ADFS as ADFS_IDP;
16
use SimpleSAML\Metadata;
17
use SimpleSAML\Session;
18
use SimpleSAML\Utils;
19
use SimpleSAML\XHTML\Template;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpFoundation\StreamedResponse;
23
24
/**
25
 * Controller class for the adfs module.
26
 *
27
 * This class serves the adfs views available in the module.
28
 *
29
 * @package SimpleSAML\Module\adfs
30
 */
31
class Adfs
32
{
33
    /** @var \SimpleSAML\Configuration */
34
    protected Configuration $config;
35
36
    /** @var \SimpleSAML\Metadata\MetaDataStorageHandler */
37
    protected Metadata\MetaDataStorageHandler $metadata;
38
39
    /** @var \SimpleSAML\Session */
40
    protected Session $session;
41
42
    /** @var \SimpleSAML\Utils\Crypto */
43
    protected Utils\Crypto $cryptoUtils;
44
45
    /**
46
     * AdfsController constructor.
47
     *
48
     * @param \SimpleSAML\Configuration $config The configuration to use.
49
     * @param \SimpleSAML\Session $session The current user session.
50
     */
51
    public function __construct(Configuration $config, Session $session)
52
    {
53
        $this->config = $config;
54
        $this->metadata = Metadata\MetaDataStorageHandler::getMetadataHandler();
55
        $this->session = $session;
56
        $this->cryptoUtils = new Utils\Crypto();
57
    }
58
59
60
    /**
61
     * @param \Symfony\Component\HttpFoundation\Request $request
62
     * @return \Symfony\Component\HttpFoundation\Response|\SimpleSAML\XHTML\Template
63
     */
64
    public function metadata(Request $request): Response
65
    {
66
        if (!$this->config->getBoolean('enable.adfs-idp', false)) {
67
            throw new SspError\Error('NOACCESS');
68
        }
69
70
        // check if valid local session exists
71
        if ($this->config->getBoolean('admin.protectmetadata', false)) {
72
            $authUtils = new Utils\Auth();
73
            $authUtils->requireAdmin();
74
        }
75
76
        try {
77
            $idpentityid = $request->get('idpentityid') ?: $this->metadata->getMetaDataCurrentEntityID('adfs-idp-hosted');
78
            $idpmeta = $this->metadata->getMetaDataConfig($idpentityid, 'adfs-idp-hosted');
79
80
            $availableCerts = [];
81
            $keys = [];
82
            $certInfo = $this->cryptoUtils->loadPublicKey($idpmeta, false, 'new_');
83
84
            if ($certInfo !== null) {
85
                $availableCerts['new_idp.crt'] = $certInfo;
86
                $keys[] = [
87
                    'type'            => 'X509Certificate',
88
                    'signing'         => true,
89
                    'encryption'      => true,
90
                    'X509Certificate' => $certInfo['certData'],
91
                ];
92
                $hasNewCert = true;
93
            } else {
94
                $hasNewCert = false;
95
            }
96
97
            /** @var array $certInfo */
98
            $certInfo = $this->cryptoUtils->loadPublicKey($idpmeta, true);
99
            $availableCerts['idp.crt'] = $certInfo;
100
            $keys[] = [
101
                'type'            => 'X509Certificate',
102
                'signing'         => true,
103
                'encryption'      => ($hasNewCert ? false : true),
104
                'X509Certificate' => $certInfo['certData'],
105
            ];
106
107
            if ($idpmeta->hasValue('https.certificate')) {
108
                /** @var array $httpsCert */
109
                $httpsCert = $this->cryptoUtils->loadPublicKey($idpmeta, true, 'https.');
110
                Assert::keyExists($httpsCert, 'certData');
111
                $availableCerts['https.crt'] = $httpsCert;
112
                $keys[] = [
113
                    'type'            => 'X509Certificate',
114
                    'signing'         => true,
115
                    'encryption'      => false,
116
                    'X509Certificate' => $httpsCert['certData'],
117
                ];
118
            }
119
120
            $adfs_service_location = Module::getModuleURL('adfs') . '/idp/prp.php';
121
            $metaArray = [
122
                'metadata-set'        => 'adfs-idp-remote',
123
                'entityid'            => $idpentityid,
124
                'SingleSignOnService' => [
125
                    0 => [
126
                        'Binding'  => Constants::BINDING_HTTP_REDIRECT,
127
                        'Location' => $adfs_service_location
128
                    ]
129
                ],
130
                'SingleLogoutService' => [
131
                    0 => [
132
                        'Binding'  => Constants::BINDING_HTTP_REDIRECT,
133
                        'Location' => $adfs_service_location
134
                    ]
135
                ],
136
            ];
137
138
            if (count($keys) === 1) {
139
                $metaArray['certData'] = $keys[0]['X509Certificate'];
140
            } else {
141
                $metaArray['keys'] = $keys;
142
            }
143
144
            $metaArray['NameIDFormat'] = $idpmeta->getString(
145
                'NameIDFormat',
146
                Constants::NAMEID_TRANSIENT
147
            );
148
149
            if ($idpmeta->hasValue('OrganizationName')) {
150
                $metaArray['OrganizationName'] = $idpmeta->getLocalizedString('OrganizationName');
151
                $metaArray['OrganizationDisplayName'] = $idpmeta->getLocalizedString(
152
                    'OrganizationDisplayName',
153
                    $metaArray['OrganizationName']
154
                );
155
156
                if (!$idpmeta->hasValue('OrganizationURL')) {
157
                    throw new SspError\Exception('If OrganizationName is set, OrganizationURL must also be set.');
158
                }
159
                $metaArray['OrganizationURL'] = $idpmeta->getLocalizedString('OrganizationURL');
160
            }
161
162
            if ($idpmeta->hasValue('scope')) {
163
                $metaArray['scope'] = $idpmeta->getArray('scope');
164
            }
165
166
            if ($idpmeta->hasValue('EntityAttributes')) {
167
                $metaArray['EntityAttributes'] = $idpmeta->getArray('EntityAttributes');
168
            }
169
170
            if ($idpmeta->hasValue('UIInfo')) {
171
                $metaArray['UIInfo'] = $idpmeta->getArray('UIInfo');
172
            }
173
174
            if ($idpmeta->hasValue('DiscoHints')) {
175
                $metaArray['DiscoHints'] = $idpmeta->getArray('DiscoHints');
176
            }
177
178
            if ($idpmeta->hasValue('RegistrationInfo')) {
179
                $metaArray['RegistrationInfo'] = $idpmeta->getArray('RegistrationInfo');
180
            }
181
182
            $metaflat = '$metadata[' . var_export($idpentityid, true) . '] = ' . var_export($metaArray, true) . ';';
183
184
            $metaBuilder = new Metadata\SAMLBuilder($idpentityid);
185
            $metaBuilder->addSecurityTokenServiceType($metaArray);
186
            $metaBuilder->addOrganizationInfo($metaArray);
187
            $technicalContactEmail = $this->config->getString('technicalcontact_email', null);
188
            if ($technicalContactEmail && $technicalContactEmail !== '[email protected]') {
189
                $metaBuilder->addContact('technical', Utils\Config\Metadata::getContact([
190
                    'emailAddress' => $technicalContactEmail,
191
                    'name'         => $this->config->getString('technicalcontact_name', null),
192
                    'contactType'  => 'technical',
193
                ]));
194
            }
195
            $output_xhtml = array_key_exists('output', $_GET) && $_GET['output'] == 'xhtml';
196
            $metaxml = $metaBuilder->getEntityDescriptorText($output_xhtml);
197
            if (!$output_xhtml) {
198
                $metaxml = str_replace("\n", '', $metaxml);
199
            }
200
201
            // sign the metadata if enabled
202
            $metaxml = Metadata\Signer::sign($metaxml, $idpmeta->toArray(), 'ADFS IdP');
203
204
            if ($output_xhtml) {
205
                $t = new Template($this->config, 'metadata.twig', 'admin');
206
207
                $t->data['clipboard.js'] = true;
208
                $t->data['available_certs'] = $availableCerts;
209
                $certdata = [];
210
                foreach (array_keys($availableCerts) as $availableCert) {
211
                    $certdata[$availableCert]['name'] = $availableCert;
212
                    $certdata[$availableCert]['url'] = Module::getModuleURL('saml/idp/certs.php') .
213
                        '/' . $availableCert;
214
215
                    $certdata[$availableCert]['comment'] = '';
216
                }
217
                $t->data['certdata'] = $certdata;
218
                $t->data['headerString'] = Translate::noop('metadata_adfs-idp');
219
                $httpUtils = new Utils\HTTP();
220
                $t->data['metaurl'] = $httpUtils->getSelfURLNoQuery();
221
                $t->data['metadata'] = htmlspecialchars($metaxml);
222
                $t->data['metadataflat'] = htmlspecialchars($metaflat);
223
224
                return $t;
225
            } else {
226
                // make sure to export only the md:EntityDescriptor
227
                $i = strpos($metaxml, '<md:EntityDescriptor');
228
                $metaxml = substr($metaxml, $i ? $i : 0);
229
230
                // 22 = strlen('</md:EntityDescriptor>')
231
                $i = strrpos($metaxml, '</md:EntityDescriptor>');
232
                $metaxml = substr($metaxml, 0, $i ? $i + 22 : 0);
233
234
                $response = new Response();
235
                $response->headers->set('Content-Type', 'application/xml');
236
                $response->setContent($metaxml);
237
238
                return $response;
239
            }
240
        } catch (\Exception $exception) {
241
            throw new SspError\Error('METADATA', $exception);
242
        }
243
    }
244
245
246
    /**
247
     * @param \Symfony\Component\HttpFoundation\Request $request
248
     * @return \Symfony\Component\HttpFoundation\Response
249
     */
250
    public function prp(Request $request): Response
251
    {
252
        Logger::info('ADFS - IdP.prp: Accessing ADFS IdP endpoint prp');
253
254
        $idpEntityId = $this->metadata->getMetaDataCurrentEntityID('adfs-idp-hosted');
255
        $idp = IdP::getById('adfs:' . $idpEntityId);
256
257
        if ($request->query->has('wa')) {
258
            $wa = $request->get('wa');
259
            if ($wa === 'wsignout1.0') {
260
                return new StreamedResponse(
261
                    function () use ($idp) {
262
                        ADFS_IDP::receiveLogoutMessage($idp);
263
                    }
264
                );
265
            } elseif ($wa === 'wsignin1.0') {
266
                return ADFS_IDP::receiveAuthnRequest($request, $idp);
267
            }
268
            throw new SspError\BadRequest("Unsupported value for 'wa' specified in request.");
269
        } elseif ($request->query->has('assocId')) {
270
            // logout response from ADFS SP
271
            $assocId = $request->get('assocId'); // Association ID of the SP that sent the logout response
272
            $relayState = $request->get('relayState'); // Data that was sent in the logout request to the SP. Can be null
273
            $logoutError = null; // null on success, or an instance of a \SimpleSAML\Error\Exception on failure.
274
275
            return new StreamedResponse(
276
                function () use ($idp, /** @scrutinizer ignore-type */ $assocId, $relayState, $logoutError) {
277
                    $idp->handleLogoutResponse($assocId, $relayState, $logoutError);
0 ignored issues
show
Bug introduced by
It seems like $assocId can also be of type null; however, parameter $assocId of SimpleSAML\IdP::handleLogoutResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

277
                    $idp->handleLogoutResponse(/** @scrutinizer ignore-type */ $assocId, $relayState, $logoutError);
Loading history...
278
                }
279
            );
280
        }
281
        throw new SspError\BadRequest("Missing parameter 'wa' or 'assocId' in request.");
282
    }
283
}
284