Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions lib/Controller/SAMLController.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,22 @@ public function login(int $idp = 1): Http\RedirectResponse {
$type = $this->config->getAppValue($this->appName, 'type');
switch ($type) {
case 'saml':
$auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp));
$settings = $this->samlSettings->getOneLoginSettingsArray($idp);
$auth = new Auth($settings);
$passthroughParamsString = trim($settings['idp-passthroughParameters'] ?? '') ;
$passthroughParams = array_map('trim', explode(',', $passthroughParamsString));

$passthroughValues = [];
foreach ($passthroughParams as $passthroughParam) {
$value = (string)$this->request->getParam($passthroughParam, '');
if ($value !== '') {
$passthroughValues[$passthroughParam] = $value;
}
}


$returnUrl = $originalUrl ?: $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.login');
$ssoUrl = $auth->login($returnUrl, [], false, false, true);
$ssoUrl = $auth->login($returnUrl, $passthroughValues, false, false, true);
$response = new Http\RedirectResponse($ssoUrl);

// Small hack to make user_saml work with the loginflows
Expand Down
1 change: 1 addition & 0 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function getSamlProviderSettings(int $providerId): array {
'singleSignOnService.url' => ['required' => false],
'entityId' => ['required' => false],
'x509cert' => ['required' => false],
'idp-passthroughParameters' => ['required' => false],
];
/* Fetch all config values for the given providerId */

