Skip to content

Commit db62196

Browse files
authored
Upgrade xml-libraries to their 2.x versions (using typed values) (#62)
* Bump dependencies * Migrate calls to xml-libraries to use the new interface * Use local version of phpcs * Replace dataProvider annotation with attribute * Fix sniffer-issues * Fix phpstan-issues
1 parent c61f843 commit db62196

31 files changed

Lines changed: 394 additions & 267 deletions

.github/workflows/php.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ jobs:
162162
with:
163163
# Should be the higest supported version, so we can use the newest tools
164164
php-version: '8.5'
165-
tools: composer, composer-require-checker, composer-unused, phpcs
165+
tools: composer, composer-require-checker, composer-unused
166166
extensions: ctype, date, dom, fileinfo, filter, hash, intl, mbstring, openssl, pcre, spl, xml
167167

168168
- name: Setup problem matchers for PHP

composer.json

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,19 @@
3737
"ext-SimpleXML": "*",
3838
"ext-session": "*",
3939

40-
"simplesamlphp/assert": "^1.8",
41-
"simplesamlphp/composer-module-installer": "~1.5",
42-
"simplesamlphp/simplesamlphp": "~2.5@RC",
43-
"simplesamlphp/xml-cas": "~1.3",
44-
"simplesamlphp/xml-common": "~1.17",
45-
"simplesamlphp/xml-soap": "~1.5",
46-
"symfony/http-foundation": "^7.4",
47-
"symfony/http-kernel": "^7.4",
48-
"simplesamlphp/saml11": "~1.3"
40+
"beste/clock": "~3.0",
41+
"simplesamlphp/assert": "~1.9",
42+
"simplesamlphp/composer-module-installer": "~1.6",
43+
"simplesamlphp/simplesamlphp": "~2.5@dev",
44+
"simplesamlphp/xml-cas": "~2.0",
45+
"simplesamlphp/xml-common": "~2.0",
46+
"simplesamlphp/xml-soap": "~2.0",
47+
"symfony/http-foundation": "~7.4",
48+
"symfony/http-kernel": "~7.4",
49+
"simplesamlphp/saml11": "~2.0"
4950
},
5051
"require-dev": {
51-
"simplesamlphp/simplesamlphp-test-framework": "^1.11"
52+
"simplesamlphp/simplesamlphp-test-framework": "~1.11"
5253
},
5354
"support": {
5455
"issues": "https://github.com/simplesamlphp/simplesamlphp-module-casserver/issues",

phpstan-baseline.neon

Lines changed: 130 additions & 105 deletions
Large diffs are not rendered by default.

phpstan-dev-baseline.neon

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,43 @@
11
parameters:
22
ignoreErrors:
33
-
4-
message: "#^Property SimpleSAML\\\\Module\\\\casserver\\\\Tests\\\\Controller\\\\Cas10ControllerTest\\:\\:\\$sessionMock is never read, only written\\.$#"
4+
message: '#^Property SimpleSAML\\Module\\casserver\\Tests\\Controller\\Cas10ControllerTest\:\:\$sessionMock is never read, only written\.$#'
5+
identifier: property.onlyWritten
56
count: 1
67
path: tests/src/Controller/Cas10ControllerTest.php
78

89
-
9-
message: "#^Property SimpleSAML\\\\Module\\\\casserver\\\\Tests\\\\Controller\\\\Cas20ControllerTest\\:\\:\\$sessionMock is never read, only written\\.$#"
10+
message: '#^Property SimpleSAML\\Module\\casserver\\Tests\\Controller\\Cas20ControllerTest\:\:\$sessionMock is never read, only written\.$#'
11+
identifier: property.onlyWritten
1012
count: 1
1113
path: tests/src/Controller/Cas20ControllerTest.php
1214

1315
-
14-
message: "#^Property SimpleSAML\\\\Module\\\\casserver\\\\Tests\\\\Controller\\\\Cas20ControllerTest\\:\\:\\$ticketValidatorMock is never read, only written\\.$#"
16+
message: '#^Property SimpleSAML\\Module\\casserver\\Tests\\Controller\\Cas20ControllerTest\:\:\$ticketValidatorMock is never read, only written\.$#'
17+
identifier: property.onlyWritten
1518
count: 1
1619
path: tests/src/Controller/Cas20ControllerTest.php
1720

1821
-
19-
message: "#^Call to an undefined method SimpleSAML\\\\Module\\\\casserver\\\\Cas\\\\TicketValidator\\:\\:expects\\(\\)\\.$#"
22+
message: '#^Call to an undefined method SimpleSAML\\Module\\casserver\\Cas\\TicketValidator\:\:expects\(\)\.$#'
23+
identifier: method.notFound
2024
count: 2
2125
path: tests/src/Controller/Cas30ControllerTest.php
2226

2327
-
24-
message: "#^Property SimpleSAML\\\\Module\\\\casserver\\\\Tests\\\\Controller\\\\Cas30ControllerTest\\:\\:\\$sessionMock is never read, only written\\.$#"
28+
message: '#^Property SimpleSAML\\Module\\casserver\\Tests\\Controller\\Cas30ControllerTest\:\:\$sessionMock is never read, only written\.$#'
29+
identifier: property.onlyWritten
2530
count: 1
2631
path: tests/src/Controller/Cas30ControllerTest.php
2732

2833
-
29-
message: "#^Parameter \\#2 \\$renew of method SimpleSAML\\\\Module\\\\casserver\\\\Controller\\\\LoginController\\:\\:login\\(\\) expects bool, string given\\.$#"
34+
message: '#^Parameter \#2 \$renew of method SimpleSAML\\Module\\casserver\\Controller\\LoginController\:\:login\(\) expects bool, string given\.$#'
35+
identifier: argument.type
3036
count: 1
3137
path: tests/src/Controller/LoginControllerTest.php
3238

3339
-
34-
message: "#^Property SimpleSAML\\\\Module\\\\casserver\\\\Tests\\\\Controller\\\\LoginControllerTest\\:\\:\\$sspContainer is never read, only written\\.$#"
40+
message: '#^Property SimpleSAML\\Module\\casserver\\Tests\\Controller\\LoginControllerTest\:\:\$sspContainer is never read, only written\.$#'
41+
identifier: property.onlyWritten
3542
count: 1
3643
path: tests/src/Controller/LoginControllerTest.php

src/Cas/AttributeExtractor.php

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,23 @@
1616
*/
1717
class AttributeExtractor
1818
{
19-
/** @var Configuration */
20-
private Configuration $casconfig;
21-
22-
/** @var ProcessingChainFactory */
23-
private ProcessingChainFactory $processingChainFactory;
24-
25-
/** @var State */
19+
/** @var \SimpleSAML\Auth\State */
2620
private State $authState;
2721

2822
/**
2923
* ID of the Authentication Source used during authn.
3024
*/
3125
private ?string $authSourceId = null;
3226

27+
3328
public function __construct(
34-
Configuration $casconfig,
35-
ProcessingChainFactory $processingChainFactory,
29+
protected Configuration $casconfig,
30+
protected ProcessingChainFactory $processingChainFactory,
3631
) {
37-
$this->casconfig = $casconfig;
38-
$this->processingChainFactory = $processingChainFactory;
3932
$this->authState = new State();
4033
}
4134

35+
4236
/**
4337
* Determine the user and any CAS attributes based on the attributes from the
4438
* authsource and the CAS configuration.
@@ -97,6 +91,7 @@ public function extractUserAndAttributes(?array $state): array
9791
];
9892
}
9993

94+
10095
/**
10196
* Run authproc filters with the processing chain
10297
* Creating the ProcessingChain require metadata.
@@ -129,14 +124,15 @@ protected function runAuthProcs(array &$state): void
129124
$this->processingChainFactory->build($state)->processState($state);
130125
}
131126

127+
132128
/**
133129
* This is a wrapper around Auth/State::loadState that facilitates testing by
134130
* hiding the static method
135131
*
136132
* @param string $stateId
137133
*
138134
* @return array|null
139-
* @throws NoState
135+
* @throws \SimpleSAML\Error\NoState
140136
*/
141137
public function manageState(string $stateId): ?array
142138
{
@@ -154,6 +150,7 @@ public function manageState(string $stateId): ?array
154150
return $state;
155151
}
156152

153+
157154
/**
158155
* @param string $id
159156
* @param string $stage

src/Cas/CasException.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@
1212
*/
1313
class CasException extends Exception
1414
{
15-
/** @var string */
16-
private string $casCode;
17-
1815
/**
1916
* CasException constructor.
17+
*
2018
* @param string $casCode
2119
* @param string $message
2220
*/
23-
public function __construct(string $casCode, string $message)
24-
{
21+
public function __construct(
22+
protected string $casCode,
23+
string $message,
24+
) {
2525
parent::__construct($message);
26-
$this->casCode = $casCode;
2726
}
2827

28+
2929
/**
3030
* @return string
3131
*/

src/Cas/Factories/ProcessingChainFactory.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616

1717
class ProcessingChainFactory
1818
{
19-
/** @var Configuration */
20-
private Configuration $casconfig;
21-
2219
public function __construct(
23-
Configuration $casconfig,
20+
protected Configuration $casconfig,
2421
) {
25-
$this->casconfig = $casconfig;
2622
}
2723

24+
2825
/**
2926
* @codeCoverageIgnore
3027
* @throws \Exception

src/Cas/Protocol/Cas20.php

Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,33 @@
2525

2626
namespace SimpleSAML\Module\casserver\Cas\Protocol;
2727

28-
use DateTimeImmutable;
29-
use SimpleSAML\CAS\XML\cas\Attributes;
30-
use SimpleSAML\CAS\XML\cas\AuthenticationDate;
31-
use SimpleSAML\CAS\XML\cas\AuthenticationFailure;
32-
use SimpleSAML\CAS\XML\cas\AuthenticationSuccess;
33-
use SimpleSAML\CAS\XML\cas\IsFromNewLogin;
34-
use SimpleSAML\CAS\XML\cas\LongTermAuthenticationRequestTokenUsed;
35-
use SimpleSAML\CAS\XML\cas\ProxyFailure;
36-
use SimpleSAML\CAS\XML\cas\ProxyGrantingTicket;
37-
use SimpleSAML\CAS\XML\cas\ProxySuccess;
38-
use SimpleSAML\CAS\XML\cas\ProxyTicket;
39-
use SimpleSAML\CAS\XML\cas\ServiceResponse;
40-
use SimpleSAML\CAS\XML\cas\User;
28+
use Beste\Clock\LocalizedClock;
29+
use DateTimeZone;
30+
use SimpleSAML\Assert\AssertionFailedException;
31+
use SimpleSAML\CAS\Type\CodeValue;
32+
use SimpleSAML\CAS\XML\Attributes;
33+
use SimpleSAML\CAS\XML\AuthenticationDate;
34+
use SimpleSAML\CAS\XML\AuthenticationFailure;
35+
use SimpleSAML\CAS\XML\AuthenticationSuccess;
36+
use SimpleSAML\CAS\XML\IsFromNewLogin;
37+
use SimpleSAML\CAS\XML\LongTermAuthenticationRequestTokenUsed;
38+
use SimpleSAML\CAS\XML\ProxyFailure;
39+
use SimpleSAML\CAS\XML\ProxyGrantingTicket;
40+
use SimpleSAML\CAS\XML\ProxySuccess;
41+
use SimpleSAML\CAS\XML\ProxyTicket;
42+
use SimpleSAML\CAS\XML\ServiceResponse;
43+
use SimpleSAML\CAS\XML\User;
4144
use SimpleSAML\Configuration;
4245
use SimpleSAML\Logger;
46+
use SimpleSAML\XML\Assert\Assert;
4347
use SimpleSAML\XML\Chunk;
4448
use SimpleSAML\XML\DOMDocumentFactory;
49+
use SimpleSAML\XMLSchema\Type\BooleanValue;
50+
use SimpleSAML\XMLSchema\Type\DateTimeValue;
51+
use SimpleSAML\XMLSchema\Type\StringValue;
4552

4653
use function base64_encode;
4754
use function count;
48-
use function filter_var;
4955
use function is_null;
5056
use function is_string;
5157
use function str_replace;
@@ -117,27 +123,28 @@ public function getProxyGrantingTicketIOU(): ?string
117123

118124
/**
119125
* @param string $username
120-
* @return \SimpleSAML\CAS\XML\cas\ServiceResponse
126+
* @return \SimpleSAML\CAS\XML\ServiceResponse
121127
*/
122128
public function getValidateSuccessResponse(string $username): ServiceResponse
123129
{
124-
$user = new User($username);
130+
$user = new User(StringValue::fromString($username));
125131

126132
$proxyGrantingTicket = null;
127133
if (is_string($this->proxyGrantingTicketIOU)) {
128-
$proxyGrantingTicket = new ProxyGrantingTicket($this->proxyGrantingTicketIOU);
134+
$proxyGrantingTicket = new ProxyGrantingTicket(StringValue::fromString($this->proxyGrantingTicketIOU));
129135
}
130136

131137
$attr = [];
132138
if ($this->sendAttributes && count($this->attributes) > 0) {
133139
foreach ($this->attributes as $name => $values) {
134140
// Fix the most common cause of invalid XML elements
135141
$_name = str_replace(':', '_', $name);
136-
if ($this->isValidXmlName($_name) === true) {
142+
try {
143+
Assert::validNCName($_name);
137144
foreach ($values as $value) {
138145
$attr[] = $this->generateCas20Attribute($_name, $value);
139146
}
140-
} else {
147+
} catch (AssertionFailedException) {
141148
Logger::warning("DOMException creating attribute '$_name'. Continuing without attribute'");
142149
}
143150
}
@@ -150,10 +157,11 @@ public function getValidateSuccessResponse(string $username): ServiceResponse
150157
}
151158
}
152159

160+
$systemClock = LocalizedClock::in(new DateTimeZone('Z'));
153161
$attributes = new Attributes(
154-
new AuthenticationDate(new DateTimeImmutable('now')),
155-
new LongTermAuthenticationRequestTokenUsed('true'),
156-
new IsFromNewLogin('true'),
162+
new AuthenticationDate(DateTimeValue::now($systemClock)),
163+
new LongTermAuthenticationRequestTokenUsed(BooleanValue::fromBoolean(true)),
164+
new IsFromNewLogin(BooleanValue::fromBoolean(true)),
157165
$attr,
158166
);
159167

@@ -167,11 +175,14 @@ public function getValidateSuccessResponse(string $username): ServiceResponse
167175
/**
168176
* @param string $errorCode
169177
* @param string $explanation
170-
* @return \SimpleSAML\CAS\XML\cas\ServiceResponse
178+
* @return \SimpleSAML\CAS\XML\ServiceResponse
171179
*/
172180
public function getValidateFailureResponse(string $errorCode, string $explanation): ServiceResponse
173181
{
174-
$authenticationFailure = new AuthenticationFailure($explanation, $errorCode);
182+
$authenticationFailure = new AuthenticationFailure(
183+
StringValue::fromString($explanation),
184+
CodeValue::fromString($errorCode),
185+
);
175186
$serviceResponse = new ServiceResponse($authenticationFailure);
176187

177188
return $serviceResponse;
@@ -180,11 +191,11 @@ public function getValidateFailureResponse(string $errorCode, string $explanatio
180191

181192
/**
182193
* @param string $proxyTicketId
183-
* @return \SimpleSAML\CAS\XML\cas\ServiceResponse
194+
* @return \SimpleSAML\CAS\XML\ServiceResponse
184195
*/
185196
public function getProxySuccessResponse(string $proxyTicketId): ServiceResponse
186197
{
187-
$proxyTicket = new ProxyTicket($proxyTicketId);
198+
$proxyTicket = new ProxyTicket(StringValue::fromString($proxyTicketId));
188199
$proxySuccess = new ProxySuccess($proxyTicket);
189200
$serviceResponse = new ServiceResponse($proxySuccess);
190201

@@ -195,11 +206,14 @@ public function getProxySuccessResponse(string $proxyTicketId): ServiceResponse
195206
/**
196207
* @param string $errorCode
197208
* @param string $explanation
198-
* @return \SimpleSAML\CAS\XML\cas\ServiceResponse
209+
* @return \SimpleSAML\CAS\XML\ServiceResponse
199210
*/
200211
public function getProxyFailureResponse(string $errorCode, string $explanation): ServiceResponse
201212
{
202-
$proxyFailure = new ProxyFailure($explanation, $errorCode);
213+
$proxyFailure = new ProxyFailure(
214+
StringValue::fromString($explanation),
215+
CodeValue::fromString($errorCode),
216+
);
203217
$serviceResponse = new ServiceResponse($proxyFailure);
204218

205219
return $serviceResponse;
@@ -222,26 +236,4 @@ private function generateCas20Attribute(
222236

223237
return new Chunk($attributeElement);
224238
}
225-
226-
227-
/**
228-
* XML element names have a lot of rules and not every SAML attribute name can be converted.
229-
* Ref: https://www.w3.org/TR/REC-xml/#NT-NameChar
230-
* https://stackoverflow.com/q/2519845/54396
231-
* must only start with letter or underscore
232-
* cannot start with 'xml' (or maybe it can - stackoverflow commenters don't agree)
233-
* cannot contain a ':' since those are for namespaces
234-
* cannot contains space
235-
* can only contain letters, digits, hyphens, underscores, and periods
236-
* @param string $name The attribute name to be used as an element
237-
* @return bool true if $name would make a valid xml element.
238-
*/
239-
private function isValidXmlName(string $name): bool
240-
{
241-
return filter_var(
242-
$name,
243-
FILTER_VALIDATE_REGEXP,
244-
['options' => ['regexp' => '/^[a-zA-Z_][\w.-]*$/']],
245-
) !== false;
246-
}
247239
}

0 commit comments

Comments
 (0)