diff --git a/actions-services.yml b/actions-services.yml index df049f80..bb62a63f 100644 --- a/actions-services.yml +++ b/actions-services.yml @@ -122,6 +122,9 @@ services: ssp-idp2.local: build: . + depends_on: + - db + - broker volumes: # Utilize custom certs - ./development/idp2-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert @@ -142,6 +145,15 @@ services: ADMIN_PASS: "b" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 2" + IDP_DOMAIN_NAME: "ssp-idp1.local" + ID_BROKER_ACCESS_TOKEN: "test-cli-abc123" + ID_BROKER_ASSERT_VALID_IP: "true" + ID_BROKER_BASE_URI: "http://broker" + ID_BROKER_TRUSTED_IP_RANGES: "10.20.38.0/24" + MYSQL_HOST: "db" + MYSQL_DATABASE: "silauth" + MYSQL_USER: "silauth" + MYSQL_PASSWORD: "silauth" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" @@ -277,12 +289,19 @@ services: EMAIL_SIGNATURE: "one red pill, please" API_ACCESS_KEYS: "test-cli-abc123" APP_ENV: "prod" - MFA_TOTP_apiBaseUrl: not_needed_here - MFA_TOTP_apiKey: not_needed_here - MFA_TOTP_apiSecret: not_needed_here - MFA_WEBAUTHN_apiBaseUrl: not_needed_here - MFA_WEBAUTHN_apiKey: not_needed_here - MFA_WEBAUTHN_apiSecret: not_needed_here + RP_ORIGINS: "https://ssp-idp1.local,https://ssp-idp3.local,https://ssp-idp3.local" + HIBP_CHECK_ON_LOGIN: "false" + MFA_TOTP_apiBaseUrl: dummy + MFA_TOTP_apiKey: 10345678-1234-1234-1234-123456789012 + MFA_TOTP_apiSecret: 11345678-1234-1234-1234-12345678 + MFA_WEBAUTHN_apiBaseUrl: dummy + MFA_WEBAUTHN_apiKey: 10345678-1234-1234-1234-123456789012 + MFA_WEBAUTHN_apiSecret: 11345678-1234-1234-1234-12345678 + MFA_WEBAUTHN_appId: ourApp99 + MFA_WEBAUTHN_rpDisplayName: Our App + MFA_WEBAUTHN_rpId: http://app99 + volumes: + - ./development/m991231_235959_insert_test_users.php:/data/console/migrations/m991231_235959_insert_test_users.php command: "bash -c 'whenavail brokerDb 3306 60 ./yii migrate --interactive=0 && ./run.sh'" brokerDb: diff --git a/development/idp2-local/config/authsources.php b/development/idp2-local/config/authsources.php index 197f61b1..6195508b 100644 --- a/development/idp2-local/config/authsources.php +++ b/development/idp2-local/config/authsources.php @@ -1,5 +1,7 @@ ConfigManager::getSspConfig(), + 'example-userpass' => [ 'exampleauth:UserPass', diff --git a/development/idp2-local/metadata/saml20-idp-hosted.php b/development/idp2-local/metadata/saml20-idp-hosted.php index b8b7c7aa..e0d19e01 100644 --- a/development/idp2-local/metadata/saml20-idp-hosted.php +++ b/development/idp2-local/metadata/saml20-idp-hosted.php @@ -21,7 +21,7 @@ * Authentication source to use. Must be one that is configured in * 'config/authsources.php'. */ - 'auth' => 'example-userpass', + 'auth' => 'silauth', ]; // Copy configuration for port 80 and modify host. diff --git a/development/m991231_235959_insert_test_users.php b/development/m991231_235959_insert_test_users.php new file mode 100644 index 00000000..596351ca --- /dev/null +++ b/development/m991231_235959_insert_test_users.php @@ -0,0 +1,43 @@ +batchInsert('{{user}}', + ['id', 'employee_id', 'username', 'email', 'require_mfa', 'review_profile_after', 'nag_for_mfa_after', 'nag_for_method_after', 'manager_email', 'first_name', 'last_name', 'last_changed_utc', 'last_synced_utc', 'active', 'locked', 'uuid'], + $users); + + $nextYear = MySqlDateTime::relative('+1 year'); + $passwords = [1, 1, $now, $nextYear, $nextYear, password_hash('sildisco_password', PASSWORD_BCRYPT)]; + $this->batchInsert('{{password}}', + ['id', 'user_id', 'created_utc', 'expires_on', 'grace_period_ends_on', 'hash'], [ + $passwords, + ]); + + for ($i = 0; $i < count($users); $i++) { + $this->update('{{user}}', ['current_password_id' => $i], 'id=' . $i); + } + } + + public function safeDown() + { + $this->delete('{{email_log}}'); + $this->delete('{{mfa_backupcode}}'); + $this->delete('{{mfa_failed_attempt}}'); + $this->delete('{{mfa}}'); + $this->delete('{{method}}'); + $this->delete('{{user}}'); + $this->delete('{{password}}'); + + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 23856776..67077b01 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,7 +60,7 @@ services: - ./modules/silauth:/data/vendor/simplesamlphp/simplesamlphp/modules/silauth - ./modules/sildisco:/data/vendor/simplesamlphp/simplesamlphp/modules/sildisco - ./modules/material:/data/vendor/simplesamlphp/simplesamlphp/modules/material - command: ["/data/run-tests.sh"] + command: [ "/data/run-tests.sh" ] test-browser: image: justinribeiro/chrome-headless:stable @@ -127,7 +127,7 @@ services: HELP_CENTER_URL: "https://example.org/help" LOGGING_LEVEL: INFO - ssp-idp1.local: # using a database session store type ("sql") + ssp-idp1.local: # using a database session store type ("sql") build: . depends_on: - db @@ -191,6 +191,9 @@ services: ssp-idp2.local: build: . + depends_on: + - db + - broker volumes: # Utilize custom certs - ./development/idp2-local/cert:/data/vendor/simplesamlphp/simplesamlphp/cert @@ -220,8 +223,18 @@ services: ADMIN_PASS: "b" SECRET_SALT: "h57fjemb&dn^nsJFGNjweJ" IDP_NAME: "IDP 2" + IDP_DOMAIN_NAME: "ssp-idp2.local" + ID_BROKER_ACCESS_TOKEN: "test-cli-abc123" + ID_BROKER_ASSERT_VALID_IP: "true" + ID_BROKER_BASE_URI: "http://broker" + ID_BROKER_TRUSTED_IP_RANGES: "10.20.38.0/24" + MYSQL_HOST: "db" + MYSQL_DATABASE: "silauth" + MYSQL_USER: "silauth" + MYSQL_PASSWORD: "silauth" SECURE_COOKIE: "false" SHOW_SAML_ERRORS: "true" + RECAPTCHA_SITE_KEY: "0123456789abcdefghijklmnoABCDEFGHIJKLMNO" ssp-idp3.local: build: . @@ -377,7 +390,20 @@ services: EMAIL_SIGNATURE: "one red pill, please" API_ACCESS_KEYS: "test-cli-abc123" APP_ENV: "dev" - command: ["bash", "-c", "whenavail brokerDb 3306 60 ./yii migrate --interactive=0 && ./run.sh"] + RP_ORIGINS: "https://ssp-idp1.local,https://ssp-idp3.local,https://ssp-idp3.local" + HIBP_CHECK_ON_LOGIN: "false" + MFA_TOTP_apiBaseUrl: dummy + MFA_TOTP_apiKey: 10345678-1234-1234-1234-123456789012 + MFA_TOTP_apiSecret: 11345678-1234-1234-1234-12345678 + MFA_WEBAUTHN_apiBaseUrl: dummy + MFA_WEBAUTHN_apiKey: 10345678-1234-1234-1234-123456789012 + MFA_WEBAUTHN_apiSecret: 11345678-1234-1234-1234-12345678 + MFA_WEBAUTHN_appId: ourApp99 + MFA_WEBAUTHN_rpDisplayName: Our App + MFA_WEBAUTHN_rpId: http://app99 + volumes: + - ./development/m991231_235959_insert_test_users.php:/data/console/migrations/m991231_235959_insert_test_users.php + command: [ "bash", "-c", "whenavail brokerDb 3306 60 ./yii migrate --interactive=0 && ./run.sh" ] brokerDb: image: mariadb:10 @@ -389,6 +415,15 @@ services: MYSQL_USER: "user" MYSQL_PASSWORD: "pass" + brokerpma: + image: phpmyadmin/phpmyadmin + ports: + - "8088:80" + environment: + PMA_HOST: brokerDb + PMA_USER: user + PMA_PASSWORD: pass + dynamo: image: cnadiminti/dynamodb-local command: "-sharedDb -inMemory" diff --git a/features/material.feature b/features/material.feature index 050459c8..a4af7c06 100644 --- a/features/material.feature +++ b/features/material.feature @@ -12,19 +12,16 @@ Feature: Material theme And I should see our material theme # TODO: if this is really used, fix it. If not, delete the test, the template, and the translation file. + # (The reason this fails is because there is no "Logout" button on the new admin interface) # Scenario: Logout page # When I go to the Hub's home page -# And I click on "Authentication" -# And I click on "Test configured authentication sources" -# And I click on "admin" -# And I log in as a hub administrator -# And I click on "Logout" +# And I log in as a hub administrator +# And I click on "Logout" # Then I should see a "Logged out" page -# And I should see our material theme +# And I should see our material theme - # FIXME: this feature is especially difficult to fix because the core controller doesn't provide the IdP name. -# Scenario: Login page -# When I go to the SP1 login page -# And I click on the "IDP 2" tile -# Then I should see a "Login with your IDP 2 identity" page -# And I should see our material theme + Scenario: Login page + When I go to the SP1 login page + And I click on the "IDP 2" tile + Then I should see a "Login with your IDP 2 identity" page + And I should see our material theme diff --git a/modules/material/dictionaries/login.definition.json b/modules/material/dictionaries/login.definition.json deleted file mode 100644 index 9b040a32..00000000 --- a/modules/material/dictionaries/login.definition.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "title": { - "en": "Login with your {idpName} identity", - "es": "Inicie sesión con su identidad de {idpName}", - "fr": "Connectez-vous avec votre identité {idpName}", - "ko": "{idpName} 신원 계정으로 로그인하십시오." - }, - "header": { - "en": "Login with your {idpName} identity", - "es": "Inicie sesión con su identidad de {idpName}", - "fr": "Connectez-vous avec votre identité {idpName}", - "ko": "{idpName} 신원 계정으로 로그인하십시오." - }, - "label_username": { - "en": "Username", - "es": "Nombre de usuario", - "fr": "Nom d'utilisateur", - "ko": "사용자 이름" - }, - "label_password": { - "en": "Password", - "es": "Contraseña", - "fr": "Mot de passe", - "ko": "암호" - }, - "error_wronguserpass": { - "en": "Something is wrong with that username or password, please verify and try again.", - "es": "Algo está mal con ese nombre de usuario o contraseña, compruebe e inténtelo de nuevo.", - "fr": "Quelque chose ne va pas avec ce nom d'utilisateur ou ce mot de passe, veuillez vérifier et essayer à nouveau.", - "ko": "해당 사용자 이름 또는 비밀번호가 잘못되었습니다. 다시 확인하고 다시 시도하십시오." - }, - "button_login": { - "en": "Login", - "es": "Iniciar sesión", - "fr": "Connexion", - "ko": "로그인" - }, - "forgot": { - "en": "Forgot password?", - "es": "¿Se te olvidó tu contraseña?", - "fr": "Mot de passe oublié?", - "ko": "비밀번호를 잊으 셨나요?" - }, - "logo": { - "en": "{idpName} logo", - "es": "Logotipo de {idpName}", - "fr": "Logo {idpName}", - "ko": "{idpName} 로고" - }, - "help": { - "en": "I need help", - "es": "necesito ayuda", - "fr": "j'ai besoin d'aide", - "ko": "도움이 필요해." - }, - "profile": { - "en": "Manage my profile", - "es": "Administrar mi perfil", - "fr": "Gérer mon profil", - "ko": "내 프로필 관리" - } -} diff --git a/modules/material/locales/en/LC_MESSAGES/material.po b/modules/material/locales/en/LC_MESSAGES/material.po index dc68b431..f3c35e0d 100644 --- a/modules/material/locales/en/LC_MESSAGES/material.po +++ b/modules/material/locales/en/LC_MESSAGES/material.po @@ -44,6 +44,36 @@ msgstr "Change" msgid "{footer:copyright}" msgstr "Unauthorized use of this site is prohibited and may be subjected to civil and criminal prosecution." +msgid "{login:title}" +msgstr "Login with your %idpName% identity" + +msgid "{login:header}" +msgstr "Login with your %idpName% identity" + +msgid "{login:label_username}" +msgstr "Username" + +msgid "{login:label_password}" +msgstr "Password" + +msgid "{login:error_wronguserpass}" +msgstr "Something is wrong with that username or password, please verify and try again." + +msgid "{login:button_login}" +msgstr "Login" + +msgid "{login:forgot}" +msgstr "Forgot password?" + +msgid "{login:logo}" +msgstr "%idpName% logo" + +msgid "{login:help}" +msgstr "I need help" + +msgid "{login:profile}" +msgstr "Manage my profile" + msgid "{mfa:title}" msgstr "2-Step Verification" diff --git a/modules/material/locales/es/LC_MESSAGES/material.po b/modules/material/locales/es/LC_MESSAGES/material.po index 03d15a73..81eade8b 100644 --- a/modules/material/locales/es/LC_MESSAGES/material.po +++ b/modules/material/locales/es/LC_MESSAGES/material.po @@ -44,6 +44,36 @@ msgstr "Cambiar" msgid "{footer:copyright}" msgstr "El uso no autorizado de este sitio está prohibido y puede ser sometido a procesamiento civil y penal." +msgid "{login:title}" +msgstr "Inicie sesión con su identidad de %idpName%" + +msgid "{login:header}" +msgstr "Inicie sesión con su identidad de %idpName%" + +msgid "{login:label_username}" +msgstr "Nombre de usuario" + +msgid "{login:label_password}" +msgstr "Contraseña" + +msgid "{login:error_wronguserpass}" +msgstr "Algo está mal con ese nombre de usuario o contraseña, compruebe e inténtelo de nuevo." + +msgid "{login:button_login}" +msgstr "Iniciar sesión" + +msgid "{login:forgot}" +msgstr "¿Se te olvidó tu contraseña?" + +msgid "{login:logo}" +msgstr "Logotipo de %idpName%" + +msgid "{login:help}" +msgstr "necesito ayuda" + +msgid "{login:profile}" +msgstr "Administrar mi perfil" + msgid "{mfa:title}" msgstr "Verificación en 2 pasos" diff --git a/modules/material/locales/fr/LC_MESSAGES/material.po b/modules/material/locales/fr/LC_MESSAGES/material.po index 8a070a64..d6d9004c 100644 --- a/modules/material/locales/fr/LC_MESSAGES/material.po +++ b/modules/material/locales/fr/LC_MESSAGES/material.po @@ -44,6 +44,36 @@ msgstr "Changer" msgid "{footer:copyright}" msgstr "L'utilisation non autorisée de ce site est interdite et peut faire l'objet de poursuites civiles et pénales." +msgid "{login:title}" +msgstr "Connectez-vous avec votre identité %idpName%" + +msgid "{login:header}" +msgstr "Connectez-vous avec votre identité %idpName%" + +msgid "{login:label_username}" +msgstr "Nom d'utilisateur" + +msgid "{login:label_password}" +msgstr "Mot de passe" + +msgid "{login:error_wronguserpass}" +msgstr "Quelque chose ne va pas avec ce nom d'utilisateur ou ce mot de passe, veuillez vérifier et essayer à nouveau." + +msgid "{login:button_login}" +msgstr "Connexion" + +msgid "{login:forgot}" +msgstr "Mot de passe oublié?" + +msgid "{login:logo}" +msgstr "Logo %idpName%" + +msgid "{login:help}" +msgstr "j'ai besoin d'aide" + +msgid "{login:profile}" +msgstr "Gérer mon profil" + msgid "{mfa:title}" msgstr "Vérification en deux étapes" diff --git a/modules/material/locales/ko/LC_MESSAGES/material.po b/modules/material/locales/ko/LC_MESSAGES/material.po index b7ca908b..74e3776a 100644 --- a/modules/material/locales/ko/LC_MESSAGES/material.po +++ b/modules/material/locales/ko/LC_MESSAGES/material.po @@ -44,6 +44,36 @@ msgstr "바꾸다" msgid "{footer:copyright}" msgstr "이 사이트의 무단 사용은 금지되어 있으며 민사 및 형사 고발의 대상이 될 수 있습니다." +msgid "{login:title}" +msgstr "%idpName% 신원 계정으로 로그인하십시오." + +msgid "{login:header}" +msgstr "%idpName% 신원 계정으로 로그인하십시오." + +msgid "{login:label_username}" +msgstr "사용자 이름" + +msgid "{login:label_password}" +msgstr "암호" + +msgid "{login:error_wronguserpass}" +msgstr "해당 사용자 이름 또는 비밀번호가 잘못되었습니다. 다시 확인하고 다시 시도하십시오." + +msgid "{login:button_login}" +msgstr "로그인" + +msgid "{login:forgot}" +msgstr "비밀번호를 잊으 셨나요?" + +msgid "{login:logo}" +msgstr "%idpName% 로고" + +msgid "{login:help}" +msgstr "도움이 필요해." + +msgid "{login:profile}" +msgstr "내 프로필 관리" + msgid "{mfa:title}" msgstr "2 단계 인증" diff --git a/modules/material/themes/material/core/loginuserpass.php b/modules/material/themes/material/core/loginuserpass.php deleted file mode 100644 index 2c1656c8..00000000 --- a/modules/material/themes/material/core/loginuserpass.php +++ /dev/null @@ -1,173 +0,0 @@ - - - - configuration->getValue( - 'idp_display_name', - $this->configuration->getValue('idp_name', '—') - )); - ?> - - <?= $this->t('{material:login:title}', ['{idpName}' => $idpName]) ?> - - - - - - data['recaptcha.siteKey']); - - if (!empty($siteKey)) { - ?> - - - - - - -
-
- - -
- - - data)) { - $csrfToken = htmlentities($this->data['csrfToken']); - ?> - - - -
-
- <?= $this->t('{material:login:logo}', ['{idpName}' => $idpName]) ?> -
- -
-

- t('{material:login:header}', ['{idpName}' => $idpName]) ?> -

-
- -
-
- - - data['username'] ?? null); - ?> - id="username" - > -
- -
- - - - id="password" - > -
-
- - data['errorcode'] ?? null; - if ($errorCode == 'WRONGUSERPASS') { - $errorMessageKey = $this->data['errorparams'][1] ?? '{material:login:error_wronguserpass}'; - $errorMessageTokens = $this->data['errorparams'][2] ?? null; - - $message = $this->t($errorMessageKey, $errorMessageTokens); - ?> -

- error - - - - -

- - - - -
- configuration->getValue('passwordForgotUrl')); - if (!empty($forgotPasswordUrl)) { - ?> - - t('{material:login:forgot}') ?> - - - - - - -
-
- -
- data['helpCenterUrl'])): ?> - - t('{material:login:help}') ?> launch - - - - data['profileUrl'])): ?> - - t('{material:login:profile}') ?> launch - - -
-
-
-
- - diff --git a/modules/material/themes/material/silauth/loginuserpass.twig b/modules/material/themes/material/silauth/loginuserpass.twig new file mode 100644 index 00000000..51d31a25 --- /dev/null +++ b/modules/material/themes/material/silauth/loginuserpass.twig @@ -0,0 +1,146 @@ + + + + {{ '{login:title}'|trans({'%idpName%': idpName}) }} + + {% include 'header.twig' %} + + + + {% if siteKey is defined %} + + + + {% endif %} + + +
+
+ {% include 'announcement.twig' %} + +
+ + + {% if csrfToken is defined %} + + {% endif %} + +
+
+ {{ '{login:logo}'|trans({'%idpName%': idpName}) }} +
+ +
+

