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