diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2dd73f67..a5b75964 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,9 +55,9 @@ When accessing locked down portions of the portal, you will be asked for a usern The password is always `password`. `tools/docker-dev/web/htpasswd` contains all valid usernames. Notable users: -* `user1@org1.test` - admin, PI -* `user2@org1.test` - not admin, not PI -* `user2000@org2.test` - does not yet have an account +* `user1_org1_test` - admin, PI +* `user2_org1_test` - not admin, not PI +* `user2000_org2_test` - does not yet have an account ### Changes to Dev Environment diff --git a/README.md b/README.md index d3f1b054..7770f018 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,9 @@ See the Docker Compose environment (`tools/docker-dev/`) for an (unsafe for prod * `COMPOSER_ALLOW_SUPERUSER=1 composer --no-dev --no-scripts --no-plugins install` * `httpd` `DocumentRoot` set to `webroot/` * `httpd` Authentication - * Any authentication will do as long as it defines `REMOTE_USER`, `givenName`, `sn`, and `mail` - * `REMOTE_USER` must also be unique, non-reassignable, and persistent + * Any authentication will do as long as it defines `REMOTE_USER`, `eppn`, `givenName`, `sn`, and `mail` + * `REMOTE_USER` is used for the UID attribute for users in LDAP + * Unity uses Shibboleth `AttributeResolver type=Transform` to convert `eppn` into `REMOTE_USER` ([private link to config](https://gitlab.rc.umass.edu/unity/admin/ansible/-/blob/fe42ba30f722b75a7980a0b07d93be90055e4f83/roles/shibboleth/templates/shibboleth2.xml.j2)) * Unity uses Shibboleth SP and the Apache Shibboleth module (`apt install shibboleth-sp-utils libapache2-mod-shib` on Ubuntu) * `httpd` Authorization * Global access to `webroot/panel/` diff --git a/resources/lib/UnitySSO.php b/resources/lib/UnitySSO.php index 0495a164..52c8333c 100644 --- a/resources/lib/UnitySSO.php +++ b/resources/lib/UnitySSO.php @@ -7,13 +7,6 @@ class UnitySSO { - private static function eppnToUID($eppn) - { - $eppn_output = str_replace(".", "_", $eppn); - $eppn_output = str_replace("@", "_", $eppn_output); - return strtolower($eppn_output); - } - private static function eppnToOrg($eppn) { $parts = explode("@", $eppn); @@ -58,8 +51,8 @@ private static function getAttribute($attributeName, $fallbackAttributeName = nu public static function getSSO() { return array( - "user" => self::eppnToUID(self::getAttribute("REMOTE_USER")), - "org" => self::eppnToOrg(self::getAttribute("REMOTE_USER")), + "user" => self::getAttribute("REMOTE_USER"), + "org" => self::eppnToOrg(self::getAttribute("eppn")), "firstname" => self::getAttribute("givenName"), "lastname" => self::getAttribute("sn"), "name" => self::getAttribute("givenName") . " " . self::getAttribute("sn"), diff --git a/test/functional/InvalidEPPNTest.php b/test/functional/InvalidEPPNTest.php index 239d9d91..a88475e7 100644 --- a/test/functional/InvalidEPPNTest.php +++ b/test/functional/InvalidEPPNTest.php @@ -9,15 +9,15 @@ class InvalidEPPNTest extends TestCase public static function provider() { return [ - ["", false], - ["a", false], - ["a@b", true], - ["a@b@c", false], + ["", "", false], + ["a", "a", false], + ["a@b", "a_b", true], + ["a@b@c", "a_b_c", false], ]; } #[DataProvider("provider")] - public function testInitGetSSO(string $eppn, bool $is_valid): void + public function testInitGetSSO(string $eppn, string $uid, bool $is_valid): void { global $SSO; $original_server = $_SERVER; @@ -30,7 +30,7 @@ public function testInitGetSSO(string $eppn, bool $is_valid): void $this->expectException(SSOException::class); } try { - $_SERVER["REMOTE_USER"] = $eppn; + $_SERVER["REMOTE_USER"] = $uid; $_SERVER["REMOTE_ADDR"] = "127.0.0.1"; $_SERVER["eppn"] = $eppn; $_SERVER["givenName"] = "foo"; diff --git a/test/phpunit-bootstrap.php b/test/phpunit-bootstrap.php index 34b370d9..6382e6af 100644 --- a/test/phpunit-bootstrap.php +++ b/test/phpunit-bootstrap.php @@ -60,6 +60,7 @@ function arraysAreEqualUnOrdered(array $a, array $b): bool function switchUser( + string $uid, string $eppn, string $given_name, string $sn, @@ -74,7 +75,7 @@ function switchUser( session_id($session_id); } // session_start will be called on the first post() - $_SERVER["REMOTE_USER"] = $eppn; + $_SERVER["REMOTE_USER"] = $uid; $_SERVER["REMOTE_ADDR"] = "127.0.0.1"; $_SERVER["HTTP_HOST"] = "phpunit"; // used for config override $_SERVER["eppn"] = $eppn; @@ -127,64 +128,64 @@ function http_get(string $phpfile, array $get_data = array()): void function getNormalUser() { - return ["user2@org1.test", "foo", "bar", "user2@org1.test"]; + return ["user2_org1_test", "user2@org1.test", "foo", "bar", "user2@org1.test"]; } function getNormalUser2() { - return ["user2@org1.test", "foo", "bar", "user2@org1.test"]; + return ["user2_org1_test", "user2@org1.test", "foo", "bar", "user2@org1.test"]; } function getUserHasNotRequestedAccountDeletionHasGroup() { - return ["user1@org1.test", "foo", "bar", "user1@org1.test"]; + return ["user1_org1_test", "user1@org1.test", "foo", "bar", "user1@org1.test"]; } function getUserHasNotRequestedAccountDeletionHasNoGroups() { - return ["user2@org1.test", "foo", "bar", "user2@org1.test"]; + return ["user2_org1_test", "user2@org1.test", "foo", "bar", "user2@org1.test"]; } function getUserHasNoSshKeys() { - return ["user3@org1.test", "foo", "bar", "user3@org1.test"]; + return ["user3_org1_test", "user3@org1.test", "foo", "bar", "user3@org1.test"]; } function getUserNotPiNotRequestedBecomePi() { - return ["user2@org1.test", "foo", "bar", "user2@org1.test"]; + return ["user2_org1_test", "user2@org1.test", "foo", "bar", "user2@org1.test"]; } function getUserNotPiNotRequestedBecomePiRequestedAccountDeletion() { - return ["user4@org1.test", "foo", "bar", "user4@org1.test"]; + return ["user4_org1_test", "user4@org1.test", "foo", "bar", "user4@org1.test"]; } function getUserWithOneKey() { - return ["user5@org2.test", "foo", "bar", "user5@org2.test"]; + return ["user5_org2_test", "user5@org2.test", "foo", "bar", "user5@org2.test"]; } function getUserIsPIHasNoMembersNoMemberRequests() { - return ["user5@org2.test", "foo", "bar", "user5@org2.test"]; + return ["user5_org2_test", "user5@org2.test", "foo", "bar", "user5@org2.test"]; } function getUserIsPIHasAtLeastOneMember() { - return ["user1@org1.test", "foo", "bar", "user1@org1.test"]; + return ["user1_org1_test", "user1@org1.test", "foo", "bar", "user1@org1.test"]; } function getNonExistentUser() { - return ["user2001@org998.test", "foo", "bar", "user2001@org998.test"]; + return ["user2001_org998_test", "user2001@org998.test", "foo", "bar", "user2001@org998.test"]; } function getNonexistentUsersWithExistentOrg() { return [ - ["user2003@org1.test", "foo", "bar", "user2003@org1.test"], - ["user2004@org1.test", "foo", "bar", "user2004@org1.test"], + ["user2003_org1_test", "user2003@org1.test", "foo", "bar", "user2003@org1.test"], + ["user2004_org1_test", "user2004@org1.test", "foo", "bar", "user2004@org1.test"], ]; } @@ -192,20 +193,21 @@ function getNonExistentUserAndExpectedUIDGIDNoCustomMapping() { // defaults/config.ini.default: ldap.offset_UIDGID=1000000 // test/custom_user_mappings/test.csv has reservations for 1000000-1000004 - return [["user2002@org998.test", "foo", "bar", "user2002@org998.test"], 1000005]; + return [["user2002_org998_test", "user2002@org998.test", "foo", "bar", "user2002@org998.test"], 1000005]; } function getNonExistentUserAndExpectedUIDGIDWithCustomMapping() { // test/custom_user_mappings/test.csv: {user2001: 555} - return [["user2001@org998.test", "foo", "bar", "user2001@org998.test"], 555]; + return [["user2001_org998_test", "user2001@org998.test", "foo", "bar", "user2001@org998.test"], 555]; } function getMultipleValueAttributesAndExpectedSSO() { return [ [ - "REMOTE_USER" => "user2003@org998.test", + "REMOTE_USER" => "user2003_org998_test", + "eppn" => "user2003@org998.test", "givenName" => "foo;foo", "sn" => "bar;bar", "mail" => "user2003@org998.test;user2003@org998.test", @@ -220,5 +222,5 @@ function getMultipleValueAttributesAndExpectedSSO() function getAdminUser() { - return ["user1@org1.test", "foo", "bar", "user1@org1.test"]; + return ["user1_org1_test", "user1@org1.test", "foo", "bar", "user1@org1.test"]; } diff --git a/tools/docker-dev/web/Dockerfile b/tools/docker-dev/web/Dockerfile index a65eee49..bfe253a1 100644 --- a/tools/docker-dev/web/Dockerfile +++ b/tools/docker-dev/web/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y \ php-xml \ php-intl \ php-xdebug +RUN a2enmod rewrite COPY htpasswd /etc/apache2/.htpasswd RUN chown www-data /etc/apache2/.htpasswd COPY unity-apache.conf /etc/apache2/sites-available/unity.conf diff --git a/tools/docker-dev/web/unity-apache.conf b/tools/docker-dev/web/unity-apache.conf index 34a4740b..6df69ee8 100644 --- a/tools/docker-dev/web/unity-apache.conf +++ b/tools/docker-dev/web/unity-apache.conf @@ -11,17 +11,35 @@ SetEnv givenName "DevFirstname" SetEnv sn "DevLastname" SetEnv mail "DevName@DevDomain.com" + + RewriteEngine On + # copy REMOTE_USER to eppn + RewriteCond %{ENV:REMOTE_USER} (.*) + RewriteRule .* - [E=eppn:%1] + # substitute '@' and '.' with '_' in REMOTE_USER + # this is only garunteed to work for the simple usernames in htpasswd + RewriteCond %{ENV:REMOTE_USER} ^(.*)@(.*)\.(.*)$ + RewriteRule .* - [E=REMOTE_USER:%1_%2_%3] AuthType Basic AuthName "Unity Admin Panel" AuthUserFile /etc/apache2/.htpasswd - Require user user1@org1.test user2@org1.test user3@org1.test user4@org1.test user5@org2.test user6@org1.test user7@org1.test user8@org1.test user9@org3.test user10@org1.test user11@org1.test user12@org1.test user13@org1.test user14@org3.test + Require user user1_org1_test user2_org1_test user3_org1_test user4_org1_test user5_org2_test user6_org1_test user7_org1_test user8_org1_test user9_org3_test user10_org1_test user11_org1_test user12_org1_test user13_org1_test user14_org3_test SetEnv givenName "DevFirstname" SetEnv sn "DevLastname" SetEnv mail "DevName@DevDomain.com" + + RewriteEngine On + # copy REMOTE_USER to eppn + RewriteCond %{ENV:REMOTE_USER} (.*) + RewriteRule .* - [E=eppn:%1] + # substitute '@' and '.' with '_' in REMOTE_USER + # this is only garunteed to work for the simple usernames in htpasswd + RewriteCond %{ENV:REMOTE_USER} ^(.*)@(.*)\.(.*)$ + RewriteRule .* - [E=REMOTE_USER:%1_%2_%3]