Skip to content

Commit 1e9bbbe

Browse files
authored
Merge pull request #901 from SUNET/master
feat(PassthroughParameters): Make it possible to pass through parameters to the SAML library
2 parents 03e8026 + 2999e2d commit 1e9bbbe

File tree

6 files changed

+146
-5
lines changed

6 files changed

+146
-5
lines changed

lib/Controller/SAMLController.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,22 @@ public function login(int $idp = 1): Http\RedirectResponse {
150150
$type = $this->config->getAppValue($this->appName, 'type');
151151
switch ($type) {
152152
case 'saml':
153-
$auth = new Auth($this->samlSettings->getOneLoginSettingsArray($idp));
153+
$settings = $this->samlSettings->getOneLoginSettingsArray($idp);
154+
$auth = new Auth($settings);
155+
$passthroughParamsString = trim($settings['idp-passthroughParameters'] ?? '') ;
156+
$passthroughParams = array_map('trim', explode(',', $passthroughParamsString));
157+
158+
$passthroughValues = [];
159+
foreach ($passthroughParams as $passthroughParam) {
160+
$value = (string)$this->request->getParam($passthroughParam, '');
161+
if ($value !== '') {
162+
$passthroughValues[$passthroughParam] = $value;
163+
}
164+
}
165+
154166

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

159171
// Small hack to make user_saml work with the loginflows

lib/Controller/SettingsController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public function getSamlProviderSettings(int $providerId): array {
5757
'singleSignOnService.url' => ['required' => false],
5858
'entityId' => ['required' => false],
5959
'x509cert' => ['required' => false],
60+
'idp-passthroughParameters' => ['required' => false],
6061
];
6162
/* Fetch all config values for the given providerId */
6263

lib/SAMLSettings.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class SAMLSettings {
3030
'idp-singleLogoutService.responseUrl',
3131
'idp-singleLogoutService.url',
3232
'idp-singleSignOnService.url',
33+
'idp-passthroughParameters',
3334
'idp-x509cert',
3435
'security-authnRequestsSigned',
3536
'security-general',
@@ -133,6 +134,7 @@ public function getOneLoginSettingsArray(int $idp): array {
133134
'strict' => true,
134135
'debug' => $this->config->getSystemValue('debug', false),
135136
'baseurl' => $this->urlGenerator->linkToRouteAbsolute('user_saml.SAML.base'),
137+
'idp-passthroughParameters' => $this->configurations[$idp]['idp-passthroughParameters'] ?? '',
136138
'security' => [
137139
'nameIdEncrypted' => ($this->configurations[$idp]['security-nameIdEncrypted'] ?? '0') === '1',
138140
'authnRequestsSigned' => ($this->configurations[$idp]['security-authnRequestsSigned'] ?? '0') === '1',

templates/admin.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@
153153
<label class="user-saml-standalone-label" for="user-saml-x509cert"><?php p($l->t('Public X.509 certificate of the IdP')) ?></label><br/>
154154
<textarea id="user-saml-x509cert" name="x509cert"><?php p($_['config']['idp-x509cert'] ?? '') ?></textarea>
155155
</p>
156+
<p>
157+
<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/>
158+
<input id="user-saml-idp-passthroughParameters" name="idp-passthroughParameters" value="<?php p($_['config']['idp-passthroughParameters'] ?? '') ?>" type="text" placeholder="idp_hint,extra_parameter"/>
159+
</p>
156160
</div>
157161
</div>
158162

tests/integration/features/Shibboleth.feature

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ Feature: Shibboleth
2121
And The user value "id" should be "student1"
2222
And The last login timestamp of "student1" should not be empty
2323

24+
Scenario: Authenticating using Shibboleth with SAML and no check if user exists on backend and supply passthroughParameters
25+
Given The config "idp-passthroughParameters" is "idp_hint,test_param"
26+
And The setting "type" is set to "saml"
27+
And The setting "general-require_provisioned_account" is set to "1"
28+
And The setting "general-uid_mapping" is set to "urn:oid:0.9.2342.19200300.100.1.1"
29+
And The setting "idp-entityId" is set to "https://shibboleth-integration-nextcloud.localdomain/idp/shibboleth"
30+
And The setting "idp-singleSignOnService.url" is set to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO"
31+
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="
32+
And The setting "security-authnRequestsSigned" is set to "1"
33+
And The setting "security-wantAssertionsEncrypted" is set to "1"
34+
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-----"
35+
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-----"
36+
And The setting "security-wantAssertionsSigned" is set to "1"
37+
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"
38+
Then The config "idp-passthroughParameters" should be "idp_hint,test_param"
39+
And I should be redirected to "https://localhost:4443/idp/profile/SAML2/Redirect/SSO?idp_hint=hello&test_param=hey" with query params
40+
2441
Scenario: Authenticating using Shibboleth with SAML and check if user exists on backend and not existing user
2542
Given The setting "type" is set to "saml"
2643
And The setting "general-require_provisioned_account" is set to "1"

tests/integration/features/bootstrap/FeatureContext.php

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
45
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -133,6 +134,41 @@ public function theSettingIsSetTo(string $settingName, string $value): void {
133134
)
134135
);
135136
}
137+
/**
138+
* @Given The config :configName is :value
139+
*/
140+
public function theConfigIs(string $configName, string $value): void {
141+
shell_exec(
142+
sprintf(
143+
'%s %s saml:config:set --"%s"="%s" %d',
144+
PHP_BINARY,
145+
__DIR__ . '/../../../../../../occ',
146+
$configName,
147+
$value,
148+
1
149+
)
150+
);
151+
}
152+
/**
153+
* @Then The config :configName should be :expectedValue
154+
*/
155+
public function theConfigShouldBe(string $configName, string $expectedValue): void {
156+
$json = shell_exec(
157+
sprintf(
158+
'%s %s saml:config:get --providerId %d --output json',
159+
PHP_BINARY,
160+
__DIR__ . '/../../../../../../occ',
161+
1
162+
)
163+
);
164+
$json = json_decode($json);
165+
$value = $json->{'1'}->$configName;
166+
if ($value !== $expectedValue) {
167+
throw new UnexpectedValueException(
168+
sprintf('Config value for %s is %s, but expected was %s', $configName, $value, $expectedValue)
169+
);
170+
}
171+
}
136172

137173
/**
138174
* @Then The setting :settingName is currently :expectedValue
@@ -189,6 +225,24 @@ public function iSendAGetRequestTo($url) {
189225
]
190226
);
191227
}
228+
/**
229+
* @When I send a GET request with query params to :url
230+
*/
231+
public function iSendAGetRequestWithQueryParamsTo($url) {
232+
$url = $url . '&idp=1';
233+
$query = parse_url($url)['query'];
234+
$url = str_replace('?' . $query, '', $url);
235+
$this->response = $this->client->request(
236+
'GET',
237+
$url,
238+
[
239+
'headers' => [
240+
'Accept' => 'text/html',
241+
],
242+
'query' => $query
243+
]
244+
);
245+
}
192246

193247
/**
194248
* @Then I should be redirected to :targetUrl
@@ -223,15 +277,65 @@ public function iShouldBeRedirectedTo($targetUrl) {
223277
}
224278
}
225279
}
280+
/**
281+
* @Then I should be redirected to :targetUrl with query params
282+
*
283+
* @param string $targetUrl
284+
* @throws InvalidArgumentException
285+
*/
286+
public function iShouldBeRedirectedToWithQueryParams($targetUrl) {
287+
$redirectHeader = $this->response->getHeader('X-Guzzle-Redirect-History');
288+
$firstUrl = $redirectHeader[0];
289+
$firstUrlParsed = parse_url($firstUrl);
290+
$targetUrl = parse_url($targetUrl);
291+
$paramsToCheck = [
292+
'scheme',
293+
'host',
294+
'path',
295+
'query'
296+
];
297+
298+
// Remove everything after a comma in the URL since cookies are passed there
299+
[$firstUrlParsed['path']] = explode(';', $firstUrlParsed['path']);
300+
$passthroughParams = $targetUrl['query'];
301+
foreach ($paramsToCheck as $param) {
302+
if ($param == 'query') {
303+
foreach (explode('&', $passthroughParams) as $passthrough) {
304+
if (!str_contains($firstUrl, $passthrough)) {
305+
throw new InvalidArgumentException(
306+
sprintf(
307+
'Expected to find %s for parameter %s',
308+
$passthrough,
309+
$param,
310+
)
311+
);
312+
}
313+
}
314+
} else {
315+
if ($targetUrl[$param] !== $firstUrlParsed[$param]) {
316+
throw new InvalidArgumentException(
317+
sprintf(
318+
'Expected %s for parameter %s, got %s',
319+
$targetUrl[$param],
320+
$param,
321+
$firstUrlParsed[$param]
322+
)
323+
);
324+
}
325+
}
326+
}
327+
}
226328

227329
/**
228330
* @Then I send a POST request to :url with the following data
229331
*
230332
* @param string $url
231333
* @param TableNode $table
232334
*/
233-
public function iSendAPostRequestToWithTheFollowingData($url,
234-
TableNode $table) {
335+
public function iSendAPostRequestToWithTheFollowingData(
336+
$url,
337+
TableNode $table,
338+
) {
235339
$postParams = $table->getColumnsHash()[0];
236340
$this->response = $this->client->request(
237341
'POST',
@@ -351,7 +455,8 @@ public function theGroupHasExactlyTheMembers(string $group, string $memberList):
351455
'format' => 'json',
352456
],
353457
'auth' => [
354-
'admin', 'admin'
458+
'admin',
459+
'admin'
355460
],
356461
'cookies' => '',
357462
]

0 commit comments

Comments
 (0)