Skip to content

Commit bd1bebf

Browse files
authored
Merge pull request #14 from ioigoume/ldap-module-upgrade
ldap-module-upgrade
2 parents 5599da1 + fbc1432 commit bd1bebf

File tree

6 files changed

+260
-50
lines changed

6 files changed

+260
-50
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"simplesamlphp/assert": "^1.9",
4343
"simplesamlphp/composer-module-installer": "^1.5",
4444
"simplesamlphp/simplesamlphp": "^2.5@dev",
45-
"simplesamlphp/simplesamlphp-module-ldap": "^1.2",
45+
"simplesamlphp/simplesamlphp-module-ldap": "^v2.5.0",
4646
"simplesamlphp/xml-cas-module-slate": "^1.2",
4747
"simplesamlphp/xml-cas": "^2.3",
4848
"simplesamlphp/xml-common": "^2.5",

docs/cas.md

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,84 @@
11
# Using the CAS authentication source with SimpleSAMLphp
22

3-
This is completely based on the original cas authentication,
4-
the only difference is this is authentication module and not a script.
3+
This is completely based on the original CAS authentication;
4+
the only difference is this is an authentication module, not a script.
55

66
## Setting up the CAS authentication module
77

8-
Adding an authentication source
8+
### Adding an authentication source
9+
10+
In new deployments using ldap v2.5+, configure LDAP as a separate authsource in the ldap module and reference it by id from CAS.
911

1012
Example authsource.php:
1113

1214
```php
1315
'example-cas' => [
1416
'cas:CAS',
1517
'cas' => [
16-
'login' => 'https://cas.example.com/login',
17-
'validate' => 'https://cas.example.com/validate',
18-
'logout' => 'https://cas.example.com/logout'
18+
'login' => 'https://cas.example.com/login',
19+
'validate' => 'https://cas.example.com/validate', // CAS v2
20+
'logout' => 'https://cas.example.com/logout',
21+
],
22+
'ldap' => [
23+
'authsource' => 'ldap-backend',
24+
],
25+
],
26+
27+
// LDAP authsource (dnpattern mode)
28+
'ldap-backend' => [
29+
'ldap:Ldap',
30+
31+
// REQUIRED in v2.5: one or more LDAP URLs
32+
'connection_string' => 'ldaps://ldap.example.com',
33+
34+
// Optional extras
35+
'encryption' => 'ssl',
36+
'version' => 3,
37+
'options' => [
38+
'network_timeout' => 3,
39+
'referrals' => false,
40+
],
41+
42+
// Dnpattern mode (no search)
43+
'dnpattern' => 'uid=%username%,cn=people,dc=example,dc=com',
44+
'search.enable' => false,
45+
46+
// 'attributes' => ['uid', 'cn', 'mail'],
47+
]
48+
```
49+
50+
OR:
51+
52+
```php
53+
'example-cas' => [
54+
'cas:CAS',
55+
'cas' => [
56+
'login' => 'https://cas.example.com/login',
57+
'serviceValidate' => 'https://cas.example.com/serviceValidate', // CAS v3
58+
'logout' => 'https://cas.example.com/logout',
1959
],
2060
'ldap' => [
21-
'servers' => 'ldaps://ldaps.example.be:636/',
22-
'enable_tls' => true,
23-
'searchbase' => 'ou=people,dc=org,dc=com',
24-
'searchattributes' => 'uid',
25-
'attributes' => ['uid','cn'],
26-
'priv_user_dn' => 'cn=simplesamlphp,ou=applications,dc=org,dc=com',
27-
'priv_user_pw' => 'password',
61+
'authsource' => 'ldap-backend',
62+
],
63+
],
64+
65+
// LDAP authsource (search mode)
66+
'ldap-backend' => [
67+
'ldap:Ldap',
68+
'connection_string' => 'ldaps://ldap1.example.com ldaps://ldap2.example.com',
69+
'search' => [
70+
'username' => 'cn=simplesamlphp,ou=apps,dc=example,dc=com',
71+
'password' => 'secret',
72+
'base' => ['ou=people,dc=example,dc=com'],
73+
'filter' => '(uid=%username%)',
74+
'scope' => 'sub',
75+
],
76+
'attributes' => ['*'],
77+
'attributes.binary' => ['jpegPhoto'],
78+
'timeout' => 3,
79+
'options' => [
80+
'network_timeout' => 3,
81+
'referrals' => false,
2882
],
2983
],
3084
```
@@ -39,7 +93,7 @@ To get them, call `serviceValidate`, either directly:
3993

