Completed
Push — master ( d1c4a6...3cfe02 )
by Damian
02:02
created

RealMeService::generateSimpleSAMLSalt()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 14
rs 9.2
cc 4
eloc 8
nc 3
nop 0
1
<?php
2
class RealMeService extends Object
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
3
{
4
    /**
5
     * Current RealMe supported environments.
6
     */
7
    const ENV_MTS = 'mts';
8
    const ENV_ITE = 'ite';
9
    const ENV_PROD = 'prod';
10
11
    /**
12
     * the valid AuthN context values for each supported RealMe environment.
13
     */
14
    const AUTHN_LOW_STRENGTH = 'urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:LowStrength';
15
    const AUTHN_MOD_STRENTH = 'urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength';
16
    const AUTHN_MOD_MOBILE_SMS = 'urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength::OTP:Mobile:SMS';
17
    const AUTHN_MOD_TOKEN_SID = 'urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength::OTP:Token:SID';
18
19
    /**
20
     * @var ArrayData|null User data returned by RealMe. Provided by {@link self::ensureLogin()}.
21
     *
22
     * Data within this ArrayData is as follows:
23
     * - NameID:       ArrayData   Includes the UserFlt and associated formatting information
24
     * - UserFlt:      string      RealMe pseudonymous username / identity
25
     * - Attributes:   ArrayData   User attributes returned by RealMe
26
     * - Expire:       SS_Datetime The expiry date & time of this authentication session
27
     * - SessionIndex: string      Unique identifier used to identify a user with both IdP and SP for given user.
28
     */
29
    private static $user_data = null;
0 ignored issues
show
Unused Code introduced by
The property $user_data is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
30
31
    /**
32
     * @config
33
     * @var string The authentication source to use, which ultimately determines which RealMe environment is
34
     * authenticated against. This should be set by Config, and generally be different per environment (e.g. developer
35
     * environments would generally use 'realme-mts', UAT/staging sites might use 'realme-ite', and production sites
36
     * would use 'realme-prod'.
37
     */
38
    private static $auth_source_name = 'realme-mts';
0 ignored issues
show
Unused Code introduced by
The property $auth_source_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
39
40
    /**
41
     * @config
42
     * @var string The base url path that is passed through to SimpleSAMLphp. This should be relative to the web root,
43
     * and is passed through to SimpleSAMLphp's config.php for it to base all its URLs from. The default is
44
     * 'vendor/madmatt/simplesamlphp/www/', which implies that '//your-site-url.com/vendor/madmatt/simplesamlphp/www/' 
45
     * is routed through to the SimpleSAMLphp `www` directory.
46
     * @see RealMeSetupTask for more information on how this is configured
47
     */
48
    private static $simplesaml_base_url_path = 'vendor/madmatt/simplesamlphp/www/';
0 ignored issues
show
Unused Code introduced by
The property $simplesaml_base_url_path is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
49
50
    /**
51
     * @config
52
     * @var string The complete password that will be passed to SimpleSAMLphp for admin logins to the SimpleSAMLphp web
53
     * interface. If set to the default `null`, the @link self::findOrMakeSimpleSAMLPassword() will make a random
54
     * password which won't be accessible again later. If this value is set via the Config API, then it should be in
55
     * the format required by SimpleSAMLphp. To generate a password in this format, see the bin/pwgen.php file in the
56
     * SimpleSAMLphp base directory.
57
     * @see self::findOrMakeSimpleSAMLPassword()
58
     */
59
    private static $simplesaml_hashed_admin_password = null;
0 ignored issues
show
Unused Code introduced by
The property $simplesaml_hashed_admin_password is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
60
61
    /**
62
     * @config
63
     * @var string A 32-byte salt that is used by SimpleSAMLphp when signing content. Stored in SimpleSAMLphp's config
64
     * if required.
65
     * @see self::generateSimpleSAMLSalt()
66
     */
67
    private static $simplesaml_secret_salt = null;
0 ignored issues
show
Unused Code introduced by
The property $simplesaml_secret_salt is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
68
69
    /**
70
     * @config
71
     * @var array The RealMe environments that can be used. If this is changed, then the RealMeSetupTask would need to
72
     * be run again, and updated environment names would need to be put into the authsources.php and
73
     * saml20-idp-remote.php files.
74
     */
75
    private static $allowed_realme_environments = array(self::ENV_MTS, self::ENV_ITE, self::ENV_PROD);
0 ignored issues
show
Unused Code introduced by
The property $allowed_realme_environments is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
76
77
    /**
78
     * @config
79
     * @var array Stores the entity ID value for each supported RealMe environment. This needs to be setup prior to
80
     * running the `RealMeSetupTask` build task. For more information, see the module documentation. An entity ID takes
81
     * the form of a URL, e.g. https://www.agency.govt.nz/privacy-realm-name/application-name
82
     */
83
    private static $entity_ids = array(
0 ignored issues
show
Unused Code introduced by
The property $entity_ids is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
84
        self::ENV_MTS => null,
85
        self::ENV_ITE => null,
86
        self::ENV_PROD => null
87
    );
88
89
    /**
90
     * @config
91
     * @var array Stores the AuthN context values for each supported RealMe environment. This needs to be setup prior to
92
     * running the `RealMeSetupTask` build task. For more information, see the module documentation. An AuthN context
93
     * can be one of the following:
94
     *
95
     * Username and password only:
96
     * - urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:LowStrength
97
     *
98
     * Username, password, and any moderate strength second level of authenticator (RSA token, Google Auth, SMS)
99
     * - urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength
100
     *
101
     * The following two are less often used, and shouldn't be used unless there's a specific need.
102
     *
103
     * Username, password, and only SMS 2FA token
104
     * - urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength::OTP:Mobile:SMS
105
     *
106
     * Username, password, and only RSA 2FA token
107
     * - urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength::OTP:Token:SID
108
     */
109
    private static $authn_contexts = array(
0 ignored issues
show
Unused Code introduced by
The property $authn_contexts is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
110
        self::ENV_MTS => null,
111
        self::ENV_ITE => null,
112
        self::ENV_PROD => null
113
    );
114
115
    /**
116
     * @config $allowed_authn_context_list
117
     * @var $allowed_authn_context_list array
118
     *
119
     * A list of the valid authn context values supported for realme.
120
     */
121
    private static $allowed_authn_context_list = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_authn_context_list is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
122
        self::AUTHN_LOW_STRENGTH,
123
        self::AUTHN_MOD_STRENTH,
124
        self::AUTHN_MOD_MOBILE_SMS,
125
        self::AUTHN_MOD_TOKEN_SID
126
    );
