Passed
Push — master ( 01f5e3...cf2bb8 )
by Marek
01:41
created

OMSAML2::getPublicKeyFromCertificate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace OMSAML2;
6
7
use Exception;
8
use RobRichards\XMLSecLibs\XMLSecurityKey;
9
use SAML2\Constants;
10
use SAML2\DOMDocumentFactory;
11
use SAML2\SignedElement;
12
use SAML2\XML\md\EntityDescriptor;
13
use SAML2\XML\md\IDPSSODescriptor;
14
15
/**
16
 * @package OMSAML2
17
 */
18
class OMSAML2
19
{
20
    protected static $idp_metadata_url = null;
21
    protected static $idp_metadata_contents = null;
22
23
    /**
24
     * Returns associative array of found Login URLs
25
     * keys are binding constants, such as Constants::BINDING_HTTP_POST
26
     *
27
     * @param EntityDescriptor|null $idp_descriptor
28
     * @return array
29
     * @throws Exception
30
     * @see Constants
31
     *
32
     */
33
    public static function extractSSOLoginUrls(?EntityDescriptor $idp_descriptor = null): array
34
    {
35
        return self::extractSSOUrls(false, $idp_descriptor);
36
    }
37
38
    /**
39
     * @param bool $extract_logout_urls
40
     * @param EntityDescriptor $idp_descriptor
41
     * @return array
42
     * @throws Exception
43
     */
44
    public static function extractSSOUrls(bool $extract_logout_urls = false, ?EntityDescriptor $idp_descriptor = null): array
45
    {
46
        if (empty($idp_descriptor)) {
47
            $idp_descriptor = self::getIdpDescriptor();
48
        }
49
50
        $idp_sso_descriptor = false;
51
        if ($idp_descriptor instanceof EntityDescriptor) {
0 ignored issues
show
introduced by
$idp_descriptor is always a sub-type of SAML2\XML\md\EntityDescriptor.
Loading history...
52
            foreach ($idp_descriptor->getRoleDescriptor() as $role_descriptor) {
53
                if ($role_descriptor instanceof IDPSSODescriptor) {
54
                    $idp_sso_descriptor = $role_descriptor;
55
                }
56
            }
57
        }
58
59
        $found = [];
60
61
        if ($idp_sso_descriptor instanceof IDPSSODescriptor) {
62
            foreach ($extract_logout_urls ? $idp_sso_descriptor->getSingleLogoutService() : $idp_sso_descriptor->getSingleSignOnService() as $descriptorType) {
63
                if (empty($descriptorType->getBinding())) {
64
                    continue;
65
                }
66
                $found[$descriptorType->getBinding()] = $descriptorType->getLocation();
67
            }
68
        }
69
70
        return $found;
71
    }
72
73
    /**
74
     * Returns EntityDescriptor instance or null, if metadata could not be fetched
75
     * throws exception in case of invalid or dangerous XML contents
76
     *
77
     * @param string|null $metadata_string
78
     * @return EntityDescriptor|null null if provided string or automatically retrieved string is empty
79
     * @throws Exception
80
     */
81
    public static function getIdpDescriptor(?string $metadata_string = null): ?EntityDescriptor
82
    {
83
        if (empty($metadata_string)) {
84
            $metadata_string = self::getIdPMetadataContents();
85
            if (empty($metadata_string)) {
86
                return null;
87
            }
88
        }
89
        $metadata_dom = DOMDocumentFactory::fromString($metadata_string);
90
        return new EntityDescriptor($metadata_dom->documentElement);
91
    }
92
93
    /**
94
     * Returns cached or freshly retrieved IdP metadata as a string, or null
95
     *
96
     * @param string|null $url
97
     * @return null|string
98
     * @throws Exception
99
     */
100
    public static function getIdPMetadataContents(?string $url = null): ?string
101
    {
102
        if (!empty($url)) {
103
            self::setIdPMetadataUrl($url);
104
        }
105
        if (empty(self::$idp_metadata_contents)) {
106
            if (empty(self::getIdPMetadataUrl())) {
107
                throw new Exception("IdP Metadata URL not yet configured");
108
            }
109
            $idp_metadata_contents_fresh = file_get_contents(self::getIdPMetadataUrl());
110
            self::setIdPMetadataContents($idp_metadata_contents_fresh);
111
        }
112
        return self::$idp_metadata_contents;
113
    }
114
115
    /**
116
     * Sets metadata content cache to provided string contents or null, if provided value is empty
117
     *
118
     * @param string $contents
119
     */
120
    public static function setIdPMetadataContents(?string $contents): void
121
    {
122
        $contents = trim($contents);
123
        self::$idp_metadata_contents = empty($contents) ? null : $contents;
124
    }
125
126
    /**
127
     * Retrieves currently configured IdP Metadata URL or null if current value is empty
128
     *
129
     * @return null|string
130
     */
131
    public static function getIdPMetadataUrl(): ?string
132
    {
133
        return empty(self::$idp_metadata_url) ? null : self::$idp_metadata_url;
134
    }
135
136
    /**
137
     * If provided URL is not string or is empty, will set null
138
     *
139
     * @param string $url
140
     */
141
    public static function setIdPMetadataUrl(string $url): void
142
    {
143
        $url = trim($url);
144
        if ($url != self::$idp_metadata_url) {
145
            // empty metadata contents cache if URL changes
146
            self::$idp_metadata_contents = null;
147
        }
148
        self::$idp_metadata_url = empty($url) ? null : $url;
149
    }
150
151
    /**
152
     * Reset state of whole component to default
153
     */
154
    public static function reset(): void
155
    {
156
        self::$idp_metadata_contents = null;
157
        self::$idp_metadata_url = null;
158
    }
159
160
    /**
161
     * Returns associative array of found Logout URLs
162
     * keys are binding constants, such as Constants::BINDING_HTTP_REDIRECT
163
     *
164
     * @param EntityDescriptor|null $idp_descriptor
165
     * @return array
166
     * @throws Exception
167
     * @see Constants
168
     *
169
     */
170
    public static function extractSSOLogoutUrls(?EntityDescriptor $idp_descriptor = null): array
171
    {
172
        return self::extractSSOUrls(true, $idp_descriptor);
173
    }
174
175
    /**
176
     * Validates signed element (metadata, auth-response, logout-response) signature
177
     *
178
     * @param XMLSecurityKey $publicKey
179
     * @param SignedElement|null $idp_descriptor if not provided, will be retrieved internally by configured URL
180
     * @return bool
181
     * @throws Exception
182
     */
183
    public static function validateMetadataSignature(XMLSecurityKey $publicKey, ?SignedElement $idp_descriptor = null): bool
184
    {
185
        if (empty($idp_descriptor)) {
186
            $idp_descriptor = self::getIdpDescriptor();
187
        }
188
189
        return $idp_descriptor->validate($publicKey);
190
    }
191
192
    /**
193
     * Creates public key for verifying RSA/SHA256 signature
194
     *
195
     * @param string $path absolute path to certificate file or URL from which it can be retrieved
196
     * @param string $algorithm
197
     * @param string $type
198
     * @return XMLSecurityKey
199
     * @throws Exception
200
     */
201
    public static function getPublicKeyFromCertificate(string $path, $algorithm = XMLSecurityKey::RSA_SHA256, $type = 'public'): XMLSecurityKey
202
    {
203
        $cert_data = file_get_contents($path);
204
        $key = new XMLSecurityKey($algorithm, ['type' => $type]);
205
        $key->loadKey($cert_data, false, true);
206
        return $key;
207
    }
208
209
    /**
210
     * @param string $path absolute path to key file or URL from which it can be retrieved
211
     * @param string $algorithm
212
     * @param string $type
213
     * @return XMLSecurityKey
214
     * @throws Exception
215
     */
216
    public static function getPrivateKeyFromFile(string $path, $algorithm = XMLSecurityKey::RSA_SHA256, $type = 'private'): XMLSecurityKey
217
    {
218
        $key_data = file_get_contents($path);
219
        $key = new XMLSecurityKey($algorithm, ['type' => $type]);
220
        $key->loadKey($key_data, false, false);
221
        return $key;
222
    }
223
}