4094
```php
4195
'cas' => [
42-
'serviceValidate' => 'https://cas.example.com/serviceValidate',
96+
'serviceValidate' => 'https://cas.example.com/serviceValidate', // CAS v3
4397
]
4498
```
4599

@@ -62,18 +116,18 @@ You can opt in to Slate support:
62116
'serviceValidate' => 'https://cas.example.com/p3/serviceValidate',
63117
// Enable Slate support (optional)
64118
'slate.enabled' => true,
65-
119+
66120
// Optional XPath-based attribute mappings
67121
'attributes' => [
68122
// Standard CAS attributes
69-
'uid' => 'cas:user',
70-
'mail' => 'cas:attributes/cas:mail',
71-
123+
'uid' => 'cas:user',
124+
'mail' => 'cas:attributes/cas:mail',
125+
72126
// Slate namespaced attributes inside cas:attributes
73127
'slate_person' => 'cas:attributes/slate:person',
74128
'slate_round' => 'cas:attributes/slate:round',
75129
'slate_ref' => 'cas:attributes/slate:ref',
76-
130+
77131
// Some deployments also place vendor elements at the top level
78132
'slate_person_top' => '/cas:serviceResponse/cas:authenticationSuccess/slate:person',
79133
],
@@ -105,10 +159,10 @@ for each value:
105159
```php
106160
'cas' => [
107161
'attributes' => [
108-
'uid' => 'cas:user',
109-
'sn' => 'cas:attributes/cas:sn',
162+
'uid' => 'cas:user',
163+
'sn' => 'cas:attributes/cas:sn',
110164
'givenName' => 'cas:attributes/cas:firstname',
111-
'mail' => 'cas:attributes/cas:mail',
165+
'mail' => 'cas:attributes/cas:mail',
112166
],
113167
],
114168
```
@@ -131,3 +185,9 @@ set `ldap` to `null`:
131185
'ldap' => null,
132186
]
133187
```
188+
189+
### Troubleshooting
190+
191+
- Mismatch between validate (v2) and serviceValidate (v3): ensure you use the correct endpoint for your CAS server.
192+
- Attribute mappings: verify XPath keys match your CAS response (case‑sensitive).
193+
- LDAP connection issues: confirm connection_string, credentials, and base DN; consider increasing `network_timeout` while testing.

src/Auth/Source/CAS.php

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use SimpleSAML\Configuration;
1616
use SimpleSAML\Logger;
1717
use SimpleSAML\Module;
18-
use SimpleSAML\Module\ldap\Auth\Ldap;
18+
use SimpleSAML\Module\ldap\Auth\Source\Ldap;
1919
use SimpleSAML\Slate\XML\AuthenticationSuccess as SlateAuthnSuccess;
2020
use SimpleSAML\Slate\XML\ServiceResponse as SlateServiceResponse;
2121
use SimpleSAML\Utils;
@@ -28,7 +28,6 @@
2828
use function preg_split;
2929
use function strcmp;
3030
use function strval;
31-
use function var_export;
3231

3332
/**
3433
* Authenticate using CAS.
@@ -240,13 +239,13 @@ private function casServiceValidate(string $ticket, string $service): array
240239
// array is empty or not set then an empty array will be returned.
241240
$attributesFromQueryConfiguration = $this->parseQueryAttributes($dom);
242241
if (!empty($attributesFromQueryConfiguration)) {
243-
// Overwrite attributes from parseAuthenticationSuccess with configured
244-
// XPath-based attributes, instead of combining them.
242+
// Overwrite attributes from parseAuthenticationSuccess with configured
243+
// XPath-based attributes, instead of combining them.
245244
foreach ($attributesFromQueryConfiguration as $name => $values) {
246-
// Ensure a clean, unique list of string values
245+
// Ensure a clean, unique list of string values
247246
$values = array_values(array_unique(array_map('strval', $values)));
248247

249-
// Configuration wins: replace any existing attribute with the same name
248+
// Configuration wins: replace any existing attribute with the same name
250249
$attributes[$name] = $values;
251250
}
252251
}
@@ -281,35 +280,46 @@ protected function casValidation(string $ticket, string $service): array
281280

282281
/**
283282
* Called by linkback, to finish validate/ finish logging in.
283+
*
284284
* @param array<mixed> $state
285285
*/
286+
286287
public function finalStep(array &$state): void
287288
{
288289
$ticket = $state['cas:ticket'];
289290
$stateId = Auth\State::saveState($state, self::STAGE_INIT);
290291
$service = Module::getModuleURL('cas/linkback.php', ['stateId' => $stateId]);
291-
list($username, $casAttributes) = $this->casValidation($ticket, $service);
292+
293+
[$username, $casAttributes] = $this->casValidation($ticket, $service);
294+
292295
$ldapAttributes = [];
293296

294-
$config = Configuration::loadFromArray(
295-
$this->ldapConfig,
296-
'Authentication source ' . var_export($this->authId, true),
297-
);
298-
if (!empty($this->ldapConfig['servers'])) {
299-
$ldap = new Ldap(
300-
$config->getString('servers'),
301-
$config->getOptionalBoolean('enable_tls', false),
302-
$config->getOptionalBoolean('debug', false),
303-
$config->getOptionalInteger('timeout', 0),
304-
$config->getOptionalInteger('port', 389),
305-
$config->getOptionalBoolean('referrals', true),
306-
);
307-
308-
$ldapAttributes = $ldap->validate($this->ldapConfig, $username);
309-
if ($ldapAttributes === false) {
310-
throw new Exception("Failed to authenticate against LDAP-server.");
297+
// Expect $this->ldapConfig to contain an 'authsource' key when LDAP is desired
298+
$backendId = $this->ldapConfig['authsource'] ?? null;
299+
300+
if ($backendId !== null) {
301+
/** @var \SimpleSAML\Auth\Source|null $source */
302+
$source = Auth\Source::getById($backendId);
303+
if ($source === null) {
304+
throw new Exception('Could not find authentication source with id ' . $backendId);
305+
}
306+
307+
// Ensure we only call getAttributes() on an LDAP authsource that supports it
308+
if (!$source instanceof Ldap) {
309+
throw new Exception(sprintf(
310+
"Configured ldap.authsource '%s' is not an LDAP authsource.",
311+
$backendId,
312+
));
313+
}
314+
315+
try {
316+
$ldapAttributes = $source->getAttributes($username);
317+
} catch (Exception $e) {
318+
Logger::debug('CAS - ldap lookup failed: ' . $e->getMessage());
319+
$ldapAttributes = [];
311320
}
312321
}
322+
313323
$attributes = array_merge_recursive($casAttributes, $ldapAttributes);
314324
$state['Attributes'] = $attributes;
315325
}

tests/config/authsources.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,19 @@
7979
],
8080
'ldap' => [],
8181
],
82+
// LDAP backend used by CAS ldap tests
83+
'ldap-backend' => [
84+
'ldap:Ldap',
85+
'connection_string' => 'ldap://ldap.invalid.example.test', // invalid host to force failure later
86+
'search' => [
87+
'base' => ['dc=example,dc=com'],
88+
'filter' => '(uid=%username%)',
89+
'scope' => 'sub',
90+
],
91+
// Optional:
92+
// 'attributes' => ['*'],
93+
// 'attributes.binary' => [],
94+
// 'timeout' => 3,
95+
// 'options' => ['network_timeout' => 3, 'referrals' => false],
96+
],
8297
];

0 commit comments

Comments
 (0)