127
128
129
    /**
130
     * @config
131
     * @var array Stores the proxy_host values used when creating the back-channel SoapClient connection to the RealMe
132
     * artifact resolution service. This can either be:
133
     * - null (indicating no proxy is required),
134
     * - a plain string (e.g. gateway.your-network.govt.nz),
135
     * - the name of an environment variable that can be called (via getenv()) to retrieve the proxy URL from
136
     *       (e.g. env:http_proxy). In this case, it is assumed that a full URL would exist in this environment variable
137
     *       (e.g. tcp://gateway.your-network.govt.nz:8080) as it is intended to be used to mimic how curl handles HTTP
138
     *       proxy (if you specify the http_proxy env-var, curl will automatically parse it as a full URL and use that
139
     *       for resolving all requests by default.
140
     */
141
    private static $backchannel_proxy_hosts = array(
0 ignored issues
show
Unused Code introduced by
The property $backchannel_proxy_hosts is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
142
        self::ENV_MTS => null,
143
        self::ENV_ITE => null,
144
        self::ENV_PROD => null
145
    );
146
147
    /**
148
     * @config
149
     * @var array Stores the proxy_port values used when creating the back-channel SoapClient connection to the RealMe
150
     * artifact resolution service.
151
     *
152
     * See the definition for self::$backchannel_proxy_hosts for more information on the
153
     * valid values.
154
     */
