44
55namespace Patchlevel \Hydrator \Cryptography ;
66
7+ use Closure ;
78use Patchlevel \Hydrator \Cryptography \Cipher \Cipher ;
89use Patchlevel \Hydrator \Cryptography \Cipher \CipherKeyFactory ;
910use Patchlevel \Hydrator \Cryptography \Cipher \DecryptionFailed ;
1213use Patchlevel \Hydrator \Cryptography \Store \CipherKeyNotExists ;
1314use Patchlevel \Hydrator \Cryptography \Store \CipherKeyStore ;
1415use Patchlevel \Hydrator \Metadata \ClassMetadata ;
15- use Patchlevel \Hydrator \Metadata \PropertyMetadata ;
1616
1717use function array_key_exists ;
1818use function is_int ;
1919use function is_string ;
2020
2121final class SensitiveDataPayloadCryptographer implements PayloadCryptographer
2222{
23+ private const ENCRYPTED_PREFIX = '! ' ;
24+
2325 public function __construct (
2426 private readonly CipherKeyStore $ cipherKeyStore ,
2527 private readonly CipherKeyFactory $ cipherKeyFactory ,
@@ -36,12 +38,26 @@ public function __construct(
3638 */
3739 public function encrypt (ClassMetadata $ metadata , array $ data ): array
3840 {
41+ $ mapping = $ metadata ->extras [SubjectIdFieldMapping::class] ?? null ;
42+
43+ if (!$ mapping instanceof SubjectIdFieldMapping) {
44+ return $ data ;
45+ }
46+
47+ $ subjectIds = $ this ->getSubjectIds ($ metadata , $ mapping , $ data );
48+
3949 foreach ($ metadata ->properties as $ propertyMetadata ) {
40- if (!$ propertyMetadata ->isSensitiveData ()) {
50+ $ sensitiveDataInfo = $ propertyMetadata ->extras [SensitiveDataInfo::class] ?? null ;
51+
52+ if (!$ sensitiveDataInfo instanceof SensitiveDataInfo) {
4153 continue ;
4254 }
4355
44- $ subjectId = $ this ->subjectId ($ propertyMetadata , $ metadata , $ data );
56+ $ subjectId = $ subjectIds [$ sensitiveDataInfo ->subjectIdName ] ?? null ;
57+
58+ if ($ subjectId === null ) {
59+ throw new MissingSubjectId ($ metadata ->className (), $ sensitiveDataInfo ->subjectIdName );
60+ }
4561
4662 try {
4763 $ cipherKey = $ this ->cipherKeyStore ->get ($ subjectId );
@@ -51,7 +67,7 @@ public function encrypt(ClassMetadata $metadata, array $data): array
5167 }
5268
5369 $ targetFieldName = $ this ->useEncryptedFieldName
54- ? $ propertyMetadata ->encryptedFieldName ()
70+ ? self :: ENCRYPTED_PREFIX . $ propertyMetadata ->fieldName
5571 : $ propertyMetadata ->fieldName ;
5672
5773 $ data [$ targetFieldName ] = $ this ->cipher ->encrypt (
@@ -76,30 +92,44 @@ public function encrypt(ClassMetadata $metadata, array $data): array
7692 */
7793 public function decrypt (ClassMetadata $ metadata , array $ data ): array
7894 {
95+ $ mapping = $ metadata ->extras [SubjectIdFieldMapping::class] ?? null ;
96+
97+ if (!$ mapping instanceof SubjectIdFieldMapping) {
98+ return $ data ;
99+ }
100+
101+ $ subjectIds = $ this ->getSubjectIds ($ metadata , $ mapping , $ data );
102+
79103 foreach ($ metadata ->properties as $ propertyMetadata ) {
80- if (!$ propertyMetadata ->isSensitiveData ()) {
104+ $ sensitiveDataInfo = $ propertyMetadata ->extras [SensitiveDataInfo::class] ?? null ;
105+
106+ if (!$ sensitiveDataInfo instanceof SensitiveDataInfo) {
81107 continue ;
82108 }
83109
84- $ subjectId = $ this ->subjectId ($ propertyMetadata , $ metadata , $ data );
110+ $ subjectId = $ subjectIds [$ sensitiveDataInfo ->subjectIdName ] ?? null ;
111+
112+ if ($ subjectId === null ) {
113+ throw new MissingSubjectId ($ metadata ->className (), $ sensitiveDataInfo ->subjectIdName );
114+ }
85115
86116 try {
87117 $ cipherKey = $ this ->cipherKeyStore ->get ($ subjectId );
88118 } catch (CipherKeyNotExists ) {
89119 $ cipherKey = null ;
90120 }
91121
92- if ($ this ->useEncryptedFieldName && array_key_exists ($ propertyMetadata ->encryptedFieldName () , $ data )) {
93- $ rawData = $ data [$ propertyMetadata ->encryptedFieldName () ];
94- unset($ data [$ propertyMetadata ->encryptedFieldName () ]);
122+ if ($ this ->useEncryptedFieldName && array_key_exists (self :: ENCRYPTED_PREFIX . $ propertyMetadata ->fieldName , $ data )) {
123+ $ rawData = $ data [self :: ENCRYPTED_PREFIX . $ propertyMetadata ->fieldName ];
124+ unset($ data [self :: ENCRYPTED_PREFIX . $ propertyMetadata ->fieldName ]);
95125 } elseif (!$ this ->useEncryptedFieldName || $ this ->fallbackToFieldName ) {
96126 $ rawData = $ data [$ propertyMetadata ->fieldName ];
97127 } else {
98128 continue ;
99129 }
100130
101131 if (!$ cipherKey ) {
102- $ data [$ propertyMetadata ->fieldName ] = $ this ->fallback ($ propertyMetadata , $ subjectId , $ rawData );
132+ $ data [$ propertyMetadata ->fieldName ] = $ this ->fallback ($ sensitiveDataInfo , $ subjectId , $ rawData );
103133 continue ;
104134 }
105135
@@ -109,54 +139,50 @@ public function decrypt(ClassMetadata $metadata, array $data): array
109139 $ rawData ,
110140 );
111141 } catch (DecryptionFailed ) {
112- $ data [$ propertyMetadata ->fieldName ] = $ this ->fallback ($ propertyMetadata , $ subjectId , $ rawData );
142+ $ data [$ propertyMetadata ->fieldName ] = $ this ->fallback ($ sensitiveDataInfo , $ subjectId , $ rawData );
113143 }
114144 }
115145
116146 return $ data ;
117147 }
118148
119- /** @param array<string, mixed> $data */
120- private function subjectId (PropertyMetadata $ propertyMetadata , ClassMetadata $ metadata , array $ data ): string
149+ /**
150+ * @param array<string, mixed> $data
151+ *
152+ * @return array<string, string>
153+ */
154+ private function getSubjectIds (ClassMetadata $ metadata , SubjectIdFieldMapping $ mapping , array $ data ): array
121155 {
122- if (!$ propertyMetadata ->isSensitiveData ()) {
123- throw new NotSensitiveData ($ metadata ->className (), $ propertyMetadata ->propertyName ());
124- }
125-
126- $ sensitiveDataSubjectIdName = $ propertyMetadata ->sensitiveDataSubjectIdName ;
127-
128- if (!$ metadata ->hasSubjectIdIdentifier ($ sensitiveDataSubjectIdName )) {
129- throw new MissingSubjectId ($ metadata ->className (), $ propertyMetadata ->propertyName ());
130- }
156+ $ result = [];
131157
132- $ fieldName = $ metadata ->getSubjectIdFieldName ($ sensitiveDataSubjectIdName );
158+ foreach ($ mapping ->nameToField as $ name => $ fieldName ) {
159+ if (!array_key_exists ($ fieldName , $ data )) {
160+ throw new MissingSubjectId ($ metadata ->className (), $ fieldName );
161+ }
133162
134- if (!array_key_exists ($ fieldName , $ data )) {
135- throw new MissingSubjectId ($ metadata ->className (), $ fieldName );
136- }
163+ $ subjectId = $ data [$ fieldName ];
137164
138- $ subjectId = $ data [$ fieldName ];
165+ if (is_int ($ subjectId )) {
166+ $ subjectId = (string )$ subjectId ;
167+ }
139168
140- if (is_int ($ subjectId )) {
141- $ subjectId = ( string ) $ subjectId ;
142- }
169+ if (! is_string ($ subjectId )) {
170+ throw new UnsupportedSubjectId ( $ metadata -> className (), $ fieldName , $ subjectId) ;
171+ }
143172
144- if (!is_string ($ subjectId )) {
145- throw new UnsupportedSubjectId ($ metadata ->className (), $ fieldName , $ subjectId );
173+ $ result [$ name ] = $ subjectId ;
146174 }
147175
148- return $ subjectId ;
176+ return $ result ;
149177 }
150178
151- private function fallback (PropertyMetadata $ propertyMetadata , string $ subjectId , mixed $ rawData ): mixed
179+ private function fallback (SensitiveDataInfo $ sensitiveDataInfo , string $ subjectId , mixed $ rawData ): mixed
152180 {
153- $ callback = $ propertyMetadata ->sensitiveDataFallbackCallable ();
154-
155- if (!$ callback ) {
156- return $ propertyMetadata ->sensitiveDataFallback ;
181+ if ($ sensitiveDataInfo ->fallback instanceof Closure) {
182+ return ($ sensitiveDataInfo ->fallback )($ subjectId , $ rawData );
157183 }
158184
159- return $ callback ( $ subjectId , $ rawData ) ;
185+ return $ sensitiveDataInfo -> fallback ;
160186 }
161187
162188 /** @param non-empty-string $method */
0 commit comments