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
20 changes: 12 additions & 8 deletions .github/workflows/analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
coverage: xdebug
tools: composer:v2.2
php-version: '8.5'
extensions: openssl, mbstring
coverage: pcov
tools: composer:2.9

- name: Get Composer Cache Directory
id: composer-cache
shell: bash
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Dependencies
Expand All @@ -27,16 +29,18 @@ jobs:
restore-keys: ${{ runner.os }}-composer-

- name: Install Composer Dependencies
run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader
run: composer install --no-progress --prefer-dist --optimize-autoloader

- name: Run Static Analysis
run: |
composer run-script analyse
composer run-script analyse-tests

- name: Run Test Coverage
run: composer run-script test-coverage

- name: Run Static Analysis
run: composer run-script analyse

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
fail-fast: false
matrix:
operating-system: [ubuntu-latest, windows-latest]
php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
php-versions: ['8.2', '8.3', '8.4', '8.5']
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
steps:
- name: Checkout
Expand All @@ -17,8 +17,9 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
coverage: xdebug
tools: composer:v2.2
extensions: openssl, mbstring
coverage: none
tools: composer:2.9

- name: Get Composer Cache Directory
id: composer-cache
Expand All @@ -33,7 +34,7 @@ jobs:
restore-keys: ${{ runner.os }}-composer-

- name: Install Composer dependencies
run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader
run: composer install --no-progress --prefer-dist --optimize-autoloader

- name: Run Unit Tests
run: composer run-script test
run: composer run-script test-unit
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
composer.lock
composer.phar
vendor/
.phpunit.result.cache
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
CHANGELOG
=========

1.0.0 (2026-XX-XX)
------------------
* Raise the minimum PHP version to 8.2.
* Introduce strict types, typed properties, and `final` across classes.
* Replace mechanism-name string constants with the `MechanismName` backed enum (see UPGRADE-1.0.md).
* Replace SCRAM hash-algorithm string constants with the `HashAlgorithm` enum.
* Replace DIGEST-MD5 message-type integer constants with the `DigestMD5MessageType` enum.
* Replace DIGEST-MD5 cipher string keys with the `DigestMD5Cipher` enum.

0.2.1 (2026-03-22)
------------------
* Allow specifying server vs client mode when creating a challenge.
Expand Down
39 changes: 39 additions & 0 deletions UPGRADE-1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Upgrading from 0.x to 1.0
=========================

## Mechanism names are now an enum

The `string` constants previously defined on each mechanism class have been replaced by the `FreeDSx\Sasl\Mechanism\MechanismName` backed enum.

| Old | New |
|------------------------------------------|-------------------------------------------------------------------|
| `AnonymousMechanism::NAME` | `MechanismName::ANONYMOUS` |
| `PlainMechanism::NAME` | `MechanismName::PLAIN` |
| `CramMD5Mechanism::NAME` | `MechanismName::CRAM_MD5` |
| `DigestMD5Mechanism::NAME` | `MechanismName::DIGEST_MD5` |
| `ScramMechanism::SHA1` … `SHA3_512_PLUS` | `MechanismName::SCRAM_SHA1` … `SCRAM_SHA3_512_PLUS` |
| `ScramMechanism::VARIANTS` | `MechanismName::cases()` filtered with `MechanismName::isScram()` |

## `Sasl` registry takes enum values

```php
// before
$sasl->get('DIGEST-MD5');
$sasl->supports('SCRAM-SHA-256');
$sasl->remove('PLAIN');
$sasl->select(['SCRAM-SHA-256', 'PLAIN']);
new Sasl(['supported' => ['DIGEST-MD5']]);

// after
use FreeDSx\Sasl\Mechanism\MechanismName;

$sasl->get(MechanismName::DIGEST_MD5);
$sasl->supports(MechanismName::SCRAM_SHA256);
$sasl->remove(MechanismName::PLAIN);
$sasl->select([MechanismName::SCRAM_SHA256, MechanismName::PLAIN]);
new Sasl(['supported' => [MechanismName::DIGEST_MD5]]);
```

## Concrete classes are `final`

All concrete classes in the library are now marked `final`. If you were extending any of these, switch to composition instead.
41 changes: 30 additions & 11 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"keywords": [
"SASL",
"DIGEST-MD5",
"CRAM-MD5"
"CRAM-MD5",
"SCRAM"
],
"license": "MIT",
"authors": [
Expand All @@ -15,13 +16,16 @@
}
],
"require": {
"php": ">=7.1"
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^7.0|^8.5|^9.5",
"phpstan/phpstan": "^0.11|^0.12",
"symplify/easy-coding-standard": ">=6.1",
"symfony/polyfill-php80": "^1.27"
"phpunit/phpunit": "^11.5",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/extension-installer": "^1.4",
"symplify/easy-coding-standard": "^9.4",
"squizlabs/php_codesniffer": "^3.7",
"slevomat/coding-standard": "^7.2"
},
"suggest": {
"ext-openssl": "Needed for encryption for certain mechanisms."
Expand All @@ -30,17 +34,32 @@
"psr-4": {"FreeDSx\\Sasl\\": "src/FreeDSx/Sasl"}
},
"autoload-dev": {
"psr-4": {"unit\\FreeDSx\\Sasl\\": "tests/unit/FreeDSx/Sasl"}
"psr-4": {
"Tests\\Unit\\FreeDSx\\Sasl\\": "tests/unit"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
}
},
"scripts": {
"test": [
"phpunit"
"test-unit": [
"@php -d xdebug.mode=off vendor/bin/phpunit --testsuite unit"
],
"test-coverage": [
"phpunit --coverage-clover=coverage.xml"
"@php -d xdebug.mode=coverage vendor/bin/phpunit --testsuite unit --coverage-clover=coverage.xml"
],
"analyse": [
"phpstan analyse"
"phpstan --memory-limit=-1 analyse"
],
"analyse-tests": [
"phpstan --memory-limit=-1 analyse -c phpstan.tests.neon"
],
"cs-fix": [
"phpcbf --standard=ruleset.xml --extensions=php --tab-width=4 -sp src",
"ecs --fix"
]
}
}
30 changes: 30 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Fixer\ArrayNotation\ArraySyntaxFixer;
use PhpCsFixer\Fixer\Import\NoUnusedImportsFixer;
use PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer;
use Symplify\EasyCodingStandard\ValueObject\Option;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList;