155
    private static $backchannel_proxy_ports = array(
0 ignored issues
show
Unused Code introduced by
The property $backchannel_proxy_ports is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
156
        self::ENV_MTS => null,
157
        self::ENV_ITE => null,
158
        self::ENV_PROD => null
159
    );
160
161
    /**
162
     * @config
163
     * @var array Domain names for metadata files. Used in @link RealMeSetupTask when outputting metadata XML
164
     */
165
    private static $metadata_assertion_service_domains = array(
0 ignored issues
show
Unused Code introduced by
The property $metadata_assertion_service_domains is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
166
        self::ENV_MTS => null,
167
        self::ENV_ITE => null,
168
        self::ENV_PROD => null
169
    );
170
171
    /**
172
     * @config
173
     * @var string|null The organisation name to be used in metadata XML that is submitted to RealMe
174
     */
175
    private static $metadata_organisation_name = null;
0 ignored issues
show
Unused Code introduced by
The property $metadata_organisation_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
176
177
    /**
178
     * @config
179
     * @var string|null The organisation display name to be used in metadata XML that is submitted to RealMe
180
     */
181
    private static $metadata_organisation_display_name = null;
0 ignored issues
show
Unused Code introduced by
The property $metadata_organisation_display_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
182
183
    /**
184
     * @config
185
     * @var string|null The organisation URL to be used in metadata XML that is submitted to RealMe
186
     */
187
    private static $metadata_organisation_url = null;
0 ignored issues
show
Unused Code introduced by
The property $metadata_organisation_url is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
188
189
    /**
190
     * @config
191
     * @var string|null The support contact's company name to be used in metadata XML that is submitted to RealMe
192
     */
193
    private static $metadata_contact_support_company = null;
0 ignored issues
show
Unused Code introduced by
The property $metadata_contact_support_company is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
194
195
    /**
196
     * @config
197
     * @var string|null The support contact's first name(s) to be used in metadata XML that is submitted to RealMe
198
     */
199
    private static $metadata_contact_support_firstnames = null;
0 ignored issues
show
Unused Code introduced by
The property $metadata_contact_support_firstnames is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
200
201
    /**
202
     * @config
203
     * @var string|null The support contact's surname to be used in metadata XML that is submitted to RealMe
204
     */
205
    private static $metadata_contact_support_surname = null;
0 ignored issues
show
Unused Code introduced by
The property $metadata_contact_support_surname is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
206
207
    /**
208
     * @return bool true if the user is correctly authenticated, false if there was an error with login
209
     * NB: If the user is not authenticated, they will be redirected to RealMe to login, so a boolean false return here
210
     * indicates that there was a failure during the authentication process (perhaps a communication issue)
211
     */
212
    public function enforceLogin()
213
    {
214
        $auth = new SimpleSAML_Auth_Simple($this->config()->auth_source_name);
215
216
        $auth->requireAuth(array(
217
            'ReturnTo' => '/Security/realme/acs',
218
            'ErrorURL' => '/Security/realme/error'
219
        ));
220
221
        $loggedIn = false;
222
        $authData = $this->getAuthData($auth);
223
224
        if (is_null($authData)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
225
            // no-op, $loggedIn stays false and no data is written
226
        } else {
227
            $this->config()->user_data = $authData;
228
            Session::set('RealMeSessionDataSerialized', serialize($authData));
229
            $loggedIn = true;
230
        }
231
        return $loggedIn;
232
    }
233
234
    /**
235
     * Clear the RealMe credentials from Session, and also remove SimpleSAMLphp session information.
236
     * @return void
237
     */
238
    public function clearLogin()
