1: <?php
2: namespace Worldline\Connect\Sdk\Logging;
3:
4: use UnexpectedValueException;
5:
6: /**
7: * Class BodyObfuscator
8: *
9: * @package Worldline\Connect\Sdk\Logging
10: */
11: class BodyObfuscator
12: {
13: const MIME_APPLICATION_JSON = 'application/json';
14:
15: /**
16: * @var ValueObfuscator
17: */
18: protected ValueObfuscator $valueObfuscator;
19:
20: /**
21: * @var array<string, callable>
22: */
23: private array $customRules = array();
24:
25: public function __construct()
26: {
27: $this->valueObfuscator = new ValueObfuscator();
28: }
29:
30: /**
31: * @param string $contentType
32: * @param string $body
33: *
34: * @return string
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: * @param mixed $value
56: *
57: * @return mixed
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: * @param string $key
84: * @param scalar $value
85: *
86: * @return string
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: * @param string $propertyName
125: * @param callable $customRule
126: *
127: * @return void
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: