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