239
    {
240
        Session::clear('RealMeSessionDataSerialized');
241
        $this->config()->__set('user_data', null);
242
243
        $session = SimpleSAML_Session::getSessionFromRequest();
244
245
        if ($session instanceof SimpleSAML_Session) {
246
            $session->doLogout($this->config()->auth_source_name);
247
        }
248
    }
249
250
    /**
251
     * Return the user data which was saved to session from the first RealMe auth.
252
     * Note: Does not check authenticity or expiry of this data
253
     *
254
     * @return ArrayData
255
     */
256
    public function getUserData()
257
    {
258
        if (is_null($this->config()->user_data)) {
259
            $sessionData = Session::get('RealMeSessionDataSerialized');
260
261
            if (!is_null($sessionData) && unserialize($sessionData) !== false) {
262
                $this->config()->user_data = unserialize($sessionData);
263
            }
264
        }
265
266
        return $this->config()->user_data;
267
    }
268
269
    /**
270
     * @param SimpleSAML_Auth_Simple $auth The authentication context as returned from RealMe
271
     * @return ArrayData
272
     */
273
    private function getAuthData(SimpleSAML_Auth_Simple $auth)
274
    {
275
        // returns null if the current auth is invalid or timed out.
276
        $data = $auth->getAuthDataArray();
277
        $returnedData = null;
278
279
        if (
280
            is_array($data) &&
281
            isset($data['saml:sp:IdP']) &&
282
            isset($data['saml:sp:NameID']) &&
283
            is_array($data['saml:sp:NameID']) &&
284
            isset($data['saml:sp:NameID']['Value']) &&
285
            isset($data['Expire']) &&
286
            isset($data['Attributes']) &&
287
            isset($data['saml:sp:SessionIndex'])
288
        ) {
289
            $returnedData = new ArrayData(array(
290
                'NameID' => new ArrayData($data['saml:sp:NameID']),
291
                'UserFlt' => $data['saml:sp:NameID']['Value'],
292
                'Attributes' => new ArrayData($data['Attributes']),
293
                'Expire' => $data['Expire'],
294
                'SessionIndex' => $data['saml:sp:SessionIndex']
295
            ));
296
        }
297
        return $returnedData;
298
    }
299
300
    /**
301
     * @return string A BackURL as specified originally when accessing /Security/login, for use after authentication
302
     */
303
    public function getBackURL()
0 ignored issues
show
Coding Style introduced by
getBackURL uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
304
    {
305
        if (!empty($_REQUEST['BackURL'])) {
306
            $url = $_REQUEST['BackURL'];
307
        } elseif (Session::get('BackURL')) {
308
            $url = Session::get('BackURL');
309
            Session::clear('BackURL'); // Ensure we don't redirect back to the same error twice
310
        }
311
312
        if (isset($url) && Director::is_site_url($url)) {
313
            $url = Director::absoluteURL($url);
314
        } else {
315
            // Spoofing attack or no back URL set, redirect to homepage instead of spoofing url
316
            $url = Director::absoluteBaseURL();
317
        }
318
319
        return $url;
320
    }
321
322
    /**
323
     * @return string|null Either the directory where SimpleSAMLphp configuration is stored, or null if undefined
324
     */
325
    public function getSimpleSamlConfigDir()
326
    {
327
        return (defined('REALME_CONFIG_DIR') ? rtrim(REALME_CONFIG_DIR, '/') : null);
328
    }
329
330
    /**
331
     * @return string The path to SimpleSAMLphp's metadata. This will either be defined in config, or just '/metadata'
332
     */
333
    public function getSimpleSamlMetadataDir()
334
    {
335
        return sprintf('%s/metadata', $this->getSimpleSamlConfigDir());
336
    }
337
338
    /**
339
     * @return string Either the value for baseurlpath in SimpleSAML's config, or a default value if it's been unset
340
     */
341
    public function getSimpleSamlBaseUrlPath()
342
    {
343
        if (strlen($this->config()->simplesaml_base_url_path) > 0) {
344
            return $this->config()->simplesaml_base_url_path;
345
        } else {
346
            return 'simplesaml/';
347
        }
348
    }