Expand Down
2 changes: 2 additions & 0 deletions lib/SAMLSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class SAMLSettings {
'idp-singleLogoutService.responseUrl',
'idp-singleLogoutService.url',
'idp-singleSignOnService.url',
'idp-passthroughParameters',
'idp-x509cert',
'security-authnRequestsSigned',
'security-general',
Expand Down Expand Up @@ -133,6 +134,7 @@ public function getOneLoginSettingsArray(int $idp): array {
'strict' => true,
'debug' => $this->config->getSystemValue('debug', false),
'baseurl' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.base'),
'idp-passthroughParameters' => $this->configurations[$idp]['idp-passthroughParameters'] ?? '',
'security' => [
'nameIdEncrypted' => ($this->configurations[$idp]['security-nameIdEncrypted'] ?? '0') === '1',
'authnRequestsSigned' => ($this->configurations[$idp]['security-authnRequestsSigned'] ?? '0') === '1',
Expand Down
4 changes: 4 additions & 0 deletions templates/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
<label class="user-saml-standalone-label" for="user-saml-x509cert"><?php p($l->t('Public X.509 certificate of the IdP')) ?></label><br/>
<textarea id="user-saml-x509cert" name="x509cert"><?php p($_['config']['idp-x509cert'] ?? '') ?></textarea>
</p>
<p>
<label class="user-saml-standalone-label" for="user-saml-idp-passthroughParameters"><?php p($l->t('Request parameters to pass-through to IdP (comma separated list)')) ?></label><br/>
<input id="user-saml-idp-passthroughParameters" name="idp-passthroughParameters" value="<?php p($_['config']['idp-passthroughParameters'] ?? '') ?>" type="text" placeholder="idp_hint,extra_parameter"/>
</p>
</div>
</div>

Expand Down
17 changes: 17 additions & 0 deletions tests/integration/features/Shibboleth.feature
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ Feature: Shibboleth
And The user value "id" should be "student1"
And The last login timestamp of "student1" should not be empty

Scenario: Authenticating using Shibboleth with SAML and no check if user exists on backend and supply passthroughParameters
Given The config "idp-passthroughParameters" is "idp_hint,test_param"
And The setting "type" is set to "saml"
And The setting "general-require_provisioned_account" is set to "1"
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-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"
When I send a GET request with query params to "http://localhost:8080/index.php/apps/user_saml/saml/login?idp_hint=hello&test_param=hey"
Then The config "idp-passthroughParameters" should be "idp_hint,test_param"
And I should be redirected to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO?idp_hint=hello&test_param=hey" with query params

Scenario: Authenticating using Shibboleth with SAML and check if user exists on backend and not existing user
Given The setting "type" is set to "saml"
And The setting "general-require_provisioned_account" is set to "1"
Expand Down
111 changes: 108 additions & 3 deletions tests/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down Expand Up @@ -133,6 +134,41 @@ public function theSettingIsSetTo(string $settingName, string $value): void {
)
);
}
/**
* @Given The config :configName is :value
*/
public function theConfigIs(string $configName, string $value): void {
shell_exec(
sprintf(
'%s %s saml:config:set --"%s"="%s" %d',
PHP_BINARY,
__DIR__ . '/../../../../../../occ',
$configName,
$value,
1
)
);
}
/**
* @Then The config :configName should be :expectedValue
*/
public function theConfigShouldBe(string $configName, string $expectedValue): void {
$json = shell_exec(
sprintf(
'%s %s saml:config:get --providerId %d --output json',
PHP_BINARY,
__DIR__ . '/../../../../../../occ',
1
)
);
$json = json_decode($json);
$value = $json->{'1'}->$configName;
if ($value !== $expectedValue) {
throw new UnexpectedValueException(
sprintf('Config value for %s is %s, but expected was %s', $configName, $value, $expectedValue)
);
}
}

/**
* @Then The setting :settingName is currently :expectedValue
Expand Down Expand Up @@ -189,6 +225,24 @@ public function iSendAGetRequestTo($url) {
]
);
}
/**
* @When I send a GET request with query params to :url
*/
public function iSendAGetRequestWithQueryParamsTo($url) {
$url = $url . '&idp=1';
$query = parse_url($url)['query'];
$url = str_replace('?' . $query, '', $url);
$this->response = $this->client->request(
'GET',
$url,
[
'headers' => [
'Accept' => 'text/html',
],
'query' => $query
]
);
}

/**
* @Then I should be redirected to :targetUrl
Expand Down Expand Up @@ -223,15 +277,65 @@ public function iShouldBeRedirectedTo($targetUrl) {
}
}
}
/**
* @Then I should be redirected to :targetUrl with query params
*
* @param string $targetUrl
* @throws InvalidArgumentException
*/
public function iShouldBeRedirectedToWithQueryParams($targetUrl) {
$redirectHeader = $this->response->getHeader('X-Guzzle-Redirect-History');
$firstUrl = $redirectHeader[0];
$firstUrlParsed = parse_url($firstUrl);
$targetUrl = parse_url($targetUrl);
$paramsToCheck = [
'scheme',
'host',
'path',
'query'
];

// Remove everything after a comma in the URL since cookies are passed there
[$firstUrlParsed['path']] = explode(';', $firstUrlParsed['path']);
$passthroughParams = $targetUrl['query'];
foreach ($paramsToCheck as $param) {
if ($param == 'query') {
foreach (explode('&', $passthroughParams) as $passthrough) {
if (!str_contains($firstUrl, $passthrough)) {
throw new InvalidArgumentException(
sprintf(
'Expected to find %s for parameter %s',
$passthrough,
$param,
)
);
}
}
} else {
if ($targetUrl[$param] !== $firstUrlParsed[$param]) {
throw new InvalidArgumentException(
sprintf(
'Expected %s for parameter %s, got %s',
$targetUrl[$param],
$param,
$firstUrlParsed[$param]
)
);
}
}
}
}

/**
* @Then I send a POST request to :url with the following data
*
* @param string $url
* @param TableNode $table
*/
public function iSendAPostRequestToWithTheFollowingData($url,
TableNode $table) {
public function iSendAPostRequestToWithTheFollowingData(
$url,
TableNode $table,
) {
$postParams = $table->getColumnsHash()[0];
$this->response = $this->client->request(
'POST',
Expand Down Expand Up @@ -351,7 +455,8 @@ public function theGroupHasExactlyTheMembers(string $group, string $memberList):
'format' => 'json',
],
'auth' => [
'admin', 'admin'
'admin',
'admin'
],
'cookies' => '',
]
Expand Down