+ {{ '{login:header}'|trans({'%idpName%': idpName}) }} +

+
+ +
+
+ + + +
+ +
+ + + +
+
+ + {% if errorCode ?? '' == 'WRONGUSERPASS' %} + {% set errorMessageKey = errorparams[1] ?? '{login:error_wronguserpass}'|trans %} + {% set errorMessageTokens = errorparams[2] ?? null %} + {% set message = errorMessageKey|trans({errorMessageKey, errorMessageTokens}) %} +

+ error + + + {{ message|e }} + +

+ + + {% endif %} + +
+ {% if forgotPasswordUrl is defined %} + + {{ '{login:forgot}'|trans }} + + {% endif %} + + + + +
+ +
+ {% if helpCenterUrl is defined %} + + {{ '{login:help}'|trans }} + launch + + {% endif %} + + {% if profileUrl is defined %} + + {{ '{login:profile}'|trans }} + launch + + {% endif %} +
+
+
+
+
+ + diff --git a/modules/silauth/public/loginuserpass.php b/modules/silauth/public/loginuserpass.php index 11677e8c..533a66e0 100644 --- a/modules/silauth/public/loginuserpass.php +++ b/modules/silauth/public/loginuserpass.php @@ -2,14 +2,14 @@ use Sil\Psr3Adapters\Psr3StdOutLogger; use Sil\SspUtils\AnnouncementUtils; -use SimpleSAML\Module\silauth\Auth\Source\auth\Authenticator; -use SimpleSAML\Module\silauth\Auth\Source\csrf\CsrfProtector; -use SimpleSAML\Module\silauth\Auth\Source\http\Request; use SimpleSAML\Auth\Source; use SimpleSAML\Auth\State; use SimpleSAML\Configuration; use SimpleSAML\Error\BadRequest; use SimpleSAML\Error\Error as SimpleSAMLError; +use SimpleSAML\Module\silauth\Auth\Source\auth\Authenticator; +use SimpleSAML\Module\silauth\Auth\Source\csrf\CsrfProtector; +use SimpleSAML\Module\silauth\Auth\Source\http\Request; use SimpleSAML\Module\silauth\Auth\Source\SilAuth; use SimpleSAML\Session; use SimpleSAML\XHTML\Template; @@ -45,7 +45,7 @@ $authSourcesConfig = $globalConfig->getConfig('authsources.php'); $silAuthConfig = $authSourcesConfig->getConfigItem('silauth'); -$recaptchaSiteKey = $silAuthConfig->getString('recaptcha.siteKey', null); +$recaptchaSiteKey = $silAuthConfig->getOptionalString('recaptcha.siteKey', null); if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { @@ -79,7 +79,7 @@ $csrfProtector->changeMasterToken(); } -$t = new Template($globalConfig, 'core:loginuserpass.php'); +$t = new Template($globalConfig, 'silauth:loginuserpass'); $t->data['stateparams'] = array('AuthState' => $authStateId); $t->data['username'] = $username; $t->data['forceUsername'] = false; @@ -91,6 +91,8 @@ $t->data['profileUrl'] = $state['templateData']['profileUrl'] ?? ''; $t->data['helpCenterUrl'] = $state['templateData']['helpCenterUrl'] ?? ''; $t->data['announcement'] = AnnouncementUtils::getAnnouncement(); +$t->data['idpName'] = $globalConfig->getString('idp_display_name'); +$t->data['siteKey'] = $recaptchaSiteKey; /* For simplicity's sake, don't bother telling this Request to trust any IP * addresses. This is okay because we only track the failures of untrusted @@ -108,5 +110,5 @@ $t->data['SPMetadata'] = null; } -$t->show(); +$t->send(); exit(); diff --git a/modules/silauth/templates/.gitkeep b/modules/silauth/templates/.gitkeep new file mode 100644 index 00000000..57da54a3 --- /dev/null +++ b/modules/silauth/templates/.gitkeep @@ -0,0 +1 @@ +this directory will be filled in the Docker image with a copy of the material module templates