349
350
    /**
351
     * @return string|null Either the directory where certificates are stored, or null if undefined
352
     */
353
    public function getCertDir()
354
    {
355
        return (defined('REALME_CERT_DIR') ? REALME_CERT_DIR : null);
356
    }
357
358
    /**
359
     * @return string|null Either the directory where logging information is kept by SimpleSAMLphp, or null if undefined
360
     */
361
    public function getLoggingDir()
362
    {
363
        return (defined('REALME_LOG_DIR') ? REALME_LOG_DIR : null);
364
    }
365
366
    /**
367
     * @return string|null Either the directory where temp files can be written by SimpleSAMLphp, or null if undefined
368
     */
369
    public function getTempDir()
370
    {
371
        return (defined('REALME_TEMP_DIR') ? REALME_TEMP_DIR : null);
372
    }
373
374
    /**
375
     * This looks first to a Config variable that can be set in YML configuration, and falls back to generating a
376
     * salted SHA256-hashed password. To generate a password in this format, see the bin/pwgen.php file in the
377
     * SimpleSAMLphp vendor directory (normally vendor/madmatt/simplesamlphp/bin/pwgen.php). If setting a password
378
     * via Config, ensure it contains {SSHA256} at the start of the line.
379
     *
380
     * @return string|null The administrator password set for SimpleSAMLphp. If null, it means a strong hash couldn't be
381
     * created due to the code being deployed on an older machine, and a generated password will need to be set.
382
     */
383
    public function findOrMakeSimpleSAMLPassword()
384
    {
385
        if (strlen($this->config()->simplesaml_hashed_admin_password) > 0) {
386
            $password = $this->config()->simplesaml_hashed_admin_password;
387
388
            if (strpos($password, '{SSHA256}') !== 0) {
389
                $password = null; // Ensure password is salted SHA256
390
            }
391
        } else {
392
            $salt = openssl_random_pseudo_bytes(8, $strongSalt); // SHA256 needs 8 bytes
393
            $password = openssl_random_pseudo_bytes(32, $strongPassword); // Make a random 32-byte password
394
395
            if (!$strongSalt || !$strongPassword || !$salt || !$password) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $strongSalt 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...
Bug Best Practice introduced by
The expression $strongPassword 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...
396
                $password = null; // Ensure the password is strong, return null if we can't guarantee a strong one
397
            } else {
398
                $hash = hash('sha256', $password.$salt, true);
399
                $password = sprintf('{SSHA256}%s', base64_encode($hash.$salt));
400
            }
401
        }
402
403
        return $password;
404
    }
405
406
    /**
407
     * @return string A 32-byte salt string for SimpleSAML to use when signing content
408
     */
409
    public function generateSimpleSAMLSalt()
410
    {
411
        if (strlen($this->config()->simplesaml_secret_salt) > 0) {
412
            $salt = $this->config()->simplesaml_secret_salt;
413
        } else {
414
            $salt = base64_encode(openssl_random_pseudo_bytes(32, $strongSalt));
415
416
            if (!$salt || !$strongSalt) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $strongSalt 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...
417
                $salt = null; // Ensure salt is strong, return null if we can't generate a strong one
418
            }
419
        }
420
421
        return $salt;
422
    }
423
424
    /**
425
     * Returns the appropriate entity ID for RealMe, given the environment passed in. The entity ID may be different per
426
     * environment, and should be a full URL, including privacy realm and application name. For example, this may be:
427
     * https://www.agency.govt.nz/privacy-realm-name/application-name
428
     *
429
     * @param string $env The environment to return the entity ID for. Must be one of the RealMe environment names
430
     * @return string|null Returns the entity ID for the given $env, or null if no entity ID exists
431
     */
432
    public function getEntityIDForEnvironment($env)
433
    {
434
        return $this->getConfigurationVarByEnv('entity_ids', $env);
435
    }
