Skip to content

Commit 19dfbea

Browse files
committed
Support alternate service and ticket param names
For some reason (unknown to me) the Apereo java CAS client supports configuring different query param names for 'service' and 'ticket'. The combinations we have encountered in the wild for these are TARGET and SAMLart. It is a requirement in Banner to use these alernative names for "security".
1 parent 0c0f2d4 commit 19dfbea

4 files changed

Lines changed: 103 additions & 19 deletions

File tree

tests/config/module_casserver.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
'|https://override.example.com/|' => [
2828
'attrname' => 'uid',
2929
'attributes_to_transfer' => ['cn'],
30+
],
31+
'http://changeTicketParam' => [
32+
'ticketName' => 'myTicket',
3033
]
3134
],
3235

tests/www/LoginIntegrationTest.php

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Simplesamlphp\Casserver;
44

55
use DOMDocument;
6+
use PHPUnit\Framework\TestCase;
67
use SimpleSAML\Test\BuiltInServer;
78

89
/**
@@ -13,11 +14,14 @@
1314
*
1415
* @package Simplesamlphp\Casserver
1516
*/
16-
class LoginIntegrationTest extends \PHPUnit\Framework\TestCase
17+
class LoginIntegrationTest extends TestCase
1718
{
1819
/** @var string $LINK_URL */
1920
private static $LINK_URL = '/module.php/casserver/login.php';
2021

22+
/** @var string $VALIDATE_URL */
23+
private static $VALIDATE_URL = '/module.php/casserver/serviceValidate.php';
24+
2125
/**
2226
* @var string $SAMLVALIDATE_URL
2327
*/
@@ -129,10 +133,13 @@ public function testWrongServiceUrl()
129133

130134

131135
/**
132-
* test a valid service URL
136+
* Test a valid service URL
137+
* @dataProvider validServiceUrlProvider
138+
* @param string $serviceParam The name of the query parameter to use for the service url
139+
* @param string $ticketParam The name of the query parameter that will contain the ticket
133140
* @return void
134141
*/
135-
public function testValidServiceUrl()
142+
public function testValidServiceUrl(string $serviceParam, string $ticketParam)
136143
{
137144
$service_url = 'http://host1.domain:1234/path1';
138145

@@ -141,7 +148,7 @@ public function testValidServiceUrl()
141148
/** @var array $resp */
142149
$resp = $this->server->get(
143150
self::$LINK_URL,
144-
['service' => $service_url],
151+
[$serviceParam => $service_url],
145152
[
146153
CURLOPT_COOKIEJAR => $this->cookies_file,
147154
CURLOPT_COOKIEFILE => $this->cookies_file
@@ -150,7 +157,71 @@ public function testValidServiceUrl()
150157
$this->assertEquals(302, $resp['code']);
151158

152159
$this->assertStringStartsWith(
153-
$service_url . '?ticket=ST-',
160+
$service_url . '?' . $ticketParam . '=ST-',
161+
$resp['headers']['Location'],
162+
'Ticket should be part of the redirect.'
163+
);
164+
165+
// Config ticket can be validated
166+
$matches = [];
167+
$this->assertEquals(1, preg_match("@$ticketParam=(.*)@", $resp['headers']['Location'], $matches));
168+
$ticket = $matches[1];
169+
$resp = $this->server->get(
170+
self::$VALIDATE_URL,
171+
[
172+
$serviceParam => $service_url,
173+
'ticket' => $ticket,
174+
],
175+
[
176+
CURLOPT_COOKIEJAR => $this->cookies_file,
177+
CURLOPT_COOKIEFILE => $this->cookies_file
178+
]
179+
);
180+
$expectedResponse = '<?xml version="1.0"?>
181+
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
182+
<cas:authenticationSuccess>
183+
<cas:user>testuser@example.com</cas:user>
184+
<cas:attributes>
185+
<cas:eduPersonPrincipalName>testuser@example.com</cas:eduPersonPrincipalName>
186+
<cas:base64Attributes>false</cas:base64Attributes>
187+
</cas:attributes>
188+
</cas:authenticationSuccess>
189+
</cas:serviceResponse>';
190+
$this->assertEquals(200, $resp['code']);
191+
$this->assertEquals($expectedResponse, $resp['body']);
192+
}
193+
194+
public function validServiceUrlProvider(): array
195+
{
196+
return [
197+
['service', 'ticket'],
198+
['TARGET', 'SAMLart']
199+
];
200+
}
201+
202+
/**
203+
* Test changing the ticket name
204+
* @return void
205+
*/
206+
public function testValidTicketNameOverride()
207+
{
208+
$service_url = 'http://changeTicketParam/abc';
209+
210+
$this->authenticate();
211+
212+
/** @var array $resp */
213+
$resp = $this->server->get(
214+
self::$LINK_URL,
215+
['TARGET' => $service_url],
216+
[
217+
CURLOPT_COOKIEJAR => $this->cookies_file,
218+
CURLOPT_COOKIEFILE => $this->cookies_file
219+
]
220+
);
221+
$this->assertEquals(302, $resp['code']);
222+
223+
$this->assertStringStartsWith(
224+
$service_url . '?myTicket=ST-',
154225
$resp['headers']['Location'],
155226
'Ticket should be part of the redirect.'
156227
);

www/login.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,16 @@
5050
$casconfig = Configuration::getConfig('module_casserver.php');
5151
$serviceValidator = new ServiceValidator($casconfig);
5252

53-
if (isset($_GET['service'])) {
54-
$serviceCasConfig = $serviceValidator->checkServiceURL(sanitize($_GET['service']));
53+
$serviceUrl = $_GET['service'] ?? $_GET['TARGET'] ?? null;
54+
55+
if (isset($serviceUrl)) {
56+
$serviceCasConfig = $serviceValidator->checkServiceURL(sanitize($serviceUrl));
5557
if (isset($serviceCasConfig)) {
5658
// Override the cas configuration to use for this service
5759
$casconfig = $serviceCasConfig;
5860
} else {
5961
$message = 'Service parameter provided to CAS server is not listed as a legal service: [service] = ' .
60-
var_export($_GET['service'], true);
62+
var_export($serviceUrl, true);
6163
Logger::debug('casserver:' . $message);
6264

6365
throw new \Exception($message);
@@ -113,6 +115,10 @@
113115
$query['service'] = $_REQUEST['service'];
114116
}
115117

118+
if (isset($_REQUEST['TARGET'])) {
119+
$query['TARGET'] = $_REQUEST['TARGET'];
120+
}
121+
116122
if (isset($_REQUEST['method'])) {
117123
$query['method'] = $_REQUEST['method'];
118124
}
@@ -174,12 +180,15 @@
174180
}
175181
}
176182

177-
if (isset($_GET['service'])) {
183+
if (isset($serviceUrl)) {
184+
$defaultTicketName = isset($_GET['service']) ? 'ticket' : 'SAMLart';
185+
$ticketName = $casconfig->getValue('ticketName', $defaultTicketName);
186+
178187
$attributeExtractor = new AttributeExtractor();
179188
$mappedAttributes = $attributeExtractor->extractUserAndAttributes($as->getAttributes(), $casconfig);
180189

181190
$serviceTicket = $ticketFactory->createServiceTicket([
182-
'service' => $_GET['service'],
191+
'service' => $serviceUrl,
183192
'forceAuthn' => $forceAuthn,
184193
'userName' => $mappedAttributes['user'],
185194
'attributes' => $mappedAttributes['attributes'],
@@ -189,7 +198,7 @@
189198

190199
$ticketStore->addTicket($serviceTicket);
191200

192-
$parameters['ticket'] = $serviceTicket['id'];
201+
$parameters[$ticketName] = $serviceTicket['id'];
193202

194203
$validDebugModes = ['true', 'samlValidate'];
195204
if (
@@ -205,7 +214,7 @@
205214
} else {
206215
$method = 'serviceValidate';
207216
// Fake some options for validateTicket
208-
$_GET['ticket'] = $serviceTicket['id'];
217+
$_GET[$ticketName] = $serviceTicket['id'];
209218
// We want to capture the output from echo used in validateTicket
210219
ob_start();
211220
require_once 'utility/validateTicket.php';
@@ -214,9 +223,9 @@
214223
echo '<pre>' . htmlspecialchars($casResponse) . '</pre>';
215224
}
216225
} elseif ($redirect) {
217-
HTTP::redirectTrustedURL(HTTP::addURLParameters($_GET['service'], $parameters));
226+
HTTP::redirectTrustedURL(HTTP::addURLParameters($serviceUrl, $parameters));
218227
} else {
219-
HTTP::submitPOSTData($_GET['service'], $parameters);
228+
HTTP::submitPOSTData($serviceUrl, $parameters);
220229
}
221230
} else {
222231
HTTP::redirectTrustedURL(

www/utility/validateTicket.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@
4141
/** @var Cas20 $protocol */
4242
/** @psalm-suppress InvalidStringClass */
4343
$protocol = new $protocolClass($casconfig);
44+
$serviceUrl = $_GET['service'] ?? $_GET['TARGET'] ?? null;
4445

45-
if (array_key_exists('service', $_GET) && array_key_exists('ticket', $_GET)) {
46+
if (isset($serviceUrl) && array_key_exists('ticket', $_GET)) {
4647
$forceAuthn = isset($_GET['renew']) && $_GET['renew'];
4748

4849
try {
@@ -73,7 +74,7 @@
7374

7475
if (
7576
!$ticketFactory->isExpired($serviceTicket) &&
76-
sanitize($serviceTicket['service']) == sanitize($_GET['service']) &&
77+
sanitize($serviceTicket['service']) == sanitize($serviceUrl) &&
7778
(!$forceAuthn || $serviceTicket['forceAuthn'])
7879
) {
7980
$protocol->setAttributes($attributes);
@@ -91,7 +92,7 @@
9192
'userName' => $serviceTicket['userName'],
9293
'attributes' => $attributes,
9394
'forceAuthn' => false,
94-
'proxies' => array_merge([$_GET['service']], $serviceTicket['proxies']),
95+
'proxies' => array_merge([$serviceUrl], $serviceTicket['proxies']),
9596
'sessionId' => $serviceTicket['sessionId']
9697
]);
9798
try {
@@ -116,10 +117,10 @@
116117

117118
echo $protocol->getValidateFailureResponse('INVALID_TICKET', $message);
118119
} else {
119-
if (sanitize($serviceTicket['service']) != sanitize($_GET['service'])) {
120+
if (sanitize($serviceTicket['service']) != sanitize($serviceUrl)) {
120121
$message = 'Mismatching service parameters: expected ' .
121122
var_export($serviceTicket['service'], true) .
122-
' but was: ' . var_export($_GET['service'], true);
123+
' but was: ' . var_export($serviceUrl, true);
123124

124125
\SimpleSAML\Logger::debug('casserver:' . $message);
125126

0 commit comments

Comments
 (0)