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
52 changes: 42 additions & 10 deletions src/FreeDSx/Ldap/Control/ProxyAuthorizationControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

use FreeDSx\Asn1\Type\AbstractType;
use FreeDSx\Asn1\Type\SequenceType;
use FreeDSx\Ldap\Exception\InvalidArgumentException;
use FreeDSx\Ldap\Exception\ProtocolException;
use FreeDSx\Ldap\Protocol\Authorization\AuthzId;

/**
* Represents a proxied authorization control. RFC 4370.
Expand All @@ -26,34 +28,51 @@
*/
final class ProxyAuthorizationControl extends Control
{
private string $rawAuthzId;

private ?AuthzId $authzId;

public function __construct(
private string $authzId = '',
AuthzId $authzId,
bool $criticality = true,
) {
$this->setAuthzId($authzId);

parent::__construct(
self::OID_PROXY_AUTHORIZATION,
$criticality,
);
}

/**
* The authorization identity to assume: an authzId ("dn:..." / "u:..."), or empty for anonymous.
* The authorization identity to assume; lazily parsed from the raw value when decoded from a request.
*
* @throws InvalidArgumentException when the raw wire value is not a valid authzId form (e.g. a malformed request)
*/
public function getAuthzId(): string
public function getAuthzId(): AuthzId
{
return $this->authzId;
return $this->authzId ??= AuthzId::fromString($this->rawAuthzId);
}

public function setAuthzId(string $authzId): self
/**
* The raw authzId wire value, which may be an invalid form when decoded from a request.
*/
public function getRawAuthzId(): string
{
return $this->rawAuthzId;
}

public function setAuthzId(AuthzId $authzId): self
{
$this->authzId = $authzId;
$this->rawAuthzId = $authzId->toString();

return $this;
}

public function toAsn1(): SequenceType
{
$this->controlValue = $this->authzId;
$this->controlValue = $this->rawAuthzId;

return parent::toAsn1();
}
Expand All @@ -73,10 +92,23 @@ public static function fromAsn1(AbstractType $type): static
}

[1 => $criticality, 2 => $authzId] = self::parseAsn1ControlValues($type);
$rawAuthzId = $authzId ?? '';

return new static(
$authzId ?? '',
$criticality,
);
try {
return new static(
AuthzId::fromString($rawAuthzId),
$criticality,
);
} catch (InvalidArgumentException) {
// Preserve a malformed value so it surfaces as an authorization denial rather than a decode failure.
$control = new static(
AuthzId::anonymous(),
$criticality,
);
$control->rawAuthzId = $rawAuthzId;
$control->authzId = null;

return $control;
}
}
}
5 changes: 3 additions & 2 deletions src/FreeDSx/Ldap/Controls.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use FreeDSx\Ldap\Control\SubentriesControl;
use FreeDSx\Ldap\Control\Sync\SyncRequestControl;
use FreeDSx\Ldap\Control\Vlv\VlvControl;
use FreeDSx\Ldap\Protocol\Authorization\AuthzId;
use FreeDSx\Ldap\Protocol\ProtocolElementInterface;
use FreeDSx\Ldap\Search\Filter\FilterInterface;
use FreeDSx\Ldap\Search\Filter\GreaterThanOrEqualFilter;
Expand Down Expand Up @@ -184,9 +185,9 @@ public static function relaxRules(bool $criticality = true): Control
}

/**
* Create a Proxied Authorization control to run the operation as the given authzId ("dn:..." / "u:...", or empty for anonymous).
* Create a Proxied Authorization control to run the operation as the given authzId (use AuthzId::anonymous() for anonymous).
*/
public static function proxyAuthorization(string $authzId = ''): ProxyAuthorizationControl
public static function proxyAuthorization(AuthzId $authzId): ProxyAuthorizationControl
{
return new ProxyAuthorizationControl($authzId);
}
Expand Down
5 changes: 5 additions & 0 deletions src/FreeDSx/Ldap/Protocol/Authorization/AuthzId.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public static function fromString(string $authzId): self
};
}

public static function anonymous(): self
{
return new self(AuthzIdType::Anonymous);
}