436
437
    /**
438
     * Returns the appropriate AuthN Context, given the environment passed in. The AuthNContext may be different per
439
     * environment, and should be one of the strings as defined in the static {@link self::$authn_contexts} at the top
440
     * of this class.
441
     *
442
     * @param string $env The environment to return the AuthNContext for. Must be one of the RealMe environment names
443
     * @return string|null Returns the AuthNContext for the given $env, or null if no context exists
444
     */
445
    public function getAuthnContextForEnvironment($env)
446
    {
447
        return $this->getConfigurationVarByEnv('authn_contexts', $env);
448
    }
449
450
    /**
451
     * Gets the proxy host (if required) for back-channel SOAP requests. The proxy host can begin with the string 'env:'
452
     * in which case the script will call getenv() on the returned value and attempt to parse it as a full URL. This is
453
     * designed primarily to be compatible with the 'http_proxy' that curl uses by default. In other words, passing in
454
     * `env:http_proxy` is the equivalent of saying 'use the same HTTP proxy that curl will use in this environment'.
455
     *
456
     * @param string $env The environment to return the proxy_host for. Must be one of the RealMe environment names
457
     * @return string|null Returns the SOAPClient `proxy_host` param, or null if there isn't one
458
     */
459 View Code Duplication
    public function getProxyHostForEnvironment($env)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460
    {
461
        $host = $this->getConfigurationVarByEnv('backchannel_proxy_hosts', $env);
462
463
        // Allow usage of an environment variable to define this
464
        if (substr($host, 0, 4) === 'env:') {
465
            $host = getenv(substr($host, 4));
466
467
            if ($host === false) {
468
                // getenv() didn't return a valid environment var, it's either mis-spelled or doesn't exist
469
                $host = null;
470
            } else {
471
                $host = parse_url($host, PHP_URL_HOST);
472
473
                // This may happen on seriously malformed URLs, in which case we should return null
474
                if ($host === false) {
475
                    $host = null;
476
                }
477
            }
478
        }
479
480
        return $host;
481
    }
482
483
    /**
484
     * Gets the proxy port (if required) for back-channel SOAP requests. The proxy port can begin with the string 'env:'
485
     * in which case the script will call getenv() on the returned value and attempt to parse it as a full URL. This is
486
     * designed primarily to be compatible with the 'http_proxy' that curl uses by default. In other words, passing in
487
     * `env:http_proxy` is the equivalent of saying 'use the same HTTP proxy that curl will use in this environment'.
488
     *
489
     * @param string $env The environment to return the proxy_port for. Must be one of the RealMe environment names
490
     * @return string|null Returns the SOAPClient `proxy_port` param, or null if there isn't one
491
     */
492 View Code Duplication
    public function getProxyPortForEnvironment($env)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
493
    {
494
        $port = $this->getConfigurationVarByEnv('backchannel_proxy_ports', $env);
495
496
        // Allow usage of an environment variable to define this
497
        if (substr($port, 0, 4) === 'env:') {
498
            $port = getenv(substr($port, 4));
499
500
            if($port === false) {
501
                // getenv() didn't return a valid environment var, it's either mis-spelled or doesn't exist
502
                $port = null;
503
            } else {
504
                $port = parse_url($port, PHP_URL_PORT);
505
506
                // This may happen on seriously malformed URLs, in which case we should return null
507
                if ($port === false) {
508
                    $port = null;
509
                }
510
            }
511
        }
512
513
        return $port;
514
    }
515
516
    /**
517
     * @param string $cfgName The static configuration value to get. This should be an array
518
     * @param string $env The environment to return the value for. Must be one of the RealMe environment names
519
     * @return string|null Returns the value as defined in $cfgName for the given environment, or null if none exist
520
     */
521
    private function getConfigurationVarByEnv($cfgName, $env)
