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.