public static function fromDn(Dn $dn): self
{
return new self(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,19 @@ public function resolve(
);
}

$rawAuthzId = $control->getAuthzId();

if (!$token instanceof AuthenticatedTokenInterface) {
$this->authzIdResolver->deny(
$token,
$rawAuthzId,
$control->getRawAuthzId(),
);
}

try {
$authzId = AuthzId::fromString($rawAuthzId);
$authzId = $control->getAuthzId();
} catch (InvalidArgumentException) {
$this->authzIdResolver->deny(
$token,
$rawAuthzId,
$control->getRawAuthzId(),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace FreeDSx\Ldap\Protocol\ServerProtocolHandler;

use Exception;
use FreeDSx\Asn1\Exception\EncoderException;
use FreeDSx\Ldap\Operation\LdapResult;
use FreeDSx\Ldap\Operation\Response\ExtendedResponse;
Expand Down Expand Up @@ -46,14 +45,7 @@ public function handleRequest(
$userId = null;

if ($token instanceof AuthenticatedTokenInterface) {
$resolvedDn = $token->getResolvedDn();

try {
$resolvedDn->toArray();
$userId = 'dn:' . $resolvedDn->toString();
} catch (Exception) {
$userId = 'u:' . $token->getUsername();
}
$userId = $token->getAuthzId()->toString();
}

$this->queue->sendMessage(new LdapMessageResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use FreeDSx\Ldap\Exception\OperationException;
use FreeDSx\Ldap\Operation\ResultCode;
use FreeDSx\Ldap\Protocol\Authorization\AuthzId;
use FreeDSx\Ldap\Server\Backend\Auth\NameResolver\BindNameResolverInterface;
use FreeDSx\Ldap\Server\Backend\LdapBackendInterface;
use FreeDSx\Ldap\Server\Token\AuthenticatedTokenInterface;
Expand Down Expand Up @@ -58,9 +59,8 @@ public function authenticate(
foreach ($attr->getValues() as $stored) {
if ($this->hashService->verify($password, $stored)) {
return new BindToken(
AuthzId::fromDn($entry->getDn()),
$name,
$password,
$entry->getDn(),
);
}
}
Expand Down
5 changes: 1 addition & 4 deletions src/FreeDSx/Ldap/Server/Proxy/ProxyAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ public function authenticate(
);
}

return BindToken::fromDn(
$name,
$password,
);
return BindToken::fromDn($name);
}

/**
Expand Down
5 changes: 0 additions & 5 deletions src/FreeDSx/Ldap/Server/Token/AnonToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ public function getUsername(): ?string
return $this->username;
}

public function getPassword(): ?string
{
return null;
}

public function getVersion(): int
{
return $this->version;
Expand Down
6 changes: 6 additions & 0 deletions src/FreeDSx/Ldap/Server/Token/AuthenticatedTokenInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace FreeDSx\Ldap\Server\Token;

use FreeDSx\Ldap\Entry\Dn;
use FreeDSx\Ldap\Protocol\Authorization\AuthzId;

/**
* Implemented by tokens that represent a successfully authenticated identity with a resolved DN.
Expand All @@ -24,6 +25,11 @@ interface AuthenticatedTokenInterface extends TokenInterface
{
public function getResolvedDn(): Dn;

/**
* The bound identity as an authorization identity: the resolved DN, or the username when it is not a DN.
*/
public function getAuthzId(): AuthzId;

/**
* Whether the bound identity must change its password before any other operation is permitted (pwdReset).
*/
Expand Down
57 changes: 34 additions & 23 deletions src/FreeDSx/Ldap/Server/Token/BindToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
namespace FreeDSx\Ldap\Server\Token;

use FreeDSx\Ldap\Entry\Dn;
use FreeDSx\Ldap\Protocol\Authorization\AuthzId;
use FreeDSx\Ldap\Server\Utility\Uuid;
use SensitiveParameter;

/**
* Represents a username/password token that is bound and authorized.
* Represents a bound and authorized identity, identified by its authorization identity (authzId).
*
* @api
*
Expand All @@ -28,52 +28,48 @@ class BindToken implements AuthenticatedTokenInterface
{
private string $id;

private string $username;

private readonly Dn $resolvedDn;

private string $password;
private string $authcId;

private int $version;

private bool $mustChangePassword = false;

private readonly ?Dn $authorizingDn;

/**
* @param AuthzId $authzId the resolved authorization identity (its DN, or the username when it is not a DN)
* @param string|null $authcId the authentication identity as presented, for auditing; defaults to the authzId value
*/
public function __construct(
string $username,
#[SensitiveParameter]
string $password,
Dn $resolvedDn,
private readonly AuthzId $authzId,
?string $authcId = null,
int $version = 3,
?Dn $authorizingDn = null,
) {
$this->id = Uuid::v4();
$this->username = $username;
$this->resolvedDn = $resolvedDn;
$this->password = $password;
$this->resolvedDn = new Dn($authzId->getValue());
$this->authcId = $authcId ?? $authzId->getValue();
$this->version = $version;
$this->authorizingDn = $authorizingDn;
}

public static function fromDn(
string $dn,
#[SensitiveParameter]
string $password,
int $version = 3,
?Dn $authorizingDn = null,
): self {
return new self(
AuthzId::fromDn(new Dn($dn)),
$dn,
$password,
new Dn($dn),
$version,
$authorizingDn,
);
}

/**
* Creates a token for a SASL-authenticated identity; no plaintext password is carried.
* Creates a token for a SASL-authenticated identity.
*/
public static function fromSasl(
string $username,
Expand All @@ -82,9 +78,24 @@ public static function fromSasl(
?Dn $authorizingDn = null,
): self {
return new self(
AuthzId::fromDn($resolvedDn),
$username,
$version,
$authorizingDn,
);
}

/**
* Creates a token for an identity that resolved to a bare username rather than a DN.
*/
public static function fromUsername(
string $username,
int $version = 3,
?Dn $authorizingDn = null,
): self {
return new self(
AuthzId::fromUsername($username),
$username,
'',
$resolvedDn,
$version,
$authorizingDn,
);
Expand All @@ -95,14 +106,14 @@ public function getId(): string
return $this->id;
}

public function getUsername(): ?string
public function getAuthzId(): AuthzId
{
return $this->username;
return $this->authzId;
}

public function getPassword(): ?string
public function getUsername(): ?string
{
return $this->password;
return $this->authcId;
}

public function getResolvedDn(): Dn
Expand Down
5 changes: 0 additions & 5 deletions src/FreeDSx/Ldap/Server/Token/SystemToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ public function getUsername(): string
return self::IDENTITY;
}

public function getPassword(): ?string
{
return null;
}

public function getVersion(): int
{
return $this->version;
Expand Down
2 changes: 0 additions & 2 deletions src/FreeDSx/Ldap/Server/Token/TokenInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ public function getId(): string;

public function getUsername(): ?string;

public function getPassword(): ?string;

public function getVersion(): int;

/**
Expand Down
Loading
Loading