522
    {
523
        $value = null;
524
525
        if (in_array($env, $this->getAllowedRealMeEnvironments())) {
526
            $values = $this->config()->$cfgName;
527
528
            if (is_array($values) && isset($values[$env])) {
529
                $value = $values[$env];
530
            }
531
        }
532
533
        return $value;
534
    }
535
536
    /**
537
     * Returns the full path to the SAML signing certificate file, used by SimpleSAMLphp to sign all messages sent to
538
     * RealMe.
539
     *
540
     * @return string|null Either the full path to the SAML signing certificate file, or null if it doesn't exist
541
     */
542
    public function getSigningCertPath()
543
    {
544
        return $this->getCertPath('SIGNING');
545
    }
546
547
    /**
548
     * Returns the full path to the mutual back-channel certificate file, used by SimpleSAMLphp to communicate securely
549
     * with RealMe when connecting to the RealMe Assertion Resolution Service (Artifact Resolver).
550
     *
551
     * @return string|null Either the full path to the SAML mutual certificate file, or null if it doesn't exist
552
     */
553
    public function getMutualCertPath()
554
    {
555
        return $this->getCertPath('MUTUAL');
556
    }
557
558
    /**
559
     * @param string $certName The certificate name, either 'SIGNING' or 'MUTUAL'
560
     * @return string|null Either the full path to the certificate file, or null if it doesn't exist
561
     * @see self::getSigningCertPathForEnvironment(), self::getMutualCertPathForEnvironment()
562
     */
563
    private function getCertPath($certName)
564
    {
565
        $certPath = null;
566
        $certDir = $this->getCertDir();
567
568
        if (in_array($certName, array('SIGNING', 'MUTUAL'))) {
569
            $constName = sprintf('REALME_%s_CERT_FILENAME', strtoupper($certName));
570
            if (defined($constName)) {
571
                $filename = constant($constName);
572
                $certPath = Controller::join_links($certDir, $filename);
573
            }
574
        }
575
576
        // Ensure the file exists, if it doesn't then set it to null
577
        if (!is_null($certPath) && !file_exists($certPath)) {
578
            $certPath = null;
579
        }
580
581
        return $certPath;
582
    }
583
584
    /**
585
     * Returns the password (if any) necessary to decrypt the signing cert specified by self::getSigningCertPath(). If
586
     * no password is set, then this method returns null. MTS certificates require a password, however generally the
587
     * certificates used for ITE and production don't need one.
588
     *
589
     * @return string|null Either the password, or null if there is no password.
590
     */
591
    public function getSigningCertPassword()
592
    {
593
        return (defined('REALME_SIGNING_CERT_PASSWORD') ? REALME_SIGNING_CERT_PASSWORD : null);
594
    }
595
596
    /**
597
     * Returns the password (if any) necessary to decrypt the mutual back-channel cert specified by
598
     * self::getSigningCertPath(). If no password is set, then this method returns null. MTS certificates require a
599
     * password, however generally the certificates used for ITE and production don't need one.
600
     *
601
     * @return string|null Either the password, or null if there is no password.
602
     */
603
    public function getMutualCertPassword()
604
    {
605
        return (defined('REALME_MUTUAL_CERT_PASSWORD') ? REALME_MUTUAL_CERT_PASSWORD : null);
606
    }
607
608
    /**
609
     * Returns the content of the SAML signing certificate. This is used by @link RealMeSetupTask to output metadata.
610
     * The metadata file requires just the certificate to be included, without the BEGIN/END CERTIFICATE lines
611
     * @return string|null The content of the signing certificate
612
     */
613
    public function getSigningCertContent()
614
    {
615
        $certPath = $this->getSigningCertPath();
616
        $certificate = null;
617
618
        if (!is_null($certPath)) {
619
            $certificateContents = file_get_contents($certPath);
620
621
            // This is a PEM key, and we need to extract just the certificate, stripping out the private key etc.
622
            // So we search for everything between '-----BEGIN CERTIFICATE-----' and '-----END CERTIFICATE-----'
623
            preg_match(
624
                '/-----BEGIN CERTIFICATE-----\n([^-]*)\n-----END CERTIFICATE-----/',
625
                $certificateContents,
626
                $matches
627
            );
628
629
            if (isset($matches) && is_array($matches) && isset($matches[1])) {
630
                $certificate = $matches[1];
631
            }
632
        }
633
634
        return $certificate;
635
    }
