1<?php
2/**
3 * This file is part of the FreeDSx LDAP package.
4 *
5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace FreeDSx\Ldap\Control;
12
13use FreeDSx\Asn1\Asn1;
14use FreeDSx\Asn1\Type\AbstractType;
15use FreeDSx\Asn1\Type\IncompleteType;
16use FreeDSx\Asn1\Type\SequenceType;
17use FreeDSx\Ldap\Exception\ProtocolException;
18use FreeDSx\Ldap\Protocol\LdapEncoder;
19
20/**
21 * Represents a password policy response. draft-behera-ldap-password-policy-09
22 *
23 * PasswordPolicyResponseValue ::= SEQUENCE {
24 *     warning [0] CHOICE {
25 *         timeBeforeExpiration [0] INTEGER (0 .. maxInt),
26 *         graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL,
27 *     error   [1] ENUMERATED {
28 *         passwordExpired             (0),
29 *         accountLocked               (1),
30 *         changeAfterReset            (2),
31 *         passwordModNotAllowed       (3),
32 *         mustSupplyOldPassword       (4),
33 *         insufficientPasswordQuality (5),
34 *         passwordTooShort            (6),
35 *         passwordTooYoung            (7),
36 *         passwordInHistory           (8) } OPTIONAL }
37 *
38 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
39 */
40class PwdPolicyResponseControl extends Control
41{
42    /**
43     * @var int|null
44     */
45    protected $timeBeforeExpiration;
46
47    /**
48     * @var int|null
49     */
50    protected $graceAuthRemaining;
51
52    /**
53     * @var int|null
54     */
55    protected $error;
56
57    /**
58     * @param int|null $timeBeforeExpiration
59     * @param int|null $graceAuthRemaining
60     * @param int|null $error
61     */
62    public function __construct(?int $timeBeforeExpiration = null, ?int $graceAuthRemaining = null, ?int $error = null)
63    {
64        $this->timeBeforeExpiration = $timeBeforeExpiration;
65        $this->graceAuthRemaining = $graceAuthRemaining;
66        $this->error = $error;
67        parent::__construct(self::OID_PWD_POLICY);
68    }
69
70    /**
71     * @return int|null
72     */
73    public function getTimeBeforeExpiration(): ?int
74    {
75        return $this->timeBeforeExpiration;
76    }
77
78    /**
79     * @return int|null
80     */
81    public function getGraceAttemptsRemaining(): ?int
82    {
83        return $this->graceAuthRemaining;
84    }
85
86    /**
87     * @return int|null
88     */
89    public function getError(): ?int
90    {
91        return $this->error;
92    }
93
94    /**
95     * {@inheritdoc}
96     */
97    public function toAsn1(): AbstractType
98    {
99        $response = Asn1::sequence();
100        $warning = null;
101
102        if ($this->graceAuthRemaining !== null && $this->timeBeforeExpiration !== null) {
103            throw new ProtocolException('The password policy response cannot have both a time expiration and a grace auth value.');
104        }
105        if ($this->timeBeforeExpiration !== null) {
106            $warning = Asn1::context(0, Asn1::sequence(
107                Asn1::context(0, Asn1::integer($this->timeBeforeExpiration))
108            ));
109        }
110        if ($this->graceAuthRemaining !== null) {
111            $warning = Asn1::context(0, Asn1::sequence(
112                Asn1::context(1, Asn1::integer($this->graceAuthRemaining))
113            ));
114        }
115
116        if ($warning !== null) {
117            $response->addChild($warning);
118        }
119        if ($this->error !== null) {
120            $response->addChild(Asn1::context(1, Asn1::enumerated($this->error)));
121        }
122        $this->controlValue = $response;
123
124        return parent::toAsn1();
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    public static function fromAsn1(AbstractType $type)
131    {
132        /** @var SequenceType $response */
133        $response = self::decodeEncodedValue($type);
134
135        $error = null;
136        $timeBeforeExpiration = null;
137        $graceAttemptsRemaining = null;
138
139        $encoder = new LdapEncoder();
140        foreach ($response->getChildren() as $child) {
141            if (!$child instanceof IncompleteType) {
142                throw new ProtocolException('The ASN1 structure for the pwdPolicy control is malformed.');
143            }
144            if ($child->getTagNumber() === 0) {
145                $warnings = $encoder->complete($child, AbstractType::TAG_TYPE_SEQUENCE, [
146                    AbstractType::TAG_CLASS_CONTEXT_SPECIFIC => [
147                        0 => AbstractType::TAG_TYPE_INTEGER,
148                        1 => AbstractType::TAG_TYPE_INTEGER,
149                    ],
150                ]);
151                /** @var AbstractType $warning */
152                foreach ($warnings->getChildren() as $warning) {
153                    if ($warning->getTagNumber() === 0) {
154                        $timeBeforeExpiration = $warning->getValue();
155                        break;
156                    } elseif ($warning->getTagNumber() === 1) {
157                        $graceAttemptsRemaining = $warning->getValue();
158                        break;
159                    }
160                }
161            } elseif ($child->getTagNumber() === 1) {
162                $error = $encoder->complete($child, AbstractType::TAG_TYPE_ENUMERATED)->getValue();
163            }
164        }
165        $control = new self($timeBeforeExpiration, $graceAttemptsRemaining, $error);
166
167        return self::mergeControlData($control, $type);
168    }
169}
170