return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [
__DIR__ . '/src',
__DIR__ . '/tests',
]);

$services = $containerConfigurator->services();
$services->set(ArraySyntaxFixer::class)
->call('configure', [[
'syntax' => 'short',
]]);
$services->set(BlankLineAfterStrictTypesFixer::class);
$services->set(BlankLineAfterOpeningTagFixer::class);
$services->set(NoUnusedImportsFixer::class);

$containerConfigurator->import(SetList::PSR_12);
};
2 changes: 0 additions & 2 deletions ecs.yml

This file was deleted.

9 changes: 9 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
parameters:
# Level 6 for now. Bumping to 9 (matching LDAP-fork) is gated on the planned
# Options DTO refactor — most level 7-9 noise comes from the array<string, mixed>
# accessors on Message and SaslContext.
level: 6
paths:
- %currentWorkingDirectory%/src
treatPhpDocTypesAsCertain: false
reportUnmatchedIgnoredErrors: false
6 changes: 0 additions & 6 deletions phpstan.neon.dist

This file was deleted.

5 changes: 5 additions & 0 deletions phpstan.tests.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
treatPhpDocTypesAsCertain: false
level: 10
paths:
- %currentWorkingDirectory%/tests
34 changes: 13 additions & 21 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="vendor/autoload.php"
>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="FreeDSx SASL Unit Tests">
<directory>./tests/unit</directory>
</testsuite>
</testsuites>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" colors="true" displayDetailsOnTestsThatTriggerDeprecations="true" displayDetailsOnTestsThatTriggerErrors="true" displayDetailsOnTestsThatTriggerNotices="true" displayDetailsOnTestsThatTriggerWarnings="true" displayDetailsOnPhpunitDeprecations="true" processIsolation="false" stopOnFailure="false" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd">
<source ignoreIndirectDeprecations="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
<coverage includeUncoveredFiles="true" pathCoverage="false" ignoreDeprecatedCodeUnits="true" disableCodeCoverageIgnore="true">
</coverage>
<testsuites>
<testsuite name="unit">
<directory>./tests/unit</directory>
</testsuite>
</testsuites>
</phpunit>
17 changes: 17 additions & 0 deletions ruleset.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<ruleset name="FreeDSxSasl">
<config name="installed_paths" value="../../slevomat/coding-standard" />
<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly" />
<rule ref="SlevomatCodingStandard.Namespaces.NamespaceSpacing" />

<rule ref="PSR12">
<exclude name="PSR2.Namespaces.UseDeclaration.SpaceAfterLastUse"/>
<exclude name="PSR12.Files.FileHeader"/>
<exclude name="PSR2.Namespaces.NamespaceDeclaration.BlankLineAfter"/>
<exclude name="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterVariadic"/>
<exclude name="PSR12.Functions.ReturnTypeDeclaration"/>
<exclude name="PSR12.Traits.UseDeclaration"/>
<exclude name="PSR12.Functions.NullableTypeDeclaration.WhitespaceFound"/>
<exclude name="PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace"/>
</rule>
</ruleset>
37 changes: 17 additions & 20 deletions src/FreeDSx/Sasl/Challenge/AnonymousChallenge.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/**
* This file is part of the FreeDSx SASL package.
*
Expand All @@ -20,17 +22,11 @@
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*/
class AnonymousChallenge implements ChallengeInterface
final readonly class AnonymousChallenge implements ChallengeInterface
{
/**
* @var SaslContext
*/
protected $context;
private readonly SaslContext $context;

/**
* @var AnonymousEncoder
*/
protected $encoder;
private readonly AnonymousEncoder $encoder;

public function __construct(bool $isServerMode = false)
{
Expand All @@ -39,11 +35,10 @@ public function __construct(bool $isServerMode = false)
$this->context->setIsServerMode($isServerMode);
}

/**
* {@inheritDoc}
*/
public function challenge(?string $received = null, array $options = []): SaslContext
{
public function challenge(
?string $received = null,
array $options = [],
): SaslContext {
if ($this->context->isServerMode()) {
$this->processServer($received);
} else {
Expand All @@ -53,25 +48,27 @@ public function challenge(?string $received = null, array $options = []): SaslCo
return $this->context;
}

protected function processServer(?string $received): void
private function processServer(?string $received): void
{
if ($received === null) {
return;
}
$received = $this->encoder->decode($received, $this->context);
$message = $this->encoder->decode($received, $this->context);

$this->context->setIsComplete(true);
$this->context->setIsAuthenticated(true);

if ($received->has('trace')) {
$this->context->set('trace', $received->get('trace'));
if ($message->has('trace')) {
$this->context->set('trace', $message->get('trace'));
}
}

protected function processClient(array $options): void
/**
* @param array<string, mixed> $options
*/
private function processClient(array $options): void
{
$data = [];

if (isset($options['username']) || isset($options['trace'])) {
$data['trace'] = $options['trace'] ?? $options['username'];
}
Expand Down
Loading
Loading