636
637
    /**
638
     * @param string $env The environment to return the entity ID for. Must be one of the RealMe environment names
639
     * @return string|null Either the assertion consumer service location, or null if information doesn't exist
640
     */
641
    public function getAssertionConsumerServiceUrlForEnvironment($env)
642
    {
643
        if (false === in_array($env, $this->getAllowedRealMeEnvironments())) {
644
            return null;
645
        }
646
647
        // Returns http://domain.govt.nz/vendor/madmatt/simplesamlphp/www/module.php/saml/sp/saml2-acs.php/realme-mts
648
        $domain = $this->getMetadataAssertionServiceDomainForEnvironment($env);
649
        if (false === filter_var($domain, FILTER_VALIDATE_URL)) {
650
            return null;
651
        }
652
653
        $basePath = $this->getSimpleSamlBaseUrlPath();
654
        $modulePath = 'module.php/saml/sp/saml2-acs.php/';
655
        $authSource = sprintf('realme-%s', $env);
656
        return Controller::join_links($domain, $basePath, $modulePath, $authSource);
657
    }
658
659
    /**
660
     * @param string $env The environment to return the domain name for. Must be one of the RealMe environment names
661
     * @return string|null Either the FQDN (e.g. https://www.realme-demo.govt.nz/) or null if none is specified
662
     */
663
    private function getMetadataAssertionServiceDomainForEnvironment($env)
664
    {
665
        return $this->getConfigurationVarByEnv('metadata_assertion_service_domains', $env);
666
    }
667
668
    /**
669
     * @return string|null The organisation name to be used in metadata XML output, or null if none exists
670
     */
671
    public function getMetadataOrganisationName()
672
    {
673
        $orgName = $this->config()->metadata_organisation_name;
674
        return (strlen($orgName) > 0) ? $orgName : null;
675
    }
676
677
    /**
678
     * @return string|null The organisation display name to be used in metadata XML output, or null if none exists
679
     */
680
    public function getMetadataOrganisationDisplayName()
681
    {
682
        $displayName = $this->config()->metadata_organisation_display_name;
683
        return (strlen($displayName) > 0) ? $displayName : null;
684
    }
685
686
    /**
687
     * @return string|null The organisation website URL to be used in metadata XML output, or null if none exists
688
     */
689
    public function getMetadataOrganisationUrl()
690
    {
691
        $url = $this->config()->metadata_organisation_url;
692
        return (strlen($url) > 0) ? $url: null;
693
    }
694
695
    /**
696
     * @return array The support contact details to be used in metadata XML output, with null values if they don't exist
697
     */
698
    public function getMetadataContactSupport()
699
    {
700
        $company = $this->config()->metadata_contact_support_company;
701
        $firstNames = $this->config()->metadata_contact_support_firstnames;
702
        $surname = $this->config()->metadata_contact_support_surname;
703
704
        return array(
705
            'company' => (strlen($company) > 0) ? $company : null,
706
            'firstNames' => (strlen($firstNames) > 0) ? $firstNames : null,
707
            'surname' => (strlen($surname) > 0) ? $surname : null
708
        );
709
    }
710
711
    /**
712
     * The list of RealMe environments that can be used. By default, we allow mts, ite and production.
713
     * @return array
714
     */
715
    public function getAllowedRealMeEnvironments()
716
    {
717
        return $this->config()->allowed_realme_environments;
718
    }
719
720
    /**
721
     * The list of valid realme AuthNContexts
722
     * @return array
723
     */
724
    public function getAllowedAuthNContextList()
725
    {
726
        return $this->config()->allowed_authn_context_list;
727
    }
728
}
729