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\Operation\Request; 13 14use FreeDSx\Asn1\Asn1; 15use FreeDSx\Asn1\Type\AbstractType; 16use FreeDSx\Asn1\Type\EnumeratedType; 17use FreeDSx\Asn1\Type\OctetStringType; 18use FreeDSx\Asn1\Type\SequenceType; 19use FreeDSx\Asn1\Type\SetType; 20use FreeDSx\Ldap\Entry\Attribute; 21use FreeDSx\Ldap\Entry\Change; 22use FreeDSx\Ldap\Entry\Dn; 23use FreeDSx\Ldap\Exception\ProtocolException; 24use function array_map; 25use function count; 26 27/** 28 * A Modify Request. RFC 4511, 4.6 29 * 30 * ModifyRequest ::= [APPLICATION 6] SEQUENCE { 31 * object LDAPDN, 32 * changes SEQUENCE OF change SEQUENCE { 33 * operation ENUMERATED { 34 * add (0), 35 * delete (1), 36 * replace (2), 37 * ... }, 38 * modification PartialAttribute } } 39 * 40 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 41 */ 42class ModifyRequest implements RequestInterface, DnRequestInterface 43{ 44 protected const APP_TAG = 6; 45 46 /** 47 * @var Change[] 48 */ 49 protected $changes; 50 51 /** 52 * @var Dn 53 */ 54 protected $dn; 55 56 /** 57 * @param string|Dn $dn 58 * @param Change ...$changes 59 */ 60 public function __construct($dn, Change ...$changes) 61 { 62 $this->setDn($dn); 63 $this->changes = $changes; 64 } 65 66 /** 67 * @return Change[] 68 */ 69 public function getChanges(): array 70 { 71 return $this->changes; 72 } 73 74 /** 75 * @param Change ...$changes 76 * @return $this 77 */ 78 public function setChanges(Change ...$changes) 79 { 80 $this->changes = $changes; 81 82 return $this; 83 } 84 85 /** 86 * @param string|Dn $dn 87 * @return $this 88 */ 89 public function setDn($dn) 90 { 91 $this->dn = $dn instanceof $dn ? $dn : new Dn($dn); 92 93 return $this; 94 } 95 96 /** 97 * @return Dn 98 */ 99 public function getDn(): Dn 100 { 101 return $this->dn; 102 } 103 104 /** 105 * {@inheritDoc} 106 * @return self 107 */ 108 public static function fromAsn1(AbstractType $type) 109 { 110 if (!($type instanceof SequenceType && count($type) === 2)) { 111 throw new ProtocolException('The modify request is malformed'); 112 } 113 114 $dn = $type->getChild(0); 115 $changes = $type->getChild(1); 116 if (!($dn instanceof OctetStringType && $changes instanceof SequenceType)) { 117 throw new ProtocolException('The modify request is malformed'); 118 } 119 120 $changeList = []; 121 foreach ($changes->getChildren() as $change) { 122 $changeList[] = self::parseChange($change); 123 } 124 125 return new self($dn->getValue(), ...$changeList); 126 } 127 128 /** 129 * {@inheritdoc} 130 */ 131 public function toAsn1(): AbstractType 132 { 133 $changes = Asn1::sequenceOf(); 134 135 foreach ($this->changes as $change) { 136 $changeSeq = Asn1::sequence(Asn1::enumerated($change->getType())); 137 138 $changeSeq->addChild(Asn1::sequence( 139 Asn1::octetString($change->getAttribute()->getDescription()), 140 Asn1::setOf(...array_map(function ($value) { 141 return Asn1::octetString($value); 142 }, $change->getAttribute()->getValues())) 143 )); 144 145 $changes->addChild($changeSeq); 146 } 147 148 return Asn1::application(self::APP_TAG, Asn1::sequence( 149 Asn1::octetString($this->dn->toString()), 150 $changes 151 )); 152 } 153 154 /** 155 * @param AbstractType $type 156 * @return Change 157 * @throws ProtocolException 158 */ 159 protected static function parseChange(AbstractType $type): Change 160 { 161 if (!($type instanceof SequenceType && count($type->getChildren()) === 2)) { 162 throw new ProtocolException('The change for the modify request is malformed.'); 163 } 164 165 $operation = $type->getChild(0); 166 $modification = $type->getChild(1); 167 if (!($operation instanceof EnumeratedType && $modification instanceof SequenceType)) { 168 throw new ProtocolException('The change for the modify request is malformed.'); 169 } 170 171 return new Change($operation->getValue(), self::parsePartialAttribute($modification)); 172 } 173 174 /** 175 * @param SequenceType $type 176 * @return Attribute 177 * @throws ProtocolException 178 */ 179 protected static function parsePartialAttribute(SequenceType $type): Attribute 180 { 181 if (count($type->getChildren()) !== 2) { 182 throw new ProtocolException('The partial attribute for the modify request is malformed.'); 183 } 184 185 $attrType = $type->getChild(0); 186 $attrVals = $type->getChild(1); 187 if (!($attrType instanceof OctetStringType && $attrVals instanceof SetType)) { 188 throw new ProtocolException('The partial attribute for the modify request is malformed.'); 189 } 190 191 $values = []; 192 foreach ($attrVals->getChildren() as $attrVal) { 193 if (!$attrVal instanceof OctetStringType) { 194 throw new ProtocolException('The partial attribute for the modify request is malformed.'); 195 } 196 $values[] = $attrVal->getValue(); 197 } 198 199 return new Attribute($attrType->getValue(), ...$values); 200 } 201} 202