Changeset 3802562 for aiwend/trunk/src/Secrets/ConnectionRepository.php
- Timestamp:
- 11/14/2026 10:49:34 PM (less than one hour ago)
- File:
-
- 1 edited
-
aiwend/trunk/src/Secrets/ConnectionRepository.php (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
-
aiwend/trunk/src/Secrets/ConnectionRepository.php
r3787448 r3802562 15 15 final class ConnectionRepository 16 16 { 17 public function __construct( 18 private Connection $db, 19 private Vault $vault, 20 private Redactor $redactor, 21 private Clock $clock, 22 ) { 23 } 24 25 public function create(int $siteId, string $provider, string $label, string $secret): int 26 { 17 private Connection $db; 18 private Vault $vault; 19 private Redactor $redactor; 20 private Clock $clock; 21 public function __construct(Connection $db, Vault $vault, Redactor $redactor, Clock $clock) 22 { 23 $this->db = $db; 24 $this->vault = $vault; 25 $this->redactor = $redactor; 26 $this->clock = $clock; 27 } 28 /** 29 * @param mixed $secret a SecretString (production callers) or a string (tests, legacy callers); it is carried as a SecretString from here on 30 */ 31 public function create( 32 int $siteId, 33 string $provider, 34 string $label, 35 #[\SensitiveParameter] 36 $secret 37 ): int { 27 38 $this->assertProvider($provider); 28 $secret = trim($secret); 29 if ($secret === '' || strlen($secret) > 4096) { 39 $secret = self::trimmed($secret); 40 $length = $secret->length(); 41 if ($length < 1 || $length > 4096) { 30 42 throw new \InvalidArgumentException('Credential must be between 1 and 4096 characters.'); 31 43 } … … 61 73 } 62 74 63 public function replaceSecret(int $siteId, int $id, string $secret): void 64 { 75 /** 76 * @param mixed $secret a SecretString (production callers) or a string (tests, legacy callers) 77 */ 78 public function replaceSecret( 79 int $siteId, 80 int $id, 81 #[\SensitiveParameter] 82 $secret 83 ): void { 65 84 $row = $this->requireRow($siteId, $id); 66 if (\Aiwend\Provider\Providers::looksLikeAnotherProvider((string) $row['provider'], trim($secret))) { 85 $secret = self::trimmed($secret); 86 if (\Aiwend\Provider\Providers::looksLikeAnotherProvider((string) $row['provider'], $secret)) { 67 87 throw new \InvalidArgumentException('key_wrong_provider'); 68 88 } 69 $this->storeSecret($siteId, $id, trim($secret));89 $this->storeSecret($siteId, $id, $secret); 70 90 $this->db->execute('UPDATE {connections} SET status = ? WHERE id = ? AND site_id = ?', ['unverified', $id, $siteId]); 71 91 } … … 126 146 127 147 /** Server-side only. The returned value is registered with the redactor. */ 128 public function secretForDispatch(int $siteId, int $id): string148 public function secretForDispatch(int $siteId, int $id): SecretString 129 149 { 130 150 $row = $this->requireRow($siteId, $id); … … 132 152 throw new \RuntimeException('Connection is disabled.'); 133 153 } 134 $secret = $this->vault->decrypt((string) $row['ciphertext'], $this->context($siteId, $id));154 $secret = new SecretString($this->vault->decrypt((string) $row['ciphertext'], $this->context($siteId, $id))); 135 155 $this->redactor->remember($secret); 136 156 return $secret; … … 161 181 } 162 182 $context = $this->context($siteId, $id); 163 $plain = $this->vault->decrypt($old, $context);183 $plain = new SecretString($this->vault->decrypt($old, $context)); 164 184 $new = $this->vault->encrypt($plain, $context); 165 if ($this->vault->needsRotation($new) || !hash_equals($plain , $this->vault->decrypt($new, $context))) {185 if ($this->vault->needsRotation($new) || !hash_equals($plain->reveal(), $this->vault->decrypt($new, $context))) { 166 186 throw new \RuntimeException('Re-encrypted credential did not verify.'); 167 187 } … … 171 191 ); 172 192 $changed === 1 ? $rotated++ : $failed++; 173 } catch (\RuntimeException | \SodiumException ) {193 } catch (\RuntimeException | \SodiumException $exception) { 174 194 $failed++; 175 195 } … … 188 208 try { 189 209 $pending += $this->vault->needsRotation((string) $row['ciphertext']) ? 1 : 0; 190 } catch (\RuntimeException ) {210 } catch (\RuntimeException $exception) { 191 211 $pending++; 192 212 } … … 202 222 try { 203 223 $pending += $this->vault->needsRotation((string) $row['ciphertext']) ? 1 : 0; 204 } catch (\RuntimeException ) {224 } catch (\RuntimeException $exception) { 205 225 $pending++; 206 226 } … … 209 229 } 210 230 211 private function storeSecret(int $siteId, int $id, string $secret): void 212 { 213 if ($secret === '' || strlen($secret) > 4096) { 231 /** Surrounding whitespace is not part of a credential. */ 232 private static function trimmed( 233 #[\SensitiveParameter] 234 $secret 235 ): SecretString { 236 $secret = SecretString::from($secret); 237 $plain = $secret->reveal(); 238 return $plain === trim($plain) ? $secret : new SecretString(trim($plain)); 239 } 240 241 private function storeSecret(int $siteId, int $id, SecretString $secret): void 242 { 243 $length = $secret->length(); 244 if ($length < 1 || $length > 4096) { 214 245 throw new \InvalidArgumentException('Credential must be between 1 and 4096 characters.'); 215 246 }
Note:
See TracChangeset
for help on using the changeset viewer.