diff --git a/tests/integration/features/Shibboleth.feature b/tests/integration/features/Shibboleth.feature index 51babc8c4..3053b72ba 100644 --- a/tests/integration/features/Shibboleth.feature +++ b/tests/integration/features/Shibboleth.feature @@ -122,3 +122,48 @@ Feature: Shibboleth And The user value "email" should be "student1@idptestbed.edu" And The user value "display-name" should be "Stud Ent" And The last login timestamp of "student1" should not be empty + + Scenario: Migrating a local group to SAML backend while assigning a SAML user to it + Given The setting "type" is set to "saml" + And The setting "general-uid_mapping" is set to "urn:oid:0.9.2342.19200300.100.1.1" + And The setting "idp-entityId" is set to "https://shibboleth-integration-nextcloud.localdomain/idp/shibboleth" + And The setting "idp-singleSignOnService.url" is set to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO" + And The setting "idp-singleLogoutService.url" is set to "https://localhost:4443/idp/profile/Logout" + And The setting "idp-x509cert" is set to "MIIDnTCCAoWgAwIBAgIUGPx9uPjCu7c172IUgV84Tm94pBcwDQYJKoZIhvcNAQEL BQAwNzE1MDMGA1UEAwwsc2hpYmJvbGV0aC1pbnRlZ3JhdGlvbi1uZXh0Y2xvdWQu bG9jYWxkb21haW4wHhcNMTcwMTA0MTAxMTI3WhcNMzcwMTA0MTAxMTI3WjA3MTUw MwYDVQQDDCxzaGliYm9sZXRoLWludGVncmF0aW9uLW5leHRjbG91ZC5sb2NhbGRv bWFpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH+bPu45tk8/JRk XOxkyfbxocWZlY4mRumEUxscd3fn0oVzOrdWbHH7lCZV4bus4KxvJljc0Nm2K+Zr LoiRUUnf/LQ4LlehWVm5Kbc4kRgOXS0iGZN3SslAWPKyIg0tywg+TLOBPoS6EtST 1WuYg1JPMFxPfeFDWQ0dQYPlXIJWBFh6F2JMTb0FLECqA5l/ryYE13QisX5l+Mqo 6y3Dh7qIgaH0IJNobXoAcEWza7Kb2RnfhZRh9e0qjZIwBqTJUFM/6I86RYXn829s INUvYQQbez6VkGTdUQJ/GuXb/dD5sMQfOyK8hrRY5MozOmK32cz3JaAzSXpiSRS9 NxFwvicCAwEAAaOBoDCBnTAdBgNVHQ4EFgQUKn8+TV0WXSDeavvF0M8mWn1o8ukw fAYDVR0RBHUwc4Isc2hpYmJvbGV0aC1pbnRlZ3JhdGlvbi1uZXh0Y2xvdWQubG9j YWxkb21haW6GQ2h0dHBzOi8vc2hpYmJvbGV0aC1pbnRlZ3JhdGlvbi1uZXh0Y2xv dWQubG9jYWxkb21haW4vaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQELBQADggEB ABI6uzoIeLZT9Az2KTlLxIc6jZ4MDmhaVja4ZuBxTXEb7BFLfeASEJmQm1wgIMOn pJId3Kh3njW+3tOBWKm7lj8JxVVpAu4yMFSoQGPaVUgYB1AVm+pmAyPLzfJ/XGhf esCU2F/b0eHWcaIb3x+BZFX089Cd/PBtP84MNXdo+TccibxC8N39sr45qJM/7SC7 TfDYU0L4q2WZHJr4S7+0F+F4KaxLx9NzCvN4h6XaoWofZWir2iHO4NzbrVQGC0ei QybS/neBfni4A2g1lyzCb6xFB58JBvNCn7AAnDJULOE7S5XWUKsDAQVQrxTNkUq7 pnhlCQqZDwUdgmIXd1KB1So=" + And The setting "security-authnRequestsSigned" is set to "1" + And The setting "security-wantAssertionsEncrypted" is set to "1" + And The setting "sp-x509cert" is set to "-----BEGIN CERTIFICATE-----MIIC+zCCAeOgAwIBAgIJAIgZuvWDBIrdMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzAxMDQxMTM5MjFaFw0yNzAxMDIxMTM5MjFaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN3ESWaDH1JiJTy9yRJQV7kahPOxgBkIH2xwcYDL1k9deKNhSKLx7aGfxE244+HBcC6WLHKVUnOm0ld2qxQ4bMYiJXzZuqL67r07L5wxGAssv12lO92qohGmlHy3+VzRYUBmovu6upqOv3R2F8HBbo7Jc7Hvt7hOEJn/jPuFuF/fHit3mqU8l6IkrIZjpaW8T9fIWOXRq98U4+hkgWpqEZWsqlfE8BxAs9DeIMZab0GxO9stHLp+GYKx10uE4ezFcaDS8W+g2C8enCTt1HXGvcnj4o5zkC1lITGvcFTsiFqfIWyXeSufcxdc0W7HoG6J3ks0WJyK38sfFn0t2Ao6kX0CAwEAAaNQME4wHQYDVR0OBBYEFAoJzX6TVYAwC1GSPe6nObBG54zaMB8GA1UdIwQYMBaAFAoJzX6TVYAwC1GSPe6nObBG54zaMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJia9R70uXdUZtgujUPjLas4+sVajzlBFmqhBqpLAo934vljf9HISsHrPtdBcbS0d0rucqXwabDf0MlR18ksnT/NYpsTwMbMx76CrXi4zYEEW5lISKEO65aIkzVTcqKWSuhjtSnRdB6iOLsFiKmNMWXaIKMR5T0+AbR9wdQgn08W+3EEeHGvafVQfE3STVsSgNb1ft7DvcSUnfPXGU7KzvmTpZa0Hfmc7uY4vpdEEhLAdRhgLReS7USZskov7ooiPSoD+JRFi2gM4klBxTemHdNUa9oFnHMXuYKOkLbkgFvHxyy+QlLq2ELQTga5e7I83ZyOfGctyf8Ul6vGw10vbQ=-----END CERTIFICATE-----" + And The setting "sp-privateKey" is set to "-----BEGIN RSA PRIVATE KEY-----MIIEpAIBAAKCAQEA3cRJZoMfUmIlPL3JElBXuRqE87GAGQgfbHBxgMvWT114o2FIovHtoZ/ETbjj4cFwLpYscpVSc6bSV3arFDhsxiIlfNm6ovruvTsvnDEYCyy/XaU73aqiEaaUfLf5XNFhQGai+7q6mo6/dHYXwcFujslzse+3uE4Qmf+M+4W4X98eK3eapTyXoiSshmOlpbxP18hY5dGr3xTj6GSBamoRlayqV8TwHECz0N4gxlpvQbE72y0cun4ZgrHXS4Th7MVxoNLxb6DYLx6cJO3Udca9yePijnOQLWUhMa9wVOyIWp8hbJd5K59zF1zRbsegboneSzRYnIrfyx8WfS3YCjqRfQIDAQABAoIBAQC5CQAdcqZ9vLpJNilBCJxJLCFmm+HAAREHD8MErg9A5UK1P4S1wJp/0qieGPi68wXBOTgY2xKSwMycgb04/+NyZidVRu388takOW/+KNBg8pMxdZ6/05GqnI0kivSbR3CXpYuz8hekwhpo9+fWmKjApsHL47ItK6WaeKmPbAFsq1YJGzfp/DXg7LIvh9GA3C1LWWGV7SuCGOyX/2Moi8xRa7qBtH4hDo/0NRhTx7zjYjlBgNEr330pJUopc3+AtHE40R+xMr2zkGvq9RsCZxYxD2VWbLwQW0yNjWmQ2OTuMgJJvk2+N73QLHcB+tea82ZTszsNzRS9DLtc6qbsKEPZAoGBAO78U3vEuRyY56f/1hpo0xuCDwOkWGzgBQWkjJl6dlyVz/zKkhXBHpEYImyt8XRN0W3iGZYpZ2hCFJGTcDp32R6UiEyGLz0Uc8R/tva/TiRVW1FdNczzSHcB24b9OMK4vE9JLs8mA8Rp8YBgtLr5DDuMfYt/a/rZJbg/HIfIN98nAoGBAO2OInCX93t2I6zzRPIqKtI6q6FYNp64VIQjvw9Y8l0x3IdJZRP9H5C8ZhCeYPsgEqTXcXa4j5hL4rQzoUtxfxflBUUH60bcnd4LGaTCMYLS14G011E3GZlIP0sJi5OjEhy8fq3zt6jVzS9V/lPHB8i+w1D7CbPrMpW7B3k32vC7AoGAX/HvdkYhZyjAAEOG6m1hK68IZhbp5TP+8CgCxm9S65K9wKh3A8LXibrdvzIKOP4w8WOPkCipOkMlTNibeu24vj01hztr5aK7Y40+oEtnjNCz67N3MQQO+LBHOSeaTRqrh01DPKjvZECAU2D/zfzEe3fIw2Nxr3DUYub7hkvMmosCgYAzxbVVypjiLGYsDDyrdmsstCKxoDMPNmcdAVljc+QmUXaZeXJw/8qAVb78wjeqo1vM1zNgR2rsKyW2VkZB1fN39q7GU6qAIBa7zLmDAduegmr7VrlSduq6UFeS9/qWa4TIBICrUqFlR2tXdKtgANF+e6y/mmaL8qdsoH1JetXZfwKBgQC1vscRpdAXivjOOZAh+mzJWzS4BUl4CTJLYYIuOEXikmN5g0EdV2fhUEdkewmyKnXHsd0x83167bYgpTDNs71jUxDHy5NXlg2qIjLkf09X9wr19gBzDApfWzfh3vUqttyMZuQMLVNepGCWM2vjlY9KGl5OvZqY6d+7yO0mLV9GmQ==-----END RSA PRIVATE KEY-----" + And The setting "security-wantAssertionsSigned" is set to "1" + And The setting "localGroupsCheckForMigration" is set to '{\"dropAfter\":9223372036854775807,\"groups\":[\"Astrophysics\",\"Students\"]}' + And the local group "Astrophysics" is created + # Initial login so the user is known and belonging to SAML + And I send a GET request to "http://localhost:8080/index.php/login" + And I should be redirected to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO" + And I send a POST request to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO?execution=e1s1" with the following data + |j_username|j_password|_eventId_proceed| + |student1 |password | | + And The response should be a SAML redirect page that gets submitted + And I should be redirected to "http://localhost:8080/index.php/apps/dashboard/" + And I send a GET request with requesttoken to "http://localhost:8080/index.php/apps/user_saml/saml/sls" + # Set the proper attributes + And The setting "saml-attribute-mapping-email_mapping" is set to "urn:oid:0.9.2342.19200300.100.1.3" + And The setting "saml-attribute-mapping-displayName_mapping" is set to "urn:oid:2.5.4.42 urn:oid:2.5.4.4" + And The setting "saml-attribute-mapping-group_mapping" is set to "groups" + And the cookies are cleared + # Now login in for good + When I send a GET request to "http://localhost:8080/index.php/login" + Then I should be redirected to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO" + And I send a POST request to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO?execution=e1s1" with the following data + |j_username|j_password|_eventId_proceed| + |student1 |password | | + And The response should be a SAML redirect page that gets submitted + And I should be redirected to "http://localhost:8080/index.php/apps/dashboard/" + And The user value "groups" should be "Astrophysics,SAML_Students" + And the group backend of "Astrophysics" should be "Database" + And Then the group backend of "Astrophysics" must not be "user_saml" + When I execute the background job for class "OCA\\User_SAML\\Jobs\\MigrateGroups" + Then the group backend of "Astrophysics" should be "user_saml" + And Then the group backend of "Astrophysics" must not be "Database" + And The user value "groups" should be "Astrophysics,SAML_Students" + diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 96dad4620..302757b4b 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -21,6 +21,7 @@ use Behat\Behat\Context\Context; use Behat\Gherkin\Node\TableNode; +use GuzzleHttp\Cookie\CookieJar; class FeatureContext implements Context { /** @var \GuzzleHttp\Message\Response */ @@ -32,6 +33,7 @@ class FeatureContext implements Context { private const ENV_CONFIG_FILE = __DIR__ . '/../../../../../../config/env.config.php'; private const MAIN_CONFIG_FILE = __DIR__ . '/../../../../../../config/config.php'; + private CookieJar $cookieJar; public function __construct() { date_default_timezone_set('Europe/Berlin'); @@ -39,10 +41,10 @@ public function __construct() { /** @BeforeScenario */ public function before() { - $jar = new \GuzzleHttp\Cookie\FileCookieJar('/tmp/cookies_' . md5(openssl_random_pseudo_bytes(12))); + $this->cookieJar = new CookieJar(); $this->client = new \GuzzleHttp\Client([ 'version' => 2.0, - 'cookies' => $jar, + 'cookies' => $this->cookieJar, 'verify' => false, 'allow_redirects' => [ 'referer' => true, @@ -66,9 +68,28 @@ public function after() { $user ) ); - if (file_exists(self::ENV_CONFIG_FILE)) { - unlink(self::ENV_CONFIG_FILE); - } + } + + $groups = [ + 'Astrophysics', + 'SAML_Astrophysics', + 'Students', + 'SAML_Students' + ]; + + foreach ($groups as $group) { + shell_exec( + sprintf( + '%s %s group:delete %s', + PHP_BINARY, + __DIR__ . '/../../../../../../occ', + $group + ) + ); + } + + if (file_exists(self::ENV_CONFIG_FILE)) { + unlink(self::ENV_CONFIG_FILE); } foreach ($this->changedSettings as $setting) { @@ -105,7 +126,8 @@ public function theSettingIsSetTo($settingName, 'type', 'general-require_provisioned_account', 'general-allow_multiple_user_back_ends', - 'general-use_saml_auth_for_desktop' + 'general-use_saml_auth_for_desktop', + 'localGroupsCheckForMigration', ])) { $this->changedSettings[] = $settingName; shell_exec( @@ -367,4 +389,116 @@ public function theGroupShouldExists(string $gid): void { throw new UnexpectedValueException('Group does not exist'); } } + + /** + * @When /^I execute the background job for class "([^"]*)"$/ + */ + public function iExecuteTheBackgroundJobForClass(string $className) { + $response = shell_exec( + sprintf( + '%s %s background-job:list --output=json --class %s', + PHP_BINARY, + __DIR__ . '/../../../../../../occ', + $className + ) + ); + + $responseArray = json_decode($response, true); + if (count($responseArray) === 0) { + throw new UnexpectedValueException('Background job was not enqueued'); + } + + foreach ($responseArray as $jobDetails) { + $jobID = (int)$jobDetails['id']; + $response = shell_exec( + sprintf( + '%s %s background-job:execute --force-execute %d', + PHP_BINARY, + __DIR__ . '/../../../../../../occ', + $jobID + ) + ); + } + } + + /** + * @Then /^the group backend of "([^"]*)" should be "([^"]*)"$/ + */ + public function theGroupBackendOfShouldBe(string $groupId, string $backendName) { + $response = shell_exec( + sprintf( + '%s %s group:info --output=json %s', + PHP_BINARY, + __DIR__ . '/../../../../../../occ', + $groupId + ) + ); + + $responseArray = json_decode($response, true); + if (!isset($responseArray['groupID']) || $responseArray['groupID'] !== $groupId) { + throw new UnexpectedValueException('Group does not exist'); + } + if (!in_array($backendName, $responseArray['backends'], true)) { + throw new UnexpectedValueException('Group does not belong to this backend'); + } + } + + /** + * @Given /^Then the group backend of "([^"]*)" must not be "([^"]*)"$/ + */ + public function thenTheGroupBackendOfMustNotBe(string $groupId, string $backendName) { + try { + $this->theGroupBackendOfShouldBe($groupId, $backendName); + throw new UnexpectedValueException('Group does belong to this backend'); + } catch (UnexpectedValueException $e) { + if ($e->getMessage() !== 'Group does not belong to this backend') { + throw $e; + } + } + } + + /** + * @Given /^the local group "([^"]*)" is created$/ + */ + public function theLocalGroupIsCreated(string $groupName) { + shell_exec( + sprintf( + '%s %s group:add %s', + PHP_BINARY, + __DIR__ . '/../../../../../../occ', + $groupName + ) + ); + } + + /** + * @Given /^I send a GET request with requesttoken to "([^"]*)"$/ + */ + public function iSendAGETRequestWithRequesttokenTo($url) { + $requestToken = substr( + preg_replace( + '/(.*)data-requesttoken="(.*)">(.*)/sm', + '\2', + $this->response->getBody()->getContents() + ), + 0, + 89 + ); + $this->response = $this->client->request( + 'GET', + $url, + [ + 'query' => [ + 'requesttoken' => $requestToken + ], + ] + ); + } + + /** + * @Given /^the cookies are cleared$/ + */ + public function theCookiesAreCleared(): void { + $this->cookieJar->clear(); + } }