| 1: | <?php |
| 2: | namespace Worldline\Connect\Sdk\Logging; |
| 3: | |
| 4: | use UnexpectedValueException; |
| 5: | |
| 6: | |
| 7: | |
| 8: | |
| 9: | |
| 10: | |
| 11: | class BodyObfuscator |
| 12: | { |
| 13: | const MIME_APPLICATION_JSON = 'application/json'; |
| 14: | |
| 15: | |
| 16: | |
| 17: | |
| 18: | protected ValueObfuscator $valueObfuscator; |
| 19: | |
| 20: | |
| 21: | |
| 22: | |
| 23: | private array $customRules = array(); |
| 24: | |
| 25: | public function __construct() |
| 26: | { |
| 27: | $this->valueObfuscator = new ValueObfuscator(); |
| 28: | } |
| 29: | |
| 30: | |
| 31: | |
| 32: | |
| 33: | |
| 34: | |
| 35: | |
| 36: | public function obfuscateBody(string $contentType, string $body): string |
| 37: | { |
| 38: | if (!$this->isJsonContentType($contentType)) { |
| 39: | return $body; |
| 40: | } |
| 41: | $decodedJsonBody = json_decode($body); |
| 42: | if (json_last_error() !== JSON_ERROR_NONE) { |
| 43: | return $body; |
| 44: | } |
| 45: | return json_encode($this->obfuscateDecodedJsonPart($decodedJsonBody), JSON_PRETTY_PRINT); |
| 46: | } |
| 47: | |
| 48: | private function isJsonContentType(string $contentType): bool |
| 49: | { |
| 50: | return $contentType === static::MIME_APPLICATION_JSON |
| 51: | || substr($contentType, 0, strlen(static::MIME_APPLICATION_JSON)) === static::MIME_APPLICATION_JSON; |
| 52: | } |
| 53: | |
| 54: | |
| 55: | |
| 56: | |
| 57: | |
| 58: | |
| 59: | protected function obfuscateDecodedJsonPart($value) |
| 60: | { |
| 61: | if (is_object($value)) { |
| 62: | foreach ($value as $propertyName => $propertyValue) { |
| 63: | if (is_scalar($propertyValue)) { |
| 64: | $value->$propertyName = $this->obfuscateScalarValue($propertyName, $propertyValue); |
| 65: | } else { |
| 66: | $value->$propertyName = $this->obfuscateDecodedJsonPart($propertyValue); |
| 67: | } |
| 68: | } |
| 69: | } |
| 70: | if (is_array($value)) { |
| 71: | foreach ($value as $elementKey => &$elementValue) { |
| 72: | if (is_scalar($elementValue)) { |
| 73: | $elementValue = $this->obfuscateScalarValue($elementKey, $elementValue); |
| 74: | } else { |
| 75: | $elementValue = $this->obfuscateDecodedJsonPart($elementValue); |
| 76: | } |
| 77: | } |
| 78: | } |
| 79: | return $value; |
| 80: | } |
| 81: | |
| 82: | |
| 83: | |
| 84: | |
| 85: | |
| 86: | |
| 87: | |
| 88: | protected function obfuscateScalarValue(string $key, $value): string |
| 89: | { |
| 90: | if (!is_scalar($value)) { |
| 91: | throw new UnexpectedValueException('scalar value expected'); |
| 92: | } |
| 93: | $lowerKey = mb_strtolower($key, 'UTF-8'); |
| 94: | if (isset($this->customRules[$lowerKey])) { |
| 95: | return call_user_func($this->customRules[$lowerKey], $value, $this->valueObfuscator); |
| 96: | } |
| 97: | switch ($lowerKey) { |
| 98: | case 'keyid': |
| 99: | case 'secretkey': |
| 100: | case 'publickey': |
| 101: | case 'userauthenticationtoken': |
| 102: | case 'encryptedpayload': |
| 103: | case 'decryptedpayload': |
| 104: | case 'encryptedcustomerinput': |
| 105: | return $this->valueObfuscator->obfuscateFixedLength(8); |
| 106: | case 'cvv': |
| 107: | case 'value': |
| 108: | return $this->valueObfuscator->obfuscateAll($value); |
| 109: | case 'bin': |
| 110: | return $this->valueObfuscator->obfuscateAllKeepStart($value, 6); |
| 111: | case 'accountnumber': |
| 112: | case 'cardnumber': |
| 113: | case 'iban': |
| 114: | case 'reformattedaccountnumber': |
| 115: | return $this->valueObfuscator->obfuscateAllKeepEnd($value, 4); |
| 116: | case 'expirydate': |
| 117: | return $this->valueObfuscator->obfuscateAllKeepEnd($value, 2); |
| 118: | default: |
| 119: | return $value; |
| 120: | } |
| 121: | } |
| 122: | |
| 123: | |
| 124: | |
| 125: | |
| 126: | |
| 127: | |
| 128: | |
| 129: | public function setCustomRule(string $propertyName, callable $customRule): void |
| 130: | { |
| 131: | $lowerName = mb_strtolower($propertyName, 'UTF-8'); |
| 132: | $this->customRules[$lowerName] = $customRule; |
| 133: | } |
| 134: | } |
| 135: | |