Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like RealMeService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use RealMeService, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
2 | class RealMeService extends Object |
||
|
|||
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; |
||
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'; |
||
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/'; |
||
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; |
||
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; |
||
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); |
||
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( |
||
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( |
||
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( |
||
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( |
||
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( |
||
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( |
||
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; |
||
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; |
||
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; |
||
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; |
||
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; |
||
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; |
||
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() |
||
233 | |||
234 | /** |
||
235 | * Clear the RealMe credentials from Session, and also remove SimpleSAMLphp session information. |
||
236 | * @return void |
||
237 | */ |
||
238 | public function clearLogin() |
||
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() |
||
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) |
||
299 | |||
300 | /** |
||
301 | * @return string A BackURL as specified originally when accessing /Security/login, for use after authentication |
||
302 | */ |
||
303 | public function getBackURL() |
||
321 | |||
322 | /** |
||
323 | * @return string|null Either the directory where SimpleSAMLphp configuration is stored, or null if undefined |
||
324 | */ |
||
325 | public function getSimpleSamlConfigDir() |
||
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() |
||
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() |
||
349 | |||
350 | /** |
||
351 | * @return string|null Either the directory where certificates are stored, or null if undefined |
||
352 | */ |
||
353 | public function getCertDir() |
||
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() |
||
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() |
||
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() |
||
405 | |||
406 | /** |
||
407 | * @return string A 32-byte salt string for SimpleSAML to use when signing content |
||
408 | */ |
||
409 | public function generateSimpleSAMLSalt() |
||
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) |
||
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) |
||
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) |
|
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) |
|
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) |
||
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() |
||
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() |
||
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) |
||
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() |
||
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() |
||
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() |
||
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) |
||
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) |
||
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() |
||
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() |
||
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() |
||
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() |
||
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() |
||
719 | |||
720 | /** |
||
721 | * The list of valid realme AuthNContexts |
||
722 | * @return array |
||
723 | */ |
||
724 | public function getAllowedAuthNContextList() |
||
728 | } |
||
729 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.