From dc2bdb6ec2424b36ae06bd9ec1cc10e726d4547f Mon Sep 17 00:00:00 2001 From: Konstantin Serebryakov <97249355+KonstantinSerebryakov@users.noreply.github.com> Date: Tue, 6 Oct 2026 01:29:54 +0300 Subject: [PATCH 1/4] feat(webhooks): support current final-risk contract --- CHANGELOG.md | 2 + README.md | 20 +- generated/.openapi-generator/FILES | 4 + generated/lib/Model/DetectionFlags.php | 150 ++++- generated/lib/Model/Fingerprint.php | 621 ++++++++++++++++++ generated/lib/Model/HRE.php | 522 +++++++++++++++ generated/lib/Model/HREResult.php | 580 ++++++++++++++++ .../lib/Model/IdentificationScoredData.php | 195 +++++- .../lib/Model/IdentificationScoredEvent.php | 134 ++-- generated/lib/Model/RiskEvent.php | 561 ++++++++++++++++ generated/lib/Model/WebhookPingEvent.php | 57 +- resources/shieldlabs-api.yaml | 403 +++++++++++- src/Event/IdentificationScoredEvent.php | 7 +- src/Event/WebhookEvent.php | 10 +- src/Internal/Normalizer.php | 6 + src/Model/DetectionFlags.php | 12 + src/Model/Identification.php | 22 +- src/Webhook.php | 9 +- tests/ContractCurrentTest.php | 29 + tests/data/contract-current.json | 243 +++++++ 20 files changed, 3478 insertions(+), 109 deletions(-) create mode 100644 generated/lib/Model/Fingerprint.php create mode 100644 generated/lib/Model/HRE.php create mode 100644 generated/lib/Model/HREResult.php create mode 100644 generated/lib/Model/RiskEvent.php create mode 100644 tests/ContractCurrentTest.php create mode 100644 tests/data/contract-current.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d01630..50ddf9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ follows [Semantic Versioning](https://semver.org/). ### Added +- Accept webhook contract `2026-10-06` with signed event IDs, final result/scoring versions, the complete risk-event catalogue, three HRE results and distinct fp21 hardware identity. Legacy webhook bodies remain supported. + - `sync.sh` downloads the OpenAPI description and `generate.sh` rebuilds `generated/` from it. The supported client is unchanged. ## [1.0.0] - 2026-09-30 diff --git a/README.md b/README.md index ac3c58b..e57686b 100644 --- a/README.md +++ b/README.md @@ -289,8 +289,8 @@ The result is HMAC-SHA256 as 64 lowercase hex characters: stable for a user and - The header is `X-Shield-Signature: sha256=`, the HMAC-SHA256 of the raw body keyed with the full signing secret, `whsec_` prefix included. - To rotate a secret without downtime, pass both: `Webhook::constructEvent($payload, $header, [$newSecret, $oldSecret])`. A delivery is valid when any secret matches. - Events: `IdentificationScoredEvent` (`$event->data` is an `Identification`), `WebhookPingEvent` (sent when you verify an endpoint) and `UnknownWebhookEvent` for event types newer than this SDK (acknowledge them with a 2xx). `SignatureVerificationException` means the delivery is not authentic; `WebhookParseException` means a correctly signed body is not a valid event. -- Today each identification is delivered once per endpoint: one attempt with a 1-second timeout and no retries. Respond with a 2xx quickly and do slow work afterwards. Future retries will resend identical bytes, so make handlers idempotent on `data.request_id` (`$event->data->request_id`) now. -- For guaranteed reads, use the History API: a missed delivery is not sent again, and a History row can be refined after its webhook was sent. +- Current failed deliveries retry within a bounded window; store the verified event before 2xx and deduplicate by event_id. +- For guaranteed reads, use the History API: retries can exhaust, and a History row can be refined after its webhook was sent. - The "Test" button in the analytics dashboard sends a sample with 17 of the 19 flags and second-precision timestamps. It parses normally; missing flags are false. - `Webhook::verifySignature()` returns a boolean without parsing, for custom flows. @@ -461,3 +461,19 @@ docker run --rm -v "$PWD":/app -w /app composer:2 sh -c "composer install && com ## License [MIT](LICENSE) + + +### Webhook contract 2026-10-06 + +Current events include signed `event_id`, optional `site_id`, the complete `data.risk_events` +catalogue (including zero-weight events), `data.fingerprint` (FP21 hardware ID distinct from +`device_id`), and `data.hre` for sharing/takeover/travel with explicit statuses. Older envelopes +remain supported. Only identification risk score is sent; AI bots/browser are planned only, +and all-time entity risks are excluded. + +Persist the verified event in a durable inbox **before** returning 2xx and deduplicate by +`event_id` (legacy scored bodies: `data.request_id`). Timeout/network/429/5xx retry with +backoff in a bounded window (8 failed sends / 15-minute retry age), then DLQ. Other 4xx are +terminal. Retried bodies and event IDs stay unchanged. `X-Shield-Event-Id` mirrors the body ID; +trust the signed body. Signature verification remains raw-body HMAC-SHA256. Delivery is not +exactly-once and later History corrections do not automatically create a new webhook event. diff --git a/generated/.openapi-generator/FILES b/generated/.openapi-generator/FILES index 7042961..aa0ffa1 100644 --- a/generated/.openapi-generator/FILES +++ b/generated/.openapi-generator/FILES @@ -10,6 +10,9 @@ lib/HeaderSelector.php lib/Model/DetectionFlags.php lib/Model/DomainProfile.php lib/Model/ErrorBody.php +lib/Model/Fingerprint.php +lib/Model/HRE.php +lib/Model/HREResult.php lib/Model/HealthStatus.php lib/Model/HistoryPage.php lib/Model/HistoryRow.php @@ -18,6 +21,7 @@ lib/Model/IdentificationScoredEvent.php lib/Model/IpInfo.php lib/Model/LegacySnapshot.php lib/Model/ModelInterface.php +lib/Model/RiskEvent.php lib/Model/ScoreDetail.php lib/Model/Signal.php lib/Model/TrafficSource.php diff --git a/generated/lib/Model/DetectionFlags.php b/generated/lib/Model/DetectionFlags.php index 56f504e..a722cbf 100644 --- a/generated/lib/Model/DetectionFlags.php +++ b/generated/lib/Model/DetectionFlags.php @@ -36,7 +36,7 @@ * DetectionFlags Class Doc Comment * * @category Class - * @description Stable yes/no verdicts for the identification. Always all 19 keys. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and `javascript_disabled` are always `false`. + * @description Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the four extension flags are present in schema 2026-10-06. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and `javascript_disabled` are always `false`. * @package ShieldLabs\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -77,7 +77,11 @@ class DetectionFlags implements ModelInterface, ArrayAccess, \JsonSerializable 'suspicious_paid_click' => 'bool', 'javascript_disabled' => 'bool', 'stun_not_checked' => 'bool', - 'check_incomplete' => 'bool' + 'check_incomplete' => 'bool', + 'os_mismatch2' => 'bool', + 'device_spoofing' => 'bool', + 'latency_test' => 'bool', + 'banned_ip' => 'bool' ]; /** @@ -106,7 +110,11 @@ class DetectionFlags implements ModelInterface, ArrayAccess, \JsonSerializable 'suspicious_paid_click' => null, 'javascript_disabled' => null, 'stun_not_checked' => null, - 'check_incomplete' => null + 'check_incomplete' => null, + 'os_mismatch2' => null, + 'device_spoofing' => null, + 'latency_test' => null, + 'banned_ip' => null ]; /** @@ -133,7 +141,11 @@ class DetectionFlags implements ModelInterface, ArrayAccess, \JsonSerializable 'suspicious_paid_click' => false, 'javascript_disabled' => false, 'stun_not_checked' => false, - 'check_incomplete' => false + 'check_incomplete' => false, + 'os_mismatch2' => false, + 'device_spoofing' => false, + 'latency_test' => false, + 'banned_ip' => false ]; /** @@ -240,7 +252,11 @@ public function isNullableSetToNull(string $property): bool 'suspicious_paid_click' => 'suspicious_paid_click', 'javascript_disabled' => 'javascript_disabled', 'stun_not_checked' => 'stun_not_checked', - 'check_incomplete' => 'check_incomplete' + 'check_incomplete' => 'check_incomplete', + 'os_mismatch2' => 'os_mismatch2', + 'device_spoofing' => 'device_spoofing', + 'latency_test' => 'latency_test', + 'banned_ip' => 'banned_ip' ]; /** @@ -267,7 +283,11 @@ public function isNullableSetToNull(string $property): bool 'suspicious_paid_click' => 'setSuspiciousPaidClick', 'javascript_disabled' => 'setJavascriptDisabled', 'stun_not_checked' => 'setStunNotChecked', - 'check_incomplete' => 'setCheckIncomplete' + 'check_incomplete' => 'setCheckIncomplete', + 'os_mismatch2' => 'setOsMismatch2', + 'device_spoofing' => 'setDeviceSpoofing', + 'latency_test' => 'setLatencyTest', + 'banned_ip' => 'setBannedIp' ]; /** @@ -294,7 +314,11 @@ public function isNullableSetToNull(string $property): bool 'suspicious_paid_click' => 'getSuspiciousPaidClick', 'javascript_disabled' => 'getJavascriptDisabled', 'stun_not_checked' => 'getStunNotChecked', - 'check_incomplete' => 'getCheckIncomplete' + 'check_incomplete' => 'getCheckIncomplete', + 'os_mismatch2' => 'getOsMismatch2', + 'device_spoofing' => 'getDeviceSpoofing', + 'latency_test' => 'getLatencyTest', + 'banned_ip' => 'getBannedIp' ]; /** @@ -373,6 +397,10 @@ public function __construct(?array $data = null) $this->setIfExists('javascript_disabled', $data ?? [], null); $this->setIfExists('stun_not_checked', $data ?? [], null); $this->setIfExists('check_incomplete', $data ?? [], null); + $this->setIfExists('os_mismatch2', $data ?? [], null); + $this->setIfExists('device_spoofing', $data ?? [], null); + $this->setIfExists('latency_test', $data ?? [], null); + $this->setIfExists('banned_ip', $data ?? [], null); } /** @@ -986,6 +1014,114 @@ public function setCheckIncomplete($check_incomplete) return $this; } + + /** + * Gets os_mismatch2 + * + * @return bool|null + */ + public function getOsMismatch2() + { + return $this->container['os_mismatch2']; + } + + /** + * Sets os_mismatch2 + * + * @param bool|null $os_mismatch2 os_mismatch2 + * + * @return self + */ + public function setOsMismatch2($os_mismatch2) + { + if (is_null($os_mismatch2)) { + throw new \InvalidArgumentException('non-nullable os_mismatch2 cannot be null'); + } + $this->container['os_mismatch2'] = $os_mismatch2; + + return $this; + } + + /** + * Gets device_spoofing + * + * @return bool|null + */ + public function getDeviceSpoofing() + { + return $this->container['device_spoofing']; + } + + /** + * Sets device_spoofing + * + * @param bool|null $device_spoofing device_spoofing + * + * @return self + */ + public function setDeviceSpoofing($device_spoofing) + { + if (is_null($device_spoofing)) { + throw new \InvalidArgumentException('non-nullable device_spoofing cannot be null'); + } + $this->container['device_spoofing'] = $device_spoofing; + + return $this; + } + + /** + * Gets latency_test + * + * @return bool|null + */ + public function getLatencyTest() + { + return $this->container['latency_test']; + } + + /** + * Sets latency_test + * + * @param bool|null $latency_test latency_test + * + * @return self + */ + public function setLatencyTest($latency_test) + { + if (is_null($latency_test)) { + throw new \InvalidArgumentException('non-nullable latency_test cannot be null'); + } + $this->container['latency_test'] = $latency_test; + + return $this; + } + + /** + * Gets banned_ip + * + * @return bool|null + */ + public function getBannedIp() + { + return $this->container['banned_ip']; + } + + /** + * Sets banned_ip + * + * @param bool|null $banned_ip banned_ip + * + * @return self + */ + public function setBannedIp($banned_ip) + { + if (is_null($banned_ip)) { + throw new \InvalidArgumentException('non-nullable banned_ip cannot be null'); + } + $this->container['banned_ip'] = $banned_ip; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/lib/Model/Fingerprint.php b/generated/lib/Model/Fingerprint.php new file mode 100644 index 0000000..408c338 --- /dev/null +++ b/generated/lib/Model/Fingerprint.php @@ -0,0 +1,621 @@ + + */ +class Fingerprint implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'Fingerprint'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'outcome' => 'string', + 'record_id' => 'string', + 'hardware_id' => 'string', + 'rules_version' => 'string', + 'sharing' => 'object', + 'takeover' => 'object', + 'travel' => 'object' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'outcome' => null, + 'record_id' => null, + 'hardware_id' => null, + 'rules_version' => null, + 'sharing' => null, + 'takeover' => null, + 'travel' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'outcome' => false, + 'record_id' => false, + 'hardware_id' => false, + 'rules_version' => false, + 'sharing' => false, + 'takeover' => false, + 'travel' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'outcome' => 'outcome', + 'record_id' => 'record_id', + 'hardware_id' => 'hardware_id', + 'rules_version' => 'rules_version', + 'sharing' => 'sharing', + 'takeover' => 'takeover', + 'travel' => 'travel' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'outcome' => 'setOutcome', + 'record_id' => 'setRecordId', + 'hardware_id' => 'setHardwareId', + 'rules_version' => 'setRulesVersion', + 'sharing' => 'setSharing', + 'takeover' => 'setTakeover', + 'travel' => 'setTravel' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'outcome' => 'getOutcome', + 'record_id' => 'getRecordId', + 'hardware_id' => 'getHardwareId', + 'rules_version' => 'getRulesVersion', + 'sharing' => 'getSharing', + 'takeover' => 'getTakeover', + 'travel' => 'getTravel' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('outcome', $data ?? [], null); + $this->setIfExists('record_id', $data ?? [], null); + $this->setIfExists('hardware_id', $data ?? [], null); + $this->setIfExists('rules_version', $data ?? [], null); + $this->setIfExists('sharing', $data ?? [], null); + $this->setIfExists('takeover', $data ?? [], null); + $this->setIfExists('travel', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['outcome'] === null) { + $invalidProperties[] = "'outcome' can't be null"; + } + if ($this->container['rules_version'] === null) { + $invalidProperties[] = "'rules_version' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets outcome + * + * @return string + */ + public function getOutcome() + { + return $this->container['outcome']; + } + + /** + * Sets outcome + * + * @param string $outcome outcome + * + * @return self + */ + public function setOutcome($outcome) + { + if (is_null($outcome)) { + throw new \InvalidArgumentException('non-nullable outcome cannot be null'); + } + $this->container['outcome'] = $outcome; + + return $this; + } + + /** + * Gets record_id + * + * @return string|null + */ + public function getRecordId() + { + return $this->container['record_id']; + } + + /** + * Sets record_id + * + * @param string|null $record_id record_id + * + * @return self + */ + public function setRecordId($record_id) + { + if (is_null($record_id)) { + throw new \InvalidArgumentException('non-nullable record_id cannot be null'); + } + $this->container['record_id'] = $record_id; + + return $this; + } + + /** + * Gets hardware_id + * + * @return string|null + */ + public function getHardwareId() + { + return $this->container['hardware_id']; + } + + /** + * Sets hardware_id + * + * @param string|null $hardware_id FP21 hardware identity, distinct from device_id. + * + * @return self + */ + public function setHardwareId($hardware_id) + { + if (is_null($hardware_id)) { + throw new \InvalidArgumentException('non-nullable hardware_id cannot be null'); + } + $this->container['hardware_id'] = $hardware_id; + + return $this; + } + + /** + * Gets rules_version + * + * @return string + */ + public function getRulesVersion() + { + return $this->container['rules_version']; + } + + /** + * Sets rules_version + * + * @param string $rules_version rules_version + * + * @return self + */ + public function setRulesVersion($rules_version) + { + if (is_null($rules_version)) { + throw new \InvalidArgumentException('non-nullable rules_version cannot be null'); + } + $this->container['rules_version'] = $rules_version; + + return $this; + } + + /** + * Gets sharing + * + * @return object|null + */ + public function getSharing() + { + return $this->container['sharing']; + } + + /** + * Sets sharing + * + * @param object|null $sharing sharing + * + * @return self + */ + public function setSharing($sharing) + { + if (is_null($sharing)) { + throw new \InvalidArgumentException('non-nullable sharing cannot be null'); + } + $this->container['sharing'] = $sharing; + + return $this; + } + + /** + * Gets takeover + * + * @return object|null + */ + public function getTakeover() + { + return $this->container['takeover']; + } + + /** + * Sets takeover + * + * @param object|null $takeover takeover + * + * @return self + */ + public function setTakeover($takeover) + { + if (is_null($takeover)) { + throw new \InvalidArgumentException('non-nullable takeover cannot be null'); + } + $this->container['takeover'] = $takeover; + + return $this; + } + + /** + * Gets travel + * + * @return object|null + */ + public function getTravel() + { + return $this->container['travel']; + } + + /** + * Sets travel + * + * @param object|null $travel travel + * + * @return self + */ + public function setTravel($travel) + { + if (is_null($travel)) { + throw new \InvalidArgumentException('non-nullable travel cannot be null'); + } + $this->container['travel'] = $travel; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/HRE.php b/generated/lib/Model/HRE.php new file mode 100644 index 0000000..3bc2704 --- /dev/null +++ b/generated/lib/Model/HRE.php @@ -0,0 +1,522 @@ + + */ +class HRE implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'HRE'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'rules_version' => 'string', + 'account_sharing' => '\ShieldLabs\Generated\Model\HREResult', + 'account_takeover' => '\ShieldLabs\Generated\Model\HREResult', + 'impossible_travel' => '\ShieldLabs\Generated\Model\HREResult' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'rules_version' => null, + 'account_sharing' => null, + 'account_takeover' => null, + 'impossible_travel' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'rules_version' => false, + 'account_sharing' => false, + 'account_takeover' => false, + 'impossible_travel' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'rules_version' => 'rules_version', + 'account_sharing' => 'account_sharing', + 'account_takeover' => 'account_takeover', + 'impossible_travel' => 'impossible_travel' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'rules_version' => 'setRulesVersion', + 'account_sharing' => 'setAccountSharing', + 'account_takeover' => 'setAccountTakeover', + 'impossible_travel' => 'setImpossibleTravel' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'rules_version' => 'getRulesVersion', + 'account_sharing' => 'getAccountSharing', + 'account_takeover' => 'getAccountTakeover', + 'impossible_travel' => 'getImpossibleTravel' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('rules_version', $data ?? [], null); + $this->setIfExists('account_sharing', $data ?? [], null); + $this->setIfExists('account_takeover', $data ?? [], null); + $this->setIfExists('impossible_travel', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['account_sharing'] === null) { + $invalidProperties[] = "'account_sharing' can't be null"; + } + if ($this->container['account_takeover'] === null) { + $invalidProperties[] = "'account_takeover' can't be null"; + } + if ($this->container['impossible_travel'] === null) { + $invalidProperties[] = "'impossible_travel' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets rules_version + * + * @return string|null + */ + public function getRulesVersion() + { + return $this->container['rules_version']; + } + + /** + * Sets rules_version + * + * @param string|null $rules_version rules_version + * + * @return self + */ + public function setRulesVersion($rules_version) + { + if (is_null($rules_version)) { + throw new \InvalidArgumentException('non-nullable rules_version cannot be null'); + } + $this->container['rules_version'] = $rules_version; + + return $this; + } + + /** + * Gets account_sharing + * + * @return \ShieldLabs\Generated\Model\HREResult + */ + public function getAccountSharing() + { + return $this->container['account_sharing']; + } + + /** + * Sets account_sharing + * + * @param \ShieldLabs\Generated\Model\HREResult $account_sharing account_sharing + * + * @return self + */ + public function setAccountSharing($account_sharing) + { + if (is_null($account_sharing)) { + throw new \InvalidArgumentException('non-nullable account_sharing cannot be null'); + } + $this->container['account_sharing'] = $account_sharing; + + return $this; + } + + /** + * Gets account_takeover + * + * @return \ShieldLabs\Generated\Model\HREResult + */ + public function getAccountTakeover() + { + return $this->container['account_takeover']; + } + + /** + * Sets account_takeover + * + * @param \ShieldLabs\Generated\Model\HREResult $account_takeover account_takeover + * + * @return self + */ + public function setAccountTakeover($account_takeover) + { + if (is_null($account_takeover)) { + throw new \InvalidArgumentException('non-nullable account_takeover cannot be null'); + } + $this->container['account_takeover'] = $account_takeover; + + return $this; + } + + /** + * Gets impossible_travel + * + * @return \ShieldLabs\Generated\Model\HREResult + */ + public function getImpossibleTravel() + { + return $this->container['impossible_travel']; + } + + /** + * Sets impossible_travel + * + * @param \ShieldLabs\Generated\Model\HREResult $impossible_travel impossible_travel + * + * @return self + */ + public function setImpossibleTravel($impossible_travel) + { + if (is_null($impossible_travel)) { + throw new \InvalidArgumentException('non-nullable impossible_travel cannot be null'); + } + $this->container['impossible_travel'] = $impossible_travel; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/HREResult.php b/generated/lib/Model/HREResult.php new file mode 100644 index 0000000..030ae03 --- /dev/null +++ b/generated/lib/Model/HREResult.php @@ -0,0 +1,580 @@ + + */ +class HREResult implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'HREResult'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'status' => 'string', + 'level' => 'string', + 'reason' => 'string', + 'devices' => 'int', + 'min_devices' => 'int' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'status' => null, + 'level' => null, + 'reason' => null, + 'devices' => null, + 'min_devices' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'status' => false, + 'level' => true, + 'reason' => false, + 'devices' => false, + 'min_devices' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'status' => 'status', + 'level' => 'level', + 'reason' => 'reason', + 'devices' => 'devices', + 'min_devices' => 'min_devices' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'status' => 'setStatus', + 'level' => 'setLevel', + 'reason' => 'setReason', + 'devices' => 'setDevices', + 'min_devices' => 'setMinDevices' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'status' => 'getStatus', + 'level' => 'getLevel', + 'reason' => 'getReason', + 'devices' => 'getDevices', + 'min_devices' => 'getMinDevices' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('status', $data ?? [], null); + $this->setIfExists('level', $data ?? [], null); + $this->setIfExists('reason', $data ?? [], null); + $this->setIfExists('devices', $data ?? [], null); + $this->setIfExists('min_devices', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['status'] === null) { + $invalidProperties[] = "'status' can't be null"; + } + if ($this->container['level'] === null && !$this->isNullableSetToNull('level')) { + $invalidProperties[] = "'level' is required"; + } + if ($this->container['reason'] === null) { + $invalidProperties[] = "'reason' can't be null"; + } + if (!is_null($this->container['devices']) && ($this->container['devices'] < 0)) { + $invalidProperties[] = "invalid value for 'devices', must be bigger than or equal to 0."; + } + + if (!is_null($this->container['min_devices']) && ($this->container['min_devices'] < 1)) { + $invalidProperties[] = "invalid value for 'min_devices', must be bigger than or equal to 1."; + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets status + * + * @return string + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param string $status status + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + throw new \InvalidArgumentException('non-nullable status cannot be null'); + } + $this->container['status'] = $status; + + return $this; + } + + /** + * Gets level + * + * @return string|null + */ + public function getLevel() + { + return $this->container['level']; + } + + /** + * Sets level + * + * @param string|null $level level + * + * @return self + */ + public function setLevel($level) + { + if (is_null($level)) { + array_push($this->openAPINullablesSetToNull, 'level'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('level', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['level'] = $level; + + return $this; + } + + /** + * Gets reason + * + * @return string + */ + public function getReason() + { + return $this->container['reason']; + } + + /** + * Sets reason + * + * @param string $reason reason + * + * @return self + */ + public function setReason($reason) + { + if (is_null($reason)) { + throw new \InvalidArgumentException('non-nullable reason cannot be null'); + } + $this->container['reason'] = $reason; + + return $this; + } + + /** + * Gets devices + * + * @return int|null + */ + public function getDevices() + { + return $this->container['devices']; + } + + /** + * Sets devices + * + * @param int|null $devices devices + * + * @return self + */ + public function setDevices($devices) + { + if (is_null($devices)) { + throw new \InvalidArgumentException('non-nullable devices cannot be null'); + } + + if (($devices < 0)) { + throw new \InvalidArgumentException('invalid value for $devices when calling HREResult., must be bigger than or equal to 0.'); + } + + $this->container['devices'] = $devices; + + return $this; + } + + /** + * Gets min_devices + * + * @return int|null + */ + public function getMinDevices() + { + return $this->container['min_devices']; + } + + /** + * Sets min_devices + * + * @param int|null $min_devices min_devices + * + * @return self + */ + public function setMinDevices($min_devices) + { + if (is_null($min_devices)) { + throw new \InvalidArgumentException('non-nullable min_devices cannot be null'); + } + + if (($min_devices < 1)) { + throw new \InvalidArgumentException('invalid value for $min_devices when calling HREResult., must be bigger than or equal to 1.'); + } + + $this->container['min_devices'] = $min_devices; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/IdentificationScoredData.php b/generated/lib/Model/IdentificationScoredData.php index ad266c1..819567e 100644 --- a/generated/lib/Model/IdentificationScoredData.php +++ b/generated/lib/Model/IdentificationScoredData.php @@ -36,7 +36,7 @@ * IdentificationScoredData Class Doc Comment * * @category Class - * @description The scored identification. Every key is always present (no key is ever omitted); only `user_hid` can be `null`. + * @description Final identification. risk_score is this scan only; no all-time entity risk. New extension fields are required by version 2026-10-06; legacy bodies remain accepted. * @package ShieldLabs\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -76,7 +76,12 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri 'risk_score' => 'int', 'signals' => '\ShieldLabs\Generated\Model\Signal[]', 'detection_flags' => '\ShieldLabs\Generated\Model\DetectionFlags', - 'observed_at' => '\DateTime' + 'observed_at' => '\DateTime', + 'result_version' => 'string', + 'scoring_version' => 'string', + 'risk_events' => '\ShieldLabs\Generated\Model\RiskEvent[]', + 'hre' => '\ShieldLabs\Generated\Model\HRE', + 'fingerprint' => '\ShieldLabs\Generated\Model\Fingerprint' ]; /** @@ -104,7 +109,12 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri 'risk_score' => null, 'signals' => null, 'detection_flags' => null, - 'observed_at' => 'date-time' + 'observed_at' => 'date-time', + 'result_version' => null, + 'scoring_version' => null, + 'risk_events' => null, + 'hre' => null, + 'fingerprint' => null ]; /** @@ -130,7 +140,12 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri 'risk_score' => false, 'signals' => false, 'detection_flags' => false, - 'observed_at' => false + 'observed_at' => false, + 'result_version' => false, + 'scoring_version' => false, + 'risk_events' => false, + 'hre' => false, + 'fingerprint' => false ]; /** @@ -236,7 +251,12 @@ public function isNullableSetToNull(string $property): bool 'risk_score' => 'risk_score', 'signals' => 'signals', 'detection_flags' => 'detection_flags', - 'observed_at' => 'observed_at' + 'observed_at' => 'observed_at', + 'result_version' => 'result_version', + 'scoring_version' => 'scoring_version', + 'risk_events' => 'risk_events', + 'hre' => 'hre', + 'fingerprint' => 'fingerprint' ]; /** @@ -262,7 +282,12 @@ public function isNullableSetToNull(string $property): bool 'risk_score' => 'setRiskScore', 'signals' => 'setSignals', 'detection_flags' => 'setDetectionFlags', - 'observed_at' => 'setObservedAt' + 'observed_at' => 'setObservedAt', + 'result_version' => 'setResultVersion', + 'scoring_version' => 'setScoringVersion', + 'risk_events' => 'setRiskEvents', + 'hre' => 'setHre', + 'fingerprint' => 'setFingerprint' ]; /** @@ -288,7 +313,12 @@ public function isNullableSetToNull(string $property): bool 'risk_score' => 'getRiskScore', 'signals' => 'getSignals', 'detection_flags' => 'getDetectionFlags', - 'observed_at' => 'getObservedAt' + 'observed_at' => 'getObservedAt', + 'result_version' => 'getResultVersion', + 'scoring_version' => 'getScoringVersion', + 'risk_events' => 'getRiskEvents', + 'hre' => 'getHre', + 'fingerprint' => 'getFingerprint' ]; /** @@ -366,6 +396,11 @@ public function __construct(?array $data = null) $this->setIfExists('signals', $data ?? [], null); $this->setIfExists('detection_flags', $data ?? [], null); $this->setIfExists('observed_at', $data ?? [], null); + $this->setIfExists('result_version', $data ?? [], null); + $this->setIfExists('scoring_version', $data ?? [], null); + $this->setIfExists('risk_events', $data ?? [], null); + $this->setIfExists('hre', $data ?? [], null); + $this->setIfExists('fingerprint', $data ?? [], null); } /** @@ -457,6 +492,10 @@ public function listInvalidProperties() $invalidProperties[] = "invalid value for 'observed_at', must be conform to the pattern /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/."; } + if (!is_null($this->container['result_version']) && (mb_strlen($this->container['result_version']) < 1)) { + $invalidProperties[] = "invalid value for 'result_version', the character length must be bigger than or equal to 1."; + } + return $invalidProperties; } @@ -956,7 +995,7 @@ public function getObservedAt() /** * Sets observed_at * - * @param \DateTime $observed_at When scoring finished and the event was built (not the page view time); identical to the envelope `created_at`. RFC 3339 in UTC with up to 9 fractional digits. + * @param \DateTime $observed_at Original snapshot scan clock, distinct from envelope created_at. RFC 3339 in UTC with up to 9 fractional digits. * * @return self */ @@ -974,6 +1013,146 @@ public function setObservedAt($observed_at) return $this; } + + /** + * Gets result_version + * + * @return string|null + */ + public function getResultVersion() + { + return $this->container['result_version']; + } + + /** + * Sets result_version + * + * @param string|null $result_version result_version + * + * @return self + */ + public function setResultVersion($result_version) + { + if (is_null($result_version)) { + throw new \InvalidArgumentException('non-nullable result_version cannot be null'); + } + + if ((mb_strlen($result_version) < 1)) { + throw new \InvalidArgumentException('invalid length for $result_version when calling IdentificationScoredData., must be bigger than or equal to 1.'); + } + + $this->container['result_version'] = $result_version; + + return $this; + } + + /** + * Gets scoring_version + * + * @return string|null + */ + public function getScoringVersion() + { + return $this->container['scoring_version']; + } + + /** + * Sets scoring_version + * + * @param string|null $scoring_version Core build source revision; core:unversioned on local builds. + * + * @return self + */ + public function setScoringVersion($scoring_version) + { + if (is_null($scoring_version)) { + throw new \InvalidArgumentException('non-nullable scoring_version cannot be null'); + } + $this->container['scoring_version'] = $scoring_version; + + return $this; + } + + /** + * Gets risk_events + * + * @return \ShieldLabs\Generated\Model\RiskEvent[]|null + */ + public function getRiskEvents() + { + return $this->container['risk_events']; + } + + /** + * Sets risk_events + * + * @param \ShieldLabs\Generated\Model\RiskEvent[]|null $risk_events risk_events + * + * @return self + */ + public function setRiskEvents($risk_events) + { + if (is_null($risk_events)) { + throw new \InvalidArgumentException('non-nullable risk_events cannot be null'); + } + $this->container['risk_events'] = $risk_events; + + return $this; + } + + /** + * Gets hre + * + * @return \ShieldLabs\Generated\Model\HRE|null + */ + public function getHre() + { + return $this->container['hre']; + } + + /** + * Sets hre + * + * @param \ShieldLabs\Generated\Model\HRE|null $hre hre + * + * @return self + */ + public function setHre($hre) + { + if (is_null($hre)) { + throw new \InvalidArgumentException('non-nullable hre cannot be null'); + } + $this->container['hre'] = $hre; + + return $this; + } + + /** + * Gets fingerprint + * + * @return \ShieldLabs\Generated\Model\Fingerprint|null + */ + public function getFingerprint() + { + return $this->container['fingerprint']; + } + + /** + * Sets fingerprint + * + * @param \ShieldLabs\Generated\Model\Fingerprint|null $fingerprint fingerprint + * + * @return self + */ + public function setFingerprint($fingerprint) + { + if (is_null($fingerprint)) { + throw new \InvalidArgumentException('non-nullable fingerprint cannot be null'); + } + $this->container['fingerprint'] = $fingerprint; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/lib/Model/IdentificationScoredEvent.php b/generated/lib/Model/IdentificationScoredEvent.php index 9208dde..49cc5aa 100644 --- a/generated/lib/Model/IdentificationScoredEvent.php +++ b/generated/lib/Model/IdentificationScoredEvent.php @@ -62,7 +62,9 @@ class IdentificationScoredEvent implements ModelInterface, ArrayAccess, \JsonSer 'event_type' => 'string', 'schema_version' => 'string', 'created_at' => '\DateTime', - 'data' => '\ShieldLabs\Generated\Model\IdentificationScoredData' + 'data' => '\ShieldLabs\Generated\Model\IdentificationScoredData', + 'event_id' => 'string', + 'site_id' => 'int' ]; /** @@ -76,7 +78,9 @@ class IdentificationScoredEvent implements ModelInterface, ArrayAccess, \JsonSer 'event_type' => null, 'schema_version' => null, 'created_at' => 'date-time', - 'data' => null + 'data' => null, + 'event_id' => null, + 'site_id' => null ]; /** @@ -88,7 +92,9 @@ class IdentificationScoredEvent implements ModelInterface, ArrayAccess, \JsonSer 'event_type' => false, 'schema_version' => false, 'created_at' => false, - 'data' => false + 'data' => false, + 'event_id' => false, + 'site_id' => false ]; /** @@ -180,7 +186,9 @@ public function isNullableSetToNull(string $property): bool 'event_type' => 'event_type', 'schema_version' => 'schema_version', 'created_at' => 'created_at', - 'data' => 'data' + 'data' => 'data', + 'event_id' => 'event_id', + 'site_id' => 'site_id' ]; /** @@ -192,7 +200,9 @@ public function isNullableSetToNull(string $property): bool 'event_type' => 'setEventType', 'schema_version' => 'setSchemaVersion', 'created_at' => 'setCreatedAt', - 'data' => 'setData' + 'data' => 'setData', + 'event_id' => 'setEventId', + 'site_id' => 'setSiteId' ]; /** @@ -204,7 +214,9 @@ public function isNullableSetToNull(string $property): bool 'event_type' => 'getEventType', 'schema_version' => 'getSchemaVersion', 'created_at' => 'getCreatedAt', - 'data' => 'getData' + 'data' => 'getData', + 'event_id' => 'getEventId', + 'site_id' => 'getSiteId' ]; /** @@ -248,19 +260,6 @@ public function getModelName() return self::$openAPIModelName; } - public const EVENT_TYPE_IDENTIFICATION_SCORED = 'identification.scored'; - - /** - * Gets allowable values of the enum - * - * @return string[] - */ - public function getEventTypeAllowableValues() - { - return [ - self::EVENT_TYPE_IDENTIFICATION_SCORED, - ]; - } /** * Associative array for storing property values @@ -281,6 +280,8 @@ public function __construct(?array $data = null) $this->setIfExists('schema_version', $data ?? [], null); $this->setIfExists('created_at', $data ?? [], null); $this->setIfExists('data', $data ?? [], null); + $this->setIfExists('event_id', $data ?? [], null); + $this->setIfExists('site_id', $data ?? [], null); } /** @@ -313,15 +314,6 @@ public function listInvalidProperties() if ($this->container['event_type'] === null) { $invalidProperties[] = "'event_type' can't be null"; } - $allowedValues = $this->getEventTypeAllowableValues(); - if (!is_null($this->container['event_type']) && !in_array($this->container['event_type'], $allowedValues, true)) { - $invalidProperties[] = sprintf( - "invalid value '%s' for 'event_type', must be one of '%s'", - $this->container['event_type'], - implode("', '", $allowedValues) - ); - } - if ($this->container['schema_version'] === null) { $invalidProperties[] = "'schema_version' can't be null"; } @@ -339,6 +331,14 @@ public function listInvalidProperties() if ($this->container['data'] === null) { $invalidProperties[] = "'data' can't be null"; } + if (!is_null($this->container['event_id']) && (mb_strlen($this->container['event_id']) < 1)) { + $invalidProperties[] = "invalid value for 'event_id', the character length must be bigger than or equal to 1."; + } + + if (!is_null($this->container['site_id']) && ($this->container['site_id'] < 1)) { + $invalidProperties[] = "invalid value for 'site_id', must be bigger than or equal to 1."; + } + return $invalidProperties; } @@ -376,16 +376,6 @@ public function setEventType($event_type) if (is_null($event_type)) { throw new \InvalidArgumentException('non-nullable event_type cannot be null'); } - $allowedValues = $this->getEventTypeAllowableValues(); - if (!in_array($event_type, $allowedValues, true)) { - throw new \InvalidArgumentException( - sprintf( - "Invalid value '%s' for 'event_type', must be one of '%s'", - $event_type, - implode("', '", $allowedValues) - ) - ); - } $this->container['event_type'] = $event_type; return $this; @@ -404,7 +394,7 @@ public function getSchemaVersion() /** * Sets schema_version * - * @param string $schema_version Version of the webhook payload contract. Every event sent today carries `2026-06-01`. Accept other values, so that a future version does not break your handler. + * @param string $schema_version Webhook contract version. Current release 2026-10-06; parsers also accept legacy 2026-06-01. * * @return self */ @@ -436,7 +426,7 @@ public function getCreatedAt() /** * Sets created_at * - * @param \DateTime $created_at When the event was built. Equal to `data.observed_at`. + * @param \DateTime $created_at RFC 3339 timestamp in UTC with up to 9 fractional digits (trailing zeros trimmed), for example `2026-09-30T12:34:57.482913041Z`. Parse it with a parser that accepts nanoseconds. * * @return self */ @@ -481,6 +471,70 @@ public function setData($data) return $this; } + + /** + * Gets event_id + * + * @return string|null + */ + public function getEventId() + { + return $this->container['event_id']; + } + + /** + * Sets event_id + * + * @param string|null $event_id Logical site/request/final-result-version/type identity. Stable across retries and endpoints. + * + * @return self + */ + public function setEventId($event_id) + { + if (is_null($event_id)) { + throw new \InvalidArgumentException('non-nullable event_id cannot be null'); + } + + if ((mb_strlen($event_id) < 1)) { + throw new \InvalidArgumentException('invalid length for $event_id when calling IdentificationScoredEvent., must be bigger than or equal to 1.'); + } + + $this->container['event_id'] = $event_id; + + return $this; + } + + /** + * Gets site_id + * + * @return int|null + */ + public function getSiteId() + { + return $this->container['site_id']; + } + + /** + * Sets site_id + * + * @param int|null $site_id Site scope when available; legacy domain-only accounts omit it. + * + * @return self + */ + public function setSiteId($site_id) + { + if (is_null($site_id)) { + throw new \InvalidArgumentException('non-nullable site_id cannot be null'); + } + + if (($site_id < 1)) { + throw new \InvalidArgumentException('invalid value for $site_id when calling IdentificationScoredEvent., must be bigger than or equal to 1.'); + } + + $this->container['site_id'] = $site_id; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/lib/Model/RiskEvent.php b/generated/lib/Model/RiskEvent.php new file mode 100644 index 0000000..de36fda --- /dev/null +++ b/generated/lib/Model/RiskEvent.php @@ -0,0 +1,561 @@ + + */ +class RiskEvent implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'RiskEvent'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'code' => 'string', + 'detected' => 'bool', + 'weight' => 'int', + 'contribution' => 'int', + 'status' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'code' => null, + 'detected' => null, + 'weight' => null, + 'contribution' => null, + 'status' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'code' => false, + 'detected' => false, + 'weight' => false, + 'contribution' => false, + 'status' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'code' => 'code', + 'detected' => 'detected', + 'weight' => 'weight', + 'contribution' => 'contribution', + 'status' => 'status' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'code' => 'setCode', + 'detected' => 'setDetected', + 'weight' => 'setWeight', + 'contribution' => 'setContribution', + 'status' => 'setStatus' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'code' => 'getCode', + 'detected' => 'getDetected', + 'weight' => 'getWeight', + 'contribution' => 'getContribution', + 'status' => 'getStatus' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('code', $data ?? [], null); + $this->setIfExists('detected', $data ?? [], null); + $this->setIfExists('weight', $data ?? [], null); + $this->setIfExists('contribution', $data ?? [], null); + $this->setIfExists('status', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['code'] === null) { + $invalidProperties[] = "'code' can't be null"; + } + if ($this->container['detected'] === null) { + $invalidProperties[] = "'detected' can't be null"; + } + if ($this->container['weight'] === null) { + $invalidProperties[] = "'weight' can't be null"; + } + if ($this->container['contribution'] === null) { + $invalidProperties[] = "'contribution' can't be null"; + } + if ($this->container['status'] === null) { + $invalidProperties[] = "'status' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets code + * + * @return string + */ + public function getCode() + { + return $this->container['code']; + } + + /** + * Sets code + * + * @param string $code code + * + * @return self + */ + public function setCode($code) + { + if (is_null($code)) { + throw new \InvalidArgumentException('non-nullable code cannot be null'); + } + $this->container['code'] = $code; + + return $this; + } + + /** + * Gets detected + * + * @return bool + */ + public function getDetected() + { + return $this->container['detected']; + } + + /** + * Sets detected + * + * @param bool $detected Final scoring flag. false does not assert that every underlying probe completed. + * + * @return self + */ + public function setDetected($detected) + { + if (is_null($detected)) { + throw new \InvalidArgumentException('non-nullable detected cannot be null'); + } + $this->container['detected'] = $detected; + + return $this; + } + + /** + * Gets weight + * + * @return int + */ + public function getWeight() + { + return $this->container['weight']; + } + + /** + * Sets weight + * + * @param int $weight Catalogue weight, not an additive score. Banned IP 999 is a marker. + * + * @return self + */ + public function setWeight($weight) + { + if (is_null($weight)) { + throw new \InvalidArgumentException('non-nullable weight cannot be null'); + } + $this->container['weight'] = $weight; + + return $this; + } + + /** + * Gets contribution + * + * @return int + */ + public function getContribution() + { + return $this->container['contribution']; + } + + /** + * Sets contribution + * + * @param int $contribution Matching score details, may contain corrections. Never recompute risk_score by summing. + * + * @return self + */ + public function setContribution($contribution) + { + if (is_null($contribution)) { + throw new \InvalidArgumentException('non-nullable contribution cannot be null'); + } + $this->container['contribution'] = $contribution; + + return $this; + } + + /** + * Gets status + * + * @return string + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param string $status The final scoring flag has been evaluated. Probe incompleteness is reported by dedicated risk events. + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + throw new \InvalidArgumentException('non-nullable status cannot be null'); + } + $this->container['status'] = $status; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/WebhookPingEvent.php b/generated/lib/Model/WebhookPingEvent.php index 6ebb9a6..e289480 100644 --- a/generated/lib/Model/WebhookPingEvent.php +++ b/generated/lib/Model/WebhookPingEvent.php @@ -61,7 +61,8 @@ class WebhookPingEvent implements ModelInterface, ArrayAccess, \JsonSerializable protected static $openAPITypes = [ 'event_type' => 'string', 'schema_version' => 'string', - 'created_at' => '\DateTime' + 'created_at' => '\DateTime', + 'event_id' => 'string' ]; /** @@ -74,7 +75,8 @@ class WebhookPingEvent implements ModelInterface, ArrayAccess, \JsonSerializable protected static $openAPIFormats = [ 'event_type' => null, 'schema_version' => null, - 'created_at' => 'date-time' + 'created_at' => 'date-time', + 'event_id' => null ]; /** @@ -85,7 +87,8 @@ class WebhookPingEvent implements ModelInterface, ArrayAccess, \JsonSerializable protected static array $openAPINullables = [ 'event_type' => false, 'schema_version' => false, - 'created_at' => false + 'created_at' => false, + 'event_id' => false ]; /** @@ -176,7 +179,8 @@ public function isNullableSetToNull(string $property): bool protected static $attributeMap = [ 'event_type' => 'event_type', 'schema_version' => 'schema_version', - 'created_at' => 'created_at' + 'created_at' => 'created_at', + 'event_id' => 'event_id' ]; /** @@ -187,7 +191,8 @@ public function isNullableSetToNull(string $property): bool protected static $setters = [ 'event_type' => 'setEventType', 'schema_version' => 'setSchemaVersion', - 'created_at' => 'setCreatedAt' + 'created_at' => 'setCreatedAt', + 'event_id' => 'setEventId' ]; /** @@ -198,7 +203,8 @@ public function isNullableSetToNull(string $property): bool protected static $getters = [ 'event_type' => 'getEventType', 'schema_version' => 'getSchemaVersion', - 'created_at' => 'getCreatedAt' + 'created_at' => 'getCreatedAt', + 'event_id' => 'getEventId' ]; /** @@ -274,6 +280,7 @@ public function __construct(?array $data = null) $this->setIfExists('event_type', $data ?? [], null); $this->setIfExists('schema_version', $data ?? [], null); $this->setIfExists('created_at', $data ?? [], null); + $this->setIfExists('event_id', $data ?? [], null); } /** @@ -329,6 +336,10 @@ public function listInvalidProperties() $invalidProperties[] = "invalid value for 'created_at', must be conform to the pattern /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/."; } + if (!is_null($this->container['event_id']) && (mb_strlen($this->container['event_id']) < 1)) { + $invalidProperties[] = "invalid value for 'event_id', the character length must be bigger than or equal to 1."; + } + return $invalidProperties; } @@ -394,7 +405,7 @@ public function getSchemaVersion() /** * Sets schema_version * - * @param string $schema_version Version of the webhook payload contract. Every event sent today carries `2026-06-01`. Accept other values, so that a future version does not break your handler. + * @param string $schema_version Webhook contract version. Current release 2026-10-06; parsers also accept legacy 2026-06-01. * * @return self */ @@ -444,6 +455,38 @@ public function setCreatedAt($created_at) return $this; } + + /** + * Gets event_id + * + * @return string|null + */ + public function getEventId() + { + return $this->container['event_id']; + } + + /** + * Sets event_id + * + * @param string|null $event_id event_id + * + * @return self + */ + public function setEventId($event_id) + { + if (is_null($event_id)) { + throw new \InvalidArgumentException('non-nullable event_id cannot be null'); + } + + if ((mb_strlen($event_id) < 1)) { + throw new \InvalidArgumentException('invalid length for $event_id when calling WebhookPingEvent., must be bigger than or equal to 1.'); + } + + $this->container['event_id'] = $event_id; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/resources/shieldlabs-api.yaml b/resources/shieldlabs-api.yaml index 2838959..b26b53a 100644 --- a/resources/shieldlabs-api.yaml +++ b/resources/shieldlabs-api.yaml @@ -467,38 +467,15 @@ webhooks: - Webhooks summary: Identification scored description: |- - Sent to every enabled webhook endpoint of your domain once for each identification, when its - scoring is final: usually about 300 ms after the browser check, and at most about 10 seconds - later when follow-up network checks run. + Sent after final snapshot scoring and mandatory fp21 + three HRE handlers. Risk score is the identification score only. AI bots and AI browser are not implemented and not emitted. - **Verify, then parse.** Compute HMAC-SHA256 over the raw request body and compare it with - `X-Shield-Signature` before you parse the JSON: - - key: the endpoint's signing secret as UTF-8 bytes, including the `whsec_` prefix (not hex- - or base64-decoded, not stripped); - - message: the exact bytes received; re-serializing parsed JSON changes them (for example, `&` - arrives escaped as `\u0026`); - - expected header: `sha256=` followed by the lowercase hex digest, compared in constant time. + Verify HMAC-SHA256 over exact raw bytes with the endpoint secret. Body event_id is signed; X-Shield-Event-Id is its convenience mirror. Persist and deduplicate by event_id before returning 2xx within the 1-second send timeout. Legacy bodies without event_id can use data.request_id. Retries preserve body and ID. Network/timeouts/429/5xx are retried with backoff up to 8 failed sends or 15 minutes of retry age; exhaustion/permanent 4xx goes to DLQ. Delivery is at-least-once attempts within that window, not guaranteed eternal delivery or exactly-once. Endpoints are independent. Redirects are not followed. - There is no timestamp, delivery ID or event-type header. Rotating a secret replaces it at - once, so accept both the old and the new secret until your deployment has switched. - - **Respond fast.** Answer any 2xx status within 1 second and process the event asynchronously; - do not redirect. Today each identification is delivered once per endpoint, with no retries. A - later release adds retries that resend identical bytes, so make your handler idempotent on - `data.request_id`. - - **Latest state.** The event is a snapshot taken when scoring finished. The History row can - still be refined afterwards (its `ver` increases) and no second event is sent. Use the History - API for guaranteed reads and for the latest state. - - **Test deliveries.** The Test button in the analytics dashboard sends a fixed sample with keys - sorted alphabetically, second-precision timestamps and two-letter country values. Its - `detection_flags` lack `browser_automation` and `search_bot`: parse missing flags as `false`. - - Deliveries are free and do not use your included identifications. + Use History for recovery/latest state; later snapshot corrections do not automatically emit another identification.scored event. security: [] parameters: - $ref: '#/components/parameters/ShieldSignature' + - $ref: '#/components/parameters/ShieldEventId' requestBody: required: true description: The event as compact JSON. Verify the signature over these exact bytes. @@ -513,11 +490,203 @@ webhooks: $ref: '#/components/examples/IdentificationScoredRateLimited' testDelivery: $ref: '#/components/examples/IdentificationScoredTestDelivery' + current: + summary: Current contract with fp21 and HRE + value: + event_id: ce256c0f66536957adc5f719250f5b10ce331bd1 + site_id: 7 + event_type: identification.scored + schema_version: '2026-10-06' + created_at: '2026-10-06T00:00:02Z' + data: + result_version: '2026-10-06T00:00:01Z' + scoring_version: core:fixture + risk_events: + - code: vpn + detected: false + weight: 30 + contribution: 0 + status: evaluated + - code: proxy + detected: false + weight: 30 + contribution: 0 + status: evaluated + - code: privacy_relay + detected: false + weight: 30 + contribution: 0 + status: evaluated + - code: check_incomplete + detected: false + weight: 0 + contribution: 0 + status: evaluated + - code: network_check_incomplete + detected: false + weight: 30 + contribution: 0 + status: evaluated + - code: os_not_detected + detected: false + weight: 30 + contribution: 0 + status: evaluated + - code: timezone_mismatch + detected: false + weight: 10 + contribution: 0 + status: evaluated + - code: javascript_disabled + detected: false + weight: 90 + contribution: 0 + status: evaluated + - code: os_mismatch + detected: false + weight: 60 + contribution: 0 + status: evaluated + - code: os_mismatch2 + detected: false + weight: 60 + contribution: 0 + status: evaluated + - code: ip_mismatch + detected: false + weight: 45 + contribution: 0 + status: evaluated + - code: latency_test + detected: false + weight: 60 + contribution: 0 + status: evaluated + - code: anti_detect_browser + detected: false + weight: 60 + contribution: 0 + status: evaluated + - code: device_spoofing + detected: false + weight: 90 + contribution: 0 + status: evaluated + - code: banned_ip + detected: false + weight: 999 + contribution: 0 + status: evaluated + - code: incognito + detected: true + weight: 0 + contribution: 0 + status: evaluated + - code: search_bot + detected: false + weight: 0 + contribution: 0 + status: evaluated + - code: browser_automation + detected: false + weight: 60 + contribution: 0 + status: evaluated + - code: tor + detected: false + weight: 99 + contribution: 0 + status: evaluated + hre: + rules_version: fp21-fixture + account_sharing: + status: evaluated + level: high + reason: '' + devices: 4 + min_devices: 4 + account_takeover: + status: evaluated + level: medium + reason: fixture + impossible_travel: + status: evaluated + level: null + reason: below_distance + request_id: 13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11 + visitor_id: 161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d + device_id: 5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f + session_id: 7a1b2c3d-4e5f-4789-abcd-ef0123456789 + cookie_id: 3f2e1d0c-9b8a-4654-b210-fedcba987654 + user_hid: test-user + domain: example.com + public_ip: + ip: 203.0.113.1 + country: '' + local_ip: + ip: '' + country: '' + connection_type: direct + os: Windows + browser: Chrome + device_type: desktop + traffic_source: + channel: '' + referrer_domain: '' + landing_url: '' + click_id_type: '' + utm_source: '' + utm_medium: '' + utm_campaign: '' + utm_content: '' + utm_term: '' + risk_score: 0 + signals: [] + detection_flags: + os_mismatch2: false + device_spoofing: false + latency_test: false + banned_ip: false + vpn: false + privacy_relay: false + browser_vpn_proxy: false + tor: false + proxy: false + datacenter_ip: false + abuser: false + os_mismatch: false + os_not_detected: false + timezone_mismatch: false + anti_detect_browser: false + browser_automation: false + ip_mismatch: false + incognito: true + search_bot: false + suspicious_paid_click: false + javascript_disabled: false + stun_not_checked: false + check_incomplete: false + observed_at: '2026-10-06T00:00:00Z' + fingerprint: + outcome: new_record + record_id: sample-record + hardware_id: sample-hardware + rules_version: fp21-fixture + sharing: + level: high + devices: 4 + min_devices: 4 + takeover: + level: medium + reason: fixture + travel: + level: '' + reason: below_distance responses: 2XX: description: Delivery accepted. The response body is ignored. 4XX: - description: Delivery rejected, for example with `401` when the signature does not verify. Any status other than 2xx, and a timeout after 1 second, counts as a failed delivery; failed deliveries are not retried today. + description: Permanent 4xx are terminal; 429 is retried. 2xx acknowledges durable acceptance. webhook.ping: post: operationId: webhookPing @@ -541,6 +710,7 @@ webhooks: security: [] parameters: - $ref: '#/components/parameters/ShieldSignature' + - $ref: '#/components/parameters/ShieldEventId' requestBody: required: true description: The ping as compact JSON with sorted keys. @@ -751,6 +921,17 @@ components: webhookPing: summary: Signature of the webhook.ping example value: sha256=ea2685733d254f7028fb031c4214583b0650de01e6c8c93131236024edd9fdd8 + ShieldEventId: + name: X-Shield-Event-Id + in: header + required: false + description: Convenience mirror of signed body event_id. Trust the body after HMAC verification. Absent on older bodies. Retries reuse the logical ID across attempts; it is not a separate delivery-attempt identity. + schema: + type: string + examples: + event: + summary: Logical event identity + value: 79e23184cd0b5aae43125e3b14910f13b107a54b schemas: RequestId: type: string @@ -1418,8 +1599,9 @@ components: SchemaVersion: type: string minLength: 1 - description: Version of the webhook payload contract. Every event sent today carries `2026-06-01`. Accept other values, so that a future version does not break your handler. + description: Webhook contract version. Current release 2026-10-06; parsers also accept legacy 2026-06-01. examples: + - '2026-10-06' - '2026-06-01' Rfc3339Timestamp: type: string @@ -1570,7 +1752,7 @@ components: DetectionFlags: type: object description: |- - Stable yes/no verdicts for the identification. Always all 19 keys. Branch on these flags and on + Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the four extension flags are present in schema 2026-10-06. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and @@ -1653,9 +1835,109 @@ components: check_incomplete: type: boolean description: Part of the browser checks timed out, so the verdict rests on partial data. Informational. + os_mismatch2: + type: boolean + device_spoofing: + type: boolean + latency_test: + type: boolean + banned_ip: + type: boolean + RiskEvent: + type: object + required: + - code + - detected + - weight + - contribution + - status + properties: + code: + type: string + detected: + type: boolean + description: Final scoring flag. false does not assert that every underlying probe completed. + weight: + type: integer + description: Catalogue weight, not an additive score. Banned IP 999 is a marker. + contribution: + type: integer + description: Matching score details, may contain corrections. Never recompute risk_score by summing. + status: + type: string + description: The final scoring flag has been evaluated. Probe incompleteness is reported by dedicated risk events. + examples: + - evaluated + HREResult: + type: object + required: + - status + - level + - reason + properties: + status: + type: string + examples: + - evaluated + - not_evaluated + - not_applicable + level: + type: + - string + - 'null' + examples: + - medium + - high + - null + reason: + type: string + devices: + type: integer + minimum: 0 + min_devices: + type: integer + minimum: 1 + HRE: + type: object + required: + - account_sharing + - account_takeover + - impossible_travel + properties: + rules_version: + type: string + account_sharing: + $ref: '#/components/schemas/HREResult' + account_takeover: + $ref: '#/components/schemas/HREResult' + impossible_travel: + $ref: '#/components/schemas/HREResult' + description: Three completed on-demand handler results. Technical errors block emission; no_history/skipped are explicit not_evaluated results. Anonymous checks are not_applicable. Multiaccount is separate. + Fingerprint: + type: object + required: + - outcome + - rules_version + properties: + outcome: + type: string + record_id: + type: string + hardware_id: + type: string + description: FP21 hardware identity, distinct from device_id. + rules_version: + type: string + sharing: + type: object + takeover: + type: object + travel: + type: object + description: FP21 for tracked users. Legacy sharing/takeover/travel mirrors are retained; prefer data.hre. Absent on anonymous checks. IdentificationScoredData: type: object - description: The scored identification. Every key is always present (no key is ever omitted); only `user_hid` can be `null`. + description: Final identification. risk_score is this scan only; no all-time entity risk. New extension fields are required by version 2026-10-06; legacy bodies remain accepted. required: - request_id - visitor_id @@ -1720,7 +2002,21 @@ components: $ref: '#/components/schemas/DetectionFlags' observed_at: $ref: '#/components/schemas/Rfc3339Timestamp' - description: When scoring finished and the event was built (not the page view time); identical to the envelope `created_at`. RFC 3339 in UTC with up to 9 fractional digits. + description: Original snapshot scan clock, distinct from envelope created_at. RFC 3339 in UTC with up to 9 fractional digits. + result_version: + type: string + minLength: 1 + scoring_version: + type: string + description: Core build source revision; core:unversioned on local builds. + risk_events: + type: array + items: + $ref: '#/components/schemas/RiskEvent' + hre: + $ref: '#/components/schemas/HRE' + fingerprint: + $ref: '#/components/schemas/Fingerprint' IdentificationScoredEvent: type: object description: 'Body of an `identification.scored` delivery. The signature is not part of the body: it arrives in the `X-Shield-Signature` header.' @@ -1738,9 +2034,47 @@ components: $ref: '#/components/schemas/SchemaVersion' created_at: $ref: '#/components/schemas/Rfc3339Timestamp' - description: When the event was built. Equal to `data.observed_at`. + description: When the event envelope was created, distinct from data.observed_at (scan time). data: $ref: '#/components/schemas/IdentificationScoredData' + event_id: + type: string + minLength: 1 + description: Logical site/request/final-result-version/type identity. Stable across retries and endpoints. + site_id: + type: integer + minimum: 1 + description: Site scope when available; legacy domain-only accounts omit it. + allOf: + - if: + properties: + schema_version: + const: '2026-10-06' + required: + - schema_version + then: + required: + - event_id + properties: + data: + required: + - result_version + - scoring_version + - risk_events + - hre + type: object + additionalProperties: true + properties: + result_version: {} + scoring_version: {} + risk_events: {} + hre: {} + event_id: + type: string + minLength: 1 + description: Logical site/request/final-result-version/type identity. Stable across retries and endpoints. + type: object + additionalProperties: true WebhookPingEvent: type: object description: Body of a `webhook.ping` delivery, sent when you verify an endpoint. It has no `data`. The keys arrive sorted alphabetically and `created_at` has second precision. @@ -1758,6 +2092,9 @@ components: created_at: $ref: '#/components/schemas/Rfc3339Timestamp' description: When the ping was sent, with second precision. + event_id: + type: string + minLength: 1 examples: HistoryPage: summary: Five identifications diff --git a/src/Event/IdentificationScoredEvent.php b/src/Event/IdentificationScoredEvent.php index b829147..56a9912 100644 --- a/src/Event/IdentificationScoredEvent.php +++ b/src/Event/IdentificationScoredEvent.php @@ -7,10 +7,9 @@ use ShieldLabs\Model\Identification; /** - * `identification.scored`: the verdict for one identification. Today it is sent once - * per identification and endpoint (one attempt, 1-second timeout, no retries). - * Future retries resend identical bytes, so make your handler idempotent on - * `$event->data->request_id`, and use the History API for guaranteed reads. + * One final identification. Retries within the bounded delivery window reuse + * event_id and exact body bytes. Deduplicate by event_id (legacy: request_id), + * persist before 2xx, and use History for recovery/latest state. */ final class IdentificationScoredEvent extends WebhookEvent { diff --git a/src/Event/WebhookEvent.php b/src/Event/WebhookEvent.php index 23c9bf6..94fceb2 100644 --- a/src/Event/WebhookEvent.php +++ b/src/Event/WebhookEvent.php @@ -11,9 +11,12 @@ */ abstract class WebhookEvent { + public readonly ?string $event_id; + public readonly ?int $site_id; + /** * @param string $event_type for example "identification.scored" - * @param string $schema_version payload schema version, "2026-06-01" today (other values are accepted) + * @param string $schema_version payload schema version, "2026-10-06" (legacy 2026-06-01 accepted) (other values are accepted) * @param \DateTimeImmutable|null $created_at when the event was created (UTC) * @param array $raw the decoded body as received */ @@ -22,5 +25,8 @@ public function __construct( public readonly string $schema_version, public readonly ?\DateTimeImmutable $created_at, public readonly array $raw = [], - ) {} + ) { + $this->event_id = \is_string($raw['event_id'] ?? null) ? $raw['event_id'] : null; + $this->site_id = \is_int($raw['site_id'] ?? null) ? $raw['site_id'] : null; + } } diff --git a/src/Internal/Normalizer.php b/src/Internal/Normalizer.php index 1805fd2..628ee0b 100644 --- a/src/Internal/Normalizer.php +++ b/src/Internal/Normalizer.php @@ -316,6 +316,12 @@ public static function fromWebhookData(array $data): array } } + foreach (['os_mismatch2', 'device_spoofing', 'latency_test', 'banned_ip'] as $key) { + if (\is_bool($rawFlags[$key] ?? null)) { + $flags[$key] = $rawFlags[$key]; + } + } + return [ 'request_id' => self::str($data['request_id'] ?? null), 'visitor_id' => self::str($data['visitor_id'] ?? null), diff --git a/src/Model/DetectionFlags.php b/src/Model/DetectionFlags.php index b95ca20..67f93c8 100644 --- a/src/Model/DetectionFlags.php +++ b/src/Model/DetectionFlags.php @@ -39,6 +39,10 @@ public function __construct( public readonly bool $javascript_disabled = false, public readonly bool $stun_not_checked = false, public readonly bool $check_incomplete = false, + public readonly ?bool $os_mismatch2 = null, + public readonly ?bool $device_spoofing = null, + public readonly ?bool $latency_test = null, + public readonly ?bool $banned_ip = null, ) {} /** @@ -68,6 +72,10 @@ public static function fromArray(array $flags): self javascript_disabled: $value('javascript_disabled'), stun_not_checked: $value('stun_not_checked'), check_incomplete: $value('check_incomplete'), + os_mismatch2: \array_key_exists('os_mismatch2', $flags) ? $value('os_mismatch2') : null, + device_spoofing: \array_key_exists('device_spoofing', $flags) ? $value('device_spoofing') : null, + latency_test: \array_key_exists('latency_test', $flags) ? $value('latency_test') : null, + banned_ip: \array_key_exists('banned_ip', $flags) ? $value('banned_ip') : null, ); } @@ -121,6 +129,10 @@ public function toArray(): array 'javascript_disabled' => $this->javascript_disabled, 'stun_not_checked' => $this->stun_not_checked, 'check_incomplete' => $this->check_incomplete, + ...($this->os_mismatch2 !== null ? ['os_mismatch2' => $this->os_mismatch2] : []), + ...($this->device_spoofing !== null ? ['device_spoofing' => $this->device_spoofing] : []), + ...($this->latency_test !== null ? ['latency_test' => $this->latency_test] : []), + ...($this->banned_ip !== null ? ['banned_ip' => $this->banned_ip] : []), ]; } diff --git a/src/Model/Identification.php b/src/Model/Identification.php index f0feb0d..1948d07 100644 --- a/src/Model/Identification.php +++ b/src/Model/Identification.php @@ -21,6 +21,15 @@ final class Identification implements \JsonSerializable public const SOURCE_WEBHOOK = 'webhook'; public const SOURCE_HISTORY = 'history'; + public readonly ?string $result_version; + public readonly ?string $scoring_version; + /** @var array|null */ + public readonly ?array $risk_events; + /** @var array|null */ + public readonly ?array $hre; + /** @var array|null */ + public readonly ?array $fingerprint; + /** * @param string $request_id UUID created in the browser for this identification * @param string $visitor_id server-side visitor ID (sticky to the device) @@ -57,7 +66,13 @@ public function __construct( public readonly ?\DateTimeImmutable $observed_at, public readonly string $source, public readonly array $raw = [], - ) {} + ) { + $this->result_version = \is_string($raw['result_version'] ?? null) ? $raw['result_version'] : null; + $this->scoring_version = \is_string($raw['scoring_version'] ?? null) ? $raw['scoring_version'] : null; + $this->risk_events = \is_array($raw['risk_events'] ?? null) ? $raw['risk_events'] : null; + $this->hre = \is_array($raw['hre'] ?? null) ? $raw['hre'] : null; + $this->fingerprint = \is_array($raw['fingerprint'] ?? null) ? $raw['fingerprint'] : null; + } /** * Builds an identification from one History API row. @@ -132,6 +147,11 @@ public function toArray(): array 'detection_flags' => $this->detection_flags->toArray(), 'observed_at' => Time::format($this->observed_at), 'source' => $this->source, + ...($this->result_version !== null ? ['result_version' => $this->result_version] : []), + ...($this->scoring_version !== null ? ['scoring_version' => $this->scoring_version] : []), + ...($this->risk_events !== null ? ['risk_events' => $this->risk_events] : []), + ...($this->hre !== null ? ['hre' => $this->hre] : []), + ...($this->fingerprint !== null ? ['fingerprint' => $this->fingerprint] : []), ]; } diff --git a/src/Webhook.php b/src/Webhook.php index 32d031c..d41612e 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -21,15 +21,14 @@ * request body (for example `file_get_contents('php://input')`), never JSON you * decoded and encoded again. * - * Today each identification is delivered once per endpoint, with a 1-second timeout - * and no retries: answer with a 2xx quickly. Future retries resend identical bytes, - * so make handlers idempotent on `data.request_id`, and use the History API for - * guaranteed reads. + * Version 2026-10-06 carries signed event_id and a convenience X-Shield-Event-Id + * header. Failed deliveries are retried in a bounded window. Persist before 2xx + * and deduplicate by event_id (legacy: data.request_id). History is recovery/latest state. */ final class Webhook { public const SIGNATURE_HEADER = 'X-Shield-Signature'; - public const SCHEMA_VERSION = '2026-06-01'; + public const SCHEMA_VERSION = '2026-10-06'; private const SIGNATURE_PREFIX = 'sha256='; diff --git a/tests/ContractCurrentTest.php b/tests/ContractCurrentTest.php new file mode 100644 index 0000000..272e56f --- /dev/null +++ b/tests/ContractCurrentTest.php @@ -0,0 +1,29 @@ +event_id); + self::assertSame(7, $event->site_id); + self::assertCount(19, $event->data->risk_events ?? []); + self::assertSame('sample-hardware', $event->data->fingerprint['hardware_id'] ?? null); + self::assertNotSame($event->data->device_id, $event->data->fingerprint['hardware_id'] ?? null); + self::assertSame('no_history', $event->data->hre['account_takeover']['reason'] ?? null); + self::assertArrayHasKey('risk_events', $event->data->toArray()); + self::assertFalse(Webhook::verifySignature($raw . ' ', $signature, 'secret')); + } +} diff --git a/tests/data/contract-current.json b/tests/data/contract-current.json new file mode 100644 index 0000000..4c6dcd6 --- /dev/null +++ b/tests/data/contract-current.json @@ -0,0 +1,243 @@ +{ + "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-06", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "result_version": "2026-10-06T00:00:01Z", + "scoring_version": "core:fixture", + "risk_events": [ + { + "code": "vpn", + "detected": false, + "weight": 30, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "proxy", + "detected": false, + "weight": 30, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "privacy_relay", + "detected": false, + "weight": 30, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "check_incomplete", + "detected": false, + "weight": 0, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "network_check_incomplete", + "detected": false, + "weight": 30, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "os_not_detected", + "detected": false, + "weight": 30, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "timezone_mismatch", + "detected": false, + "weight": 10, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "javascript_disabled", + "detected": false, + "weight": 90, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "os_mismatch", + "detected": false, + "weight": 60, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "os_mismatch2", + "detected": false, + "weight": 60, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "ip_mismatch", + "detected": false, + "weight": 45, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "latency_test", + "detected": false, + "weight": 60, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "anti_detect_browser", + "detected": false, + "weight": 60, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "device_spoofing", + "detected": false, + "weight": 90, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "banned_ip", + "detected": false, + "weight": 999, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "incognito", + "detected": true, + "weight": 0, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "search_bot", + "detected": false, + "weight": 0, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "browser_automation", + "detected": false, + "weight": 60, + "contribution": 0, + "status": "evaluated" + }, + { + "code": "tor", + "detected": false, + "weight": 99, + "contribution": 0, + "status": "evaluated" + } + ], + "hre": { + "rules_version": "fp21-fixture", + "account_sharing": { + "status": "evaluated", + "level": null, + "reason": "", + "devices": 1, + "min_devices": 4 + }, + "account_takeover": { + "status": "not_evaluated", + "level": null, + "reason": "no_history" + }, + "impossible_travel": { + "status": "not_evaluated", + "level": null, + "reason": "skipped" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "test-user", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "os_mismatch2": false, + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "browser_vpn_proxy": false, + "tor": false, + "proxy": false, + "datacenter_ip": false, + "abuser": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": true, + "search_bot": false, + "suspicious_paid_click": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z", + "fingerprint": { + "outcome": "new_record", + "record_id": "sample-record", + "hardware_id": "sample-hardware", + "rules_version": "fp21-fixture", + "sharing": { + "level": "", + "devices": 1, + "min_devices": 4 + }, + "takeover": { + "level": "", + "reason": "no_history" + }, + "travel": { + "level": "", + "reason": "skipped" + } + } + } +} From 355a92d45ccb36868a9a23b7c016458d3c5b3c28 Mon Sep 17 00:00:00 2001 From: Konstantin Serebryakov <97249355+KonstantinSerebryakov@users.noreply.github.com> Date: Tue, 6 Oct 2026 01:49:16 +0300 Subject: [PATCH 2/4] docs: align API introduction with current delivery guarantees --- resources/shieldlabs-api.yaml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/resources/shieldlabs-api.yaml b/resources/shieldlabs-api.yaml index b26b53a..03edf61 100644 --- a/resources/shieldlabs-api.yaml +++ b/resources/shieldlabs-api.yaml @@ -131,19 +131,25 @@ info: ## Webhooks - ShieldLabs sends one signed `POST` for each identification to every enabled endpoint of the - domain. A delivery has a 1-second timeout and is not retried today; a later release adds - retries that resend identical bytes. Answer 2xx within a second, process the event - asynchronously, make the handler idempotent on `data.request_id`, and use the History API for - guaranteed reads. A History row can be refined after its webhook was sent (its `ver` - increases); the webhook is not sent again. See the `identification.scored` and `webhook.ping` - entries for the signature algorithm. + ShieldLabs creates one logical signed event per identification and enabled endpoint. + Contract `2026-10-06` includes the identification score, final risk-event catalogue, + fp21 and three completed on-demand HRE results. AI bots/browser are planned only; + multiaccount is separate and all-time entity risks are not exported. + Delivery attempts have a 1-second HTTP timeout. Network errors, timeouts, 429 and 5xx + retry with backoff, up to 8 failed sends or a 15-minute retry age; other 4xx are terminal. + Worker/Redis waits can extend wall time. Retries preserve the exact signed body and + `event_id`; duplicate deliveries remain possible after an accepted request loses its response. + Verify the signature, durably store the event using its signed `event_id` (legacy fallback: + `data.request_id`), then answer 2xx within a second and process asynchronously. + Redirects are not followed. A later History refinement does not create a new webhook event. + Use the History API for the latest stored result. See the `identification.scored` and + `webhook.ping` entries for the signature algorithm. ## Compatibility Ignore fields you do not know, keep unknown values of string fields (such as new signal names or channels) instead of failing, and accept webhook `schema_version` values other than - `2026-06-01`. + `2026-10-06` (current) or `2026-06-01` (legacy). Start free at https://app.shieldlabs.ai. Guides: https://docs.shieldlabs.ai. contact: From 0800ffb16eeffab1070321b4a27a2b374b2f06d0 Mon Sep 17 00:00:00 2001 From: Konstantin Serebryakov <97249355+KonstantinSerebryakov@users.noreply.github.com> Date: Wed, 7 Oct 2026 19:47:50 +0300 Subject: [PATCH 3/4] fix(webhooks): parse simplified payload and preserve bot owners (php) --- resources/shieldlabs-api.yaml | 645 ++++++++++++++++++++++++++++--- src/Internal/Normalizer.php | 2 +- src/Model/DetectionFlags.php | 6 + src/Model/Identification.php | 9 + src/Webhook.php | 4 +- tests/ContractCurrentTest.php | 19 +- tests/data/ai-bot.json | 91 +++++ tests/data/contract-current.json | 164 +------- 8 files changed, 723 insertions(+), 217 deletions(-) create mode 100644 tests/data/ai-bot.json diff --git a/resources/shieldlabs-api.yaml b/resources/shieldlabs-api.yaml index 03edf61..9cbe011 100644 --- a/resources/shieldlabs-api.yaml +++ b/resources/shieldlabs-api.yaml @@ -466,6 +466,53 @@ paths: source: | curl "https://account.shieldlabs.ai/health" webhooks: + multi-account-changed: + post: + operationId: multiaccountChanged + tags: + - Webhooks + summary: A multi-account group changed + description: |- + Opt-in notification when a complete background detector publication detects, updates, or + resolves an exact group of accounts. Enable `multiaccount: true` on the webhook endpoint. + The default is false; existing identification.scored subscriptions are preserved. + + A repeated build with unchanged membership, level, and rules does not produce a new event. + A membership change produces resolved for the previous exact group and detected for the new + exact group. Resolved means the group is no longer supported by the current complete source + cut, including evidence expiry; it does not certify that its accounts are safe. + + Delivery is at least once, with no global ordering guarantee. Save event_id atomically with + your business operation. Within a site, publication epoch, and cluster_id, compare revision + as an integer and ignore older transitions. Verify HMAC-SHA256 over the raw received bytes; + X-Shield-Event-Id is a convenience header, and the signed body is authoritative. + + This event has no request_id or risk_score. Its source identifies the published group result; + it does not mutate an earlier identification.scored body or an already pinned Console view. + security: [] + parameters: + - $ref: '#/components/parameters/ShieldSignature' + - $ref: '#/components/parameters/ShieldEventId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MultiaccountChangedEvent' + examples: + detected: + $ref: '#/components/examples/MultiaccountDetected' + updated: + $ref: '#/components/examples/MultiaccountUpdated' + resolved: + $ref: '#/components/examples/MultiaccountResolved' + responses: + 2XX: + description: Durably accepted by the receiver. The response body is ignored. + 4XX: + description: Terminal delivery failure, except 429 which is retried. + 5XX: + description: Retried within the delivery attempt and age limits. identification.scored: post: operationId: identificationScored @@ -673,21 +720,6 @@ webhooks: stun_not_checked: false check_incomplete: false observed_at: '2026-10-06T00:00:00Z' - fingerprint: - outcome: new_record - record_id: sample-record - hardware_id: sample-hardware - rules_version: fp21-fixture - sharing: - level: high - devices: 4 - min_devices: 4 - takeover: - level: medium - reason: fixture - travel: - level: '' - reason: below_distance responses: 2XX: description: Delivery accepted. The response body is ignored. @@ -939,6 +971,144 @@ components: summary: Logical event identity value: 79e23184cd0b5aae43125e3b14910f13b107a54b schemas: + ClientIdentity: + type: object + additionalProperties: true + required: + - schema_version + - classification_revision + - classifier_version + - registry_revision + - availability + - observed_at + - decided_at + - claims + - verified + - assessments + - evidence + properties: + schema_version: + type: string + description: Open version identifier. Version 1 is currently emitted. + classification_revision: + type: integer + minimum: 1 + classifier_version: + type: string + registry_revision: + type: string + availability: + type: string + description: available or input_unavailable. Missing object means no stored identity data; it does not mean human. + observed_at: + type: string + format: date-time + decided_at: + type: string + format: date-time + claims: + type: array + items: + type: object + additionalProperties: true + required: + - profile_id + - provider_id + - provider_name + - agent_name + - client_kind + - purpose + - source + properties: + profile_id: + type: string + provider_id: + type: string + provider_name: + type: string + agent_name: + type: string + client_kind: + type: string + purpose: + type: string + source: + type: string + verified: + type: array + items: + type: object + additionalProperties: true + required: + - subject + - value_id + - evidence_ids + properties: + subject: + type: string + description: 'The proved attribute: provider, service or infrastructure. Proof does not extend to other attributes.' + value_id: + type: string + evidence_ids: + type: array + items: + type: string + assessments: + type: array + items: + type: object + additionalProperties: true + required: + - status + - reason + properties: + candidate_profile_id: + type: string + status: + type: string + reason: + type: string + evidence: + type: array + items: + type: object + additionalProperties: true + required: + - id + - method + - source_id + - source_revision + - checked_at + - evaluated_at + - covered_attributes + properties: + id: + type: string + method: + type: string + source_id: + type: string + source_revision: + type: string + checked_at: + type: string + format: date-time + evaluated_at: + type: string + format: date-time + covered_attributes: + type: array + items: + type: string + covered_components: + type: array + items: + type: string + request_binding: + type: string + replay_policy: + type: string + description: Stored ingest attribution, independent of risk score and browser automation. Claimed names are unverified. A verified provider does not verify the agent name, service or AI mode. Read proof scope from each attribution and its referenced evidence. Unknown string values and fields must be accepted. RequestId: type: string format: uuid @@ -1222,6 +1392,8 @@ components: - is_incognito - is_search_bot properties: + client_identity: + $ref: '#/components/schemas/ClientIdentity' request_id: $ref: '#/components/schemas/RequestId' session_id: @@ -1602,13 +1774,6 @@ components: type: string const: ok description: Always `ok` when the service answers. - SchemaVersion: - type: string - minLength: 1 - description: Webhook contract version. Current release 2026-10-06; parsers also accept legacy 2026-06-01. - examples: - - '2026-10-06' - - '2026-06-01' Rfc3339Timestamp: type: string format: date-time @@ -1617,6 +1782,187 @@ components: examples: - '2026-09-30T12:34:57.482913041Z' - '2026-09-30T12:34:56Z' + MultiaccountChangedData: + type: object + additionalProperties: false + required: + - kind + - domain + - action + - status + - detected + - level + - cluster_id + - revision + - previous_revision + - user_hids + - users_count + - members_complete + - evaluated_at + - rules_version + - source + - evidence_summary + properties: + kind: + const: multi_account + domain: + type: string + minLength: 1 + action: + type: string + enum: + - detected + - updated + - resolved + status: + const: evaluated + description: The result comes from a complete, verified detector source cut. Pending or unavailable work does not produce this event. + detected: + type: boolean + level: + type: + - string + - 'null' + enum: + - medium + - high + - null + cluster_id: + type: string + format: uuid + description: ID of the exact sorted user set within this site. A membership change resolves the old group and detects a new group. + revision: + type: string + pattern: ^[1-9][0-9]{0,19}$ + description: Publication version of this transition, as a decimal string to preserve UInt64 precision. + previous_revision: + type: + - string + - 'null' + pattern: ^(0|[1-9][0-9]{0,19})$ + description: Previous transition version while the group was active; null for a newly detected or reappearing group. + user_hids: + type: array + minItems: 3 + uniqueItems: true + description: Complete sorted group membership. Users from another site are never included. This list is not truncated. + items: + type: string + minLength: 1 + not: + enum: + - anonymous + - '-1' + - fail + - 00000000-0000-0000-0000-000000000000 + users_count: + type: integer + minimum: 3 + description: Equal to the length of user_hids. + members_complete: + const: true + evaluated_at: + $ref: '#/components/schemas/Rfc3339Timestamp' + rules_version: + type: string + minLength: 1 + source: + type: object + additionalProperties: false + required: + - publication_epoch + - publication_version + - coverage_id + properties: + publication_epoch: + const: multiaccount-v1 + publication_version: + type: string + pattern: ^[1-9][0-9]{0,19}$ + description: The exact immutable publication containing this result and event. + coverage_id: + type: string + pattern: ^[a-f0-9]{64}$ + description: Immutable binding to the trusted source completeness proof. + evidence_summary: + type: object + additionalProperties: false + required: + - basis_types + - rules + - detail_truncated + properties: + basis_types: + type: array + uniqueItems: true + items: + type: string + rules: + type: array + uniqueItems: true + items: + type: string + detail_truncated: + type: boolean + description: Whether the detector's underlying detailed evidence was truncated. Group membership remains complete. + allOf: + - if: + properties: + action: + const: resolved + required: + - action + then: + additionalProperties: true + properties: + detected: + const: false + level: + type: 'null' + else: + additionalProperties: true + properties: + detected: + const: true + level: + type: string + enum: + - medium + - high + MultiaccountChangedEvent: + type: object + description: An immutable opt-in notification of a complete multi-account group transition. + additionalProperties: false + required: + - event_id + - event_type + - schema_version + - site_id + - created_at + - data + properties: + event_id: + type: string + pattern: ^[a-f0-9]{40}$ + description: Stable logical event ID. Delivery retries preserve this ID and the signed body. + event_type: + const: hre.multi_account.changed + schema_version: + const: '2026-10-07' + site_id: + type: integer + minimum: 1 + created_at: + $ref: '#/components/schemas/Rfc3339Timestamp' + data: + $ref: '#/components/schemas/MultiaccountChangedData' + SchemaVersion: + type: string + minLength: 1 + description: Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. + examples: + - '2026-10-06' + - '2026-06-01' UserHid: type: - string @@ -1732,7 +2078,7 @@ components: for anti-detect browsers; - `port_scan_routed_via_proxy`: `proxy_routed_antidetect` carried forward from an earlier identification of the same device and IP; - - `browser_automation`: browser automation, for example a WebDriver-controlled browser; + - `browser_automation`: browser automation, for example a WebDriver-controlled browser (90 in task 204 scoring releases); - `javascript_disabled`: JavaScript or the browser APIs the checks need were unavailable; - `os_mismatch`: the operating system seen on the network differs from the one the browser reports; @@ -1758,7 +2104,7 @@ components: DetectionFlags: type: object description: |- - Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the four extension flags are present in schema 2026-10-06. Branch on these flags and on + Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and @@ -1807,7 +2153,7 @@ components: description: The public IP has a record of abuse in IP intelligence. os_mismatch: type: boolean - description: The operating system seen on the network differs from the one the browser reports. + description: Either internal OS consistency check detected a mismatch. The public flag combines browser/network and TCP behaviour checks. os_not_detected: type: boolean description: The operating system could not be determined from the User-Agent or the network. @@ -1819,7 +2165,7 @@ components: description: An anti-detect browser was detected. browser_automation: type: boolean - description: Browser automation was detected, for example a WebDriver-controlled browser. + description: Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60. ip_mismatch: type: boolean description: 'The public IP differs from the local IP found by the browser network check. Informational: it does not add to the score.' @@ -1841,14 +2187,22 @@ components: check_incomplete: type: boolean description: Part of the browser checks timed out, so the verdict rests on partial data. Informational. - os_mismatch2: - type: boolean device_spoofing: type: boolean latency_test: type: boolean banned_ip: type: boolean + ai_bot: + type: boolean + description: An accepted bot is classified as AI training or user-requested fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. Weight 0. The flag is optional on older payloads; its absence is not an evaluated negative result. Provider claims alone do not grant zero risk. + ai_browser: + type: boolean + description: A browser is identified by separate verified browser infrastructure. A generic browser User-Agent or a verified provider alone is insufficient. Weight 0. Optional on older payloads; absence is not an evaluated negative. + os_mismatch2: + type: boolean + deprecated: true + description: Legacy 2026-10-06 only; current payloads combine this check into os_mismatch. RiskEvent: type: object required: @@ -1860,6 +2214,7 @@ components: properties: code: type: string + description: Open catalogue of independent source signals. ai_bot and ai_browser carry weight 0; search_bot remains separate. browser_automation carries weight 90 in task 204 releases. UI Good bot and Bad bot groups are not risk event codes. Keep unknown codes and use the payload scoring_version/result_version for historical interpretation. detected: type: boolean description: Final scoring flag. false does not assert that every underlying probe completed. @@ -1903,6 +2258,11 @@ components: min_devices: type: integer minimum: 1 + cluster_id: + type: + - string + - 'null' + description: Authoritative cluster ID for this HRE result. Null when no cluster was published for the result, including old stored verdicts; never a device ID. Reused on retries. HRE: type: object required: @@ -1919,31 +2279,9 @@ components: impossible_travel: $ref: '#/components/schemas/HREResult' description: Three completed on-demand handler results. Technical errors block emission; no_history/skipped are explicit not_evaluated results. Anonymous checks are not_applicable. Multiaccount is separate. - Fingerprint: - type: object - required: - - outcome - - rules_version - properties: - outcome: - type: string - record_id: - type: string - hardware_id: - type: string - description: FP21 hardware identity, distinct from device_id. - rules_version: - type: string - sharing: - type: object - takeover: - type: object - travel: - type: object - description: FP21 for tracked users. Legacy sharing/takeover/travel mirrors are retained; prefer data.hre. Absent on anonymous checks. IdentificationScoredData: type: object - description: Final identification. risk_score is this scan only; no all-time entity risk. New extension fields are required by version 2026-10-06; legacy bodies remain accepted. + description: Final identification. risk_score is this scan only. Version 2026-10-07 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted. required: - request_id - visitor_id @@ -1964,6 +2302,8 @@ components: - detection_flags - observed_at properties: + client_identity: + $ref: '#/components/schemas/ClientIdentity' request_id: $ref: '#/components/schemas/RequestId' visitor_id: @@ -2016,13 +2356,26 @@ components: type: string description: Core build source revision; core:unversioned on local builds. risk_events: + deprecated: true + description: Legacy 2026-10-06 only; absent from current scored events. type: array items: $ref: '#/components/schemas/RiskEvent' hre: $ref: '#/components/schemas/HRE' - fingerprint: - $ref: '#/components/schemas/Fingerprint' + search_bot_owner: + type: string + description: Owner label from accepted search-bot detection; omitted when unknown or inactive. + examples: + - Google + ai_bot_owner: + type: string + description: Provider company label from accepted AI-bot detection; omitted when unknown or inactive. + examples: + - OpenAI + ai_browser_owner: + type: string + description: Owner label from accepted AI-browser detection; omitted when unknown or inactive. IdentificationScoredEvent: type: object description: 'Body of an `identification.scored` delivery. The signature is not part of the body: it arrives in the `X-Shield-Signature` header.' @@ -2081,6 +2434,83 @@ components: description: Logical site/request/final-result-version/type identity. Stable across retries and endpoints. type: object additionalProperties: true + - if: + type: object + required: + - schema_version + properties: + schema_version: + const: '2026-10-07' + additionalProperties: true + then: + type: object + required: + - event_id + properties: + event_id: + type: string + minLength: 1 + description: Logical site/request/final-result-version/type identity. Stable across retries and endpoints. + data: + type: object + required: + - result_version + - scoring_version + - hre + properties: + result_version: {} + scoring_version: {} + risk_events: {} + fingerprint: {} + detection_flags: + type: object + not: + type: object + required: + - os_mismatch2 + properties: + os_mismatch2: {} + additionalProperties: true + hre: + type: object + properties: + account_sharing: + type: object + required: + - cluster_id + properties: + cluster_id: {} + additionalProperties: true + account_takeover: + type: object + required: + - cluster_id + properties: + cluster_id: {} + additionalProperties: true + impossible_travel: + type: object + required: + - cluster_id + properties: + cluster_id: {} + additionalProperties: true + additionalProperties: true + not: + anyOf: + - type: object + required: + - risk_events + properties: + risk_events: {} + additionalProperties: true + - type: object + required: + - fingerprint + properties: + fingerprint: {} + additionalProperties: true + additionalProperties: true WebhookPingEvent: type: object description: Body of a `webhook.ping` delivery, sent when you verify an endpoint. It has no `data`. The keys arrive sorted alphabetically and `created_at` has second precision. @@ -2494,6 +2924,115 @@ components: description: The only successful answer. value: status: ok + MultiaccountDetected: + summary: Multi-account group detected + description: Generated by the Core event marshaler for a synthetic three-account group. + value: + event_id: ad5efdbb113f50d1207c3a3b1d03e54ee60dfb9a + site_id: 319 + event_type: hre.multi_account.changed + schema_version: '2026-10-07' + created_at: '2026-10-07T12:01:04Z' + data: + kind: multi_account + domain: example.com + action: detected + status: evaluated + detected: true + level: medium + cluster_id: 4293f02e-7cf0-5cda-9150-b5b8c4ee685d + revision: '7412' + previous_revision: null + user_hids: + - account_a + - account_b + - account_c + users_count: 3 + members_complete: true + evaluated_at: '2026-10-07T12:01:00Z' + rules_version: fp21-travel-pair-2026-10-06 + source: + publication_epoch: multiaccount-v1 + publication_version: '7412' + coverage_id: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + evidence_summary: + basis_types: + - lineage + rules: + - lineage_60m + detail_truncated: false + MultiaccountUpdated: + summary: Multi-account group updated + description: Generated by the Core event marshaler for a synthetic three-account group. + value: + event_id: 27225e88895f45c6f96562578308d3afea9d4ea2 + site_id: 319 + event_type: hre.multi_account.changed + schema_version: '2026-10-07' + created_at: '2026-10-07T12:01:04Z' + data: + kind: multi_account + domain: example.com + action: updated + status: evaluated + detected: true + level: high + cluster_id: 4293f02e-7cf0-5cda-9150-b5b8c4ee685d + revision: '7414' + previous_revision: '7412' + user_hids: + - account_a + - account_b + - account_c + users_count: 3 + members_complete: true + evaluated_at: '2026-10-07T12:01:00Z' + rules_version: fp21-travel-pair-2026-10-06 + source: + publication_epoch: multiaccount-v1 + publication_version: '7414' + coverage_id: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + evidence_summary: + basis_types: + - lineage + rules: + - lineage_60m + detail_truncated: false + MultiaccountResolved: + summary: Multi-account group resolved + description: Generated by the Core event marshaler for a synthetic three-account group. + value: + event_id: 0e703af70ff450065df0457d71789d32df4de079 + site_id: 319 + event_type: hre.multi_account.changed + schema_version: '2026-10-07' + created_at: '2026-10-07T12:01:04Z' + data: + kind: multi_account + domain: example.com + action: resolved + status: evaluated + detected: false + level: null + cluster_id: 4293f02e-7cf0-5cda-9150-b5b8c4ee685d + revision: '7415' + previous_revision: '7412' + user_hids: + - account_a + - account_b + - account_c + users_count: 3 + members_complete: true + evaluated_at: '2026-10-07T12:01:00Z' + rules_version: fp21-travel-pair-2026-10-06 + source: + publication_epoch: multiaccount-v1 + publication_version: '7415' + coverage_id: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + evidence_summary: + basis_types: [] + rules: [] + detail_truncated: false IdentificationScored: summary: Dangerous identification from a paid click description: Risk Score 80 from a proxy, a datacenter IP and an anti-detect browser, on a visit from a Google Ads click. diff --git a/src/Internal/Normalizer.php b/src/Internal/Normalizer.php index 628ee0b..ed4dba3 100644 --- a/src/Internal/Normalizer.php +++ b/src/Internal/Normalizer.php @@ -316,7 +316,7 @@ public static function fromWebhookData(array $data): array } } - foreach (['os_mismatch2', 'device_spoofing', 'latency_test', 'banned_ip'] as $key) { + foreach (['os_mismatch2', 'device_spoofing', 'latency_test', 'banned_ip', 'ai_bot', 'ai_browser'] as $key) { if (\is_bool($rawFlags[$key] ?? null)) { $flags[$key] = $rawFlags[$key]; } diff --git a/src/Model/DetectionFlags.php b/src/Model/DetectionFlags.php index 67f93c8..6b3ea49 100644 --- a/src/Model/DetectionFlags.php +++ b/src/Model/DetectionFlags.php @@ -43,6 +43,8 @@ public function __construct( public readonly ?bool $device_spoofing = null, public readonly ?bool $latency_test = null, public readonly ?bool $banned_ip = null, + public readonly ?bool $ai_bot = null, + public readonly ?bool $ai_browser = null, ) {} /** @@ -72,6 +74,8 @@ public static function fromArray(array $flags): self javascript_disabled: $value('javascript_disabled'), stun_not_checked: $value('stun_not_checked'), check_incomplete: $value('check_incomplete'), + ai_bot: \array_key_exists('ai_bot', $flags) ? $value('ai_bot') : null, + ai_browser: \array_key_exists('ai_browser', $flags) ? $value('ai_browser') : null, os_mismatch2: \array_key_exists('os_mismatch2', $flags) ? $value('os_mismatch2') : null, device_spoofing: \array_key_exists('device_spoofing', $flags) ? $value('device_spoofing') : null, latency_test: \array_key_exists('latency_test', $flags) ? $value('latency_test') : null, @@ -129,6 +133,8 @@ public function toArray(): array 'javascript_disabled' => $this->javascript_disabled, 'stun_not_checked' => $this->stun_not_checked, 'check_incomplete' => $this->check_incomplete, + ...($this->ai_bot !== null ? ['ai_bot' => $this->ai_bot] : []), + ...($this->ai_browser !== null ? ['ai_browser' => $this->ai_browser] : []), ...($this->os_mismatch2 !== null ? ['os_mismatch2' => $this->os_mismatch2] : []), ...($this->device_spoofing !== null ? ['device_spoofing' => $this->device_spoofing] : []), ...($this->latency_test !== null ? ['latency_test' => $this->latency_test] : []), diff --git a/src/Model/Identification.php b/src/Model/Identification.php index 1948d07..0eb80ba 100644 --- a/src/Model/Identification.php +++ b/src/Model/Identification.php @@ -21,6 +21,9 @@ final class Identification implements \JsonSerializable public const SOURCE_WEBHOOK = 'webhook'; public const SOURCE_HISTORY = 'history'; + public readonly ?string $search_bot_owner; + public readonly ?string $ai_bot_owner; + public readonly ?string $ai_browser_owner; public readonly ?string $result_version; public readonly ?string $scoring_version; /** @var array|null */ @@ -67,6 +70,9 @@ public function __construct( public readonly string $source, public readonly array $raw = [], ) { + $this->search_bot_owner = \is_string($raw['search_bot_owner'] ?? null) ? $raw['search_bot_owner'] : null; + $this->ai_bot_owner = \is_string($raw['ai_bot_owner'] ?? null) ? $raw['ai_bot_owner'] : null; + $this->ai_browser_owner = \is_string($raw['ai_browser_owner'] ?? null) ? $raw['ai_browser_owner'] : null; $this->result_version = \is_string($raw['result_version'] ?? null) ? $raw['result_version'] : null; $this->scoring_version = \is_string($raw['scoring_version'] ?? null) ? $raw['scoring_version'] : null; $this->risk_events = \is_array($raw['risk_events'] ?? null) ? $raw['risk_events'] : null; @@ -150,6 +156,9 @@ public function toArray(): array ...($this->result_version !== null ? ['result_version' => $this->result_version] : []), ...($this->scoring_version !== null ? ['scoring_version' => $this->scoring_version] : []), ...($this->risk_events !== null ? ['risk_events' => $this->risk_events] : []), + ...($this->search_bot_owner !== null ? ['search_bot_owner' => $this->search_bot_owner] : []), + ...($this->ai_bot_owner !== null ? ['ai_bot_owner' => $this->ai_bot_owner] : []), + ...($this->ai_browser_owner !== null ? ['ai_browser_owner' => $this->ai_browser_owner] : []), ...($this->hre !== null ? ['hre' => $this->hre] : []), ...($this->fingerprint !== null ? ['fingerprint' => $this->fingerprint] : []), ]; diff --git a/src/Webhook.php b/src/Webhook.php index d41612e..abccaaf 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -21,14 +21,14 @@ * request body (for example `file_get_contents('php://input')`), never JSON you * decoded and encoded again. * - * Version 2026-10-06 carries signed event_id and a convenience X-Shield-Event-Id + * Version 2026-10-07 carries signed event_id and a convenience X-Shield-Event-Id * header. Failed deliveries are retried in a bounded window. Persist before 2xx * and deduplicate by event_id (legacy: data.request_id). History is recovery/latest state. */ final class Webhook { public const SIGNATURE_HEADER = 'X-Shield-Signature'; - public const SCHEMA_VERSION = '2026-10-06'; + public const SCHEMA_VERSION = '2026-10-07'; private const SIGNATURE_PREFIX = 'sha256='; diff --git a/tests/ContractCurrentTest.php b/tests/ContractCurrentTest.php index 272e56f..8883241 100644 --- a/tests/ContractCurrentTest.php +++ b/tests/ContractCurrentTest.php @@ -19,11 +19,22 @@ public function testCurrentCoreEnvelopePreservesNewFields(): void self::assertInstanceOf(IdentificationScoredEvent::class, $event); self::assertNotEmpty($event->event_id); self::assertSame(7, $event->site_id); - self::assertCount(19, $event->data->risk_events ?? []); - self::assertSame('sample-hardware', $event->data->fingerprint['hardware_id'] ?? null); - self::assertNotSame($event->data->device_id, $event->data->fingerprint['hardware_id'] ?? null); + self::assertNull($event->data->risk_events); + self::assertNull($event->data->fingerprint); + self::assertNull($event->data->hre['account_sharing']['cluster_id']); self::assertSame('no_history', $event->data->hre['account_takeover']['reason'] ?? null); - self::assertArrayHasKey('risk_events', $event->data->toArray()); + self::assertArrayNotHasKey('risk_events', $event->data->toArray()); self::assertFalse(Webhook::verifySignature($raw . ' ', $signature, 'secret')); } + public function testAcceptedBotOwnerSurvivesNormalization(): void + { + $raw = file_get_contents(__DIR__ . '/data/ai-bot.json'); + self::assertIsString($raw); + $event = Webhook::constructEvent($raw, 'sha256=' . hash_hmac('sha256', $raw, 'secret'), 'secret'); + self::assertInstanceOf(IdentificationScoredEvent::class, $event); + self::assertSame('OpenAI', $event->data->ai_bot_owner); + self::assertTrue($event->data->detection_flags->ai_bot); + self::assertSame('OpenAI', $event->data->toArray()['ai_bot_owner']); + } + } diff --git a/tests/data/ai-bot.json b/tests/data/ai-bot.json new file mode 100644 index 0000000..30b6bc0 --- /dev/null +++ b/tests/data/ai-bot.json @@ -0,0 +1,91 @@ +{ + "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-07", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "result_version": "2026-10-06T00:00:01Z", + "scoring_version": "core:fixture", + "ai_bot_owner": "OpenAI", + "hre": { + "account_sharing": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "account_takeover": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "impossible_travel": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "anonymous", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "browser_vpn_proxy": false, + "tor": false, + "proxy": false, + "datacenter_ip": false, + "abuser": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": false, + "search_bot": false, + "ai_bot": true, + "ai_browser": false, + "suspicious_paid_click": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } +} diff --git a/tests/data/contract-current.json b/tests/data/contract-current.json index 4c6dcd6..f2f77c1 100644 --- a/tests/data/contract-current.json +++ b/tests/data/contract-current.json @@ -2,149 +2,15 @@ "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-06", + "schema_version": "2026-10-07", "created_at": "2026-10-06T00:00:02Z", "data": { "result_version": "2026-10-06T00:00:01Z", "scoring_version": "core:fixture", - "risk_events": [ - { - "code": "vpn", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "proxy", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "privacy_relay", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "check_incomplete", - "detected": false, - "weight": 0, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "network_check_incomplete", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "os_not_detected", - "detected": false, - "weight": 30, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "timezone_mismatch", - "detected": false, - "weight": 10, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "javascript_disabled", - "detected": false, - "weight": 90, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "os_mismatch", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "os_mismatch2", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "ip_mismatch", - "detected": false, - "weight": 45, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "latency_test", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "anti_detect_browser", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "device_spoofing", - "detected": false, - "weight": 90, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "banned_ip", - "detected": false, - "weight": 999, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "incognito", - "detected": true, - "weight": 0, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "search_bot", - "detected": false, - "weight": 0, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "browser_automation", - "detected": false, - "weight": 60, - "contribution": 0, - "status": "evaluated" - }, - { - "code": "tor", - "detected": false, - "weight": 99, - "contribution": 0, - "status": "evaluated" - } - ], "hre": { "rules_version": "fp21-fixture", "account_sharing": { + "cluster_id": null, "status": "evaluated", "level": null, "reason": "", @@ -152,11 +18,13 @@ "min_devices": 4 }, "account_takeover": { + "cluster_id": null, "status": "not_evaluated", "level": null, "reason": "no_history" }, "impossible_travel": { + "cluster_id": null, "status": "not_evaluated", "level": null, "reason": "skipped" @@ -195,7 +63,6 @@ "risk_score": 0, "signals": [], "detection_flags": { - "os_mismatch2": false, "device_spoofing": false, "latency_test": false, "banned_ip": false, @@ -214,30 +81,13 @@ "ip_mismatch": false, "incognito": true, "search_bot": false, + "ai_bot": false, + "ai_browser": false, "suspicious_paid_click": false, "javascript_disabled": false, "stun_not_checked": false, "check_incomplete": false }, - "observed_at": "2026-10-06T00:00:00Z", - "fingerprint": { - "outcome": "new_record", - "record_id": "sample-record", - "hardware_id": "sample-hardware", - "rules_version": "fp21-fixture", - "sharing": { - "level": "", - "devices": 1, - "min_devices": 4 - }, - "takeover": { - "level": "", - "reason": "no_history" - }, - "travel": { - "level": "", - "reason": "skipped" - } - } + "observed_at": "2026-10-06T00:00:00Z" } } From 6098ba3ed649d684a88198194c4d949232a974a0 Mon Sep 17 00:00:00 2001 From: Konstantin Serebryakov <97249355+KonstantinSerebryakov@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:08:03 +0300 Subject: [PATCH 4/4] fix: regenerate API models for simplified webhook schema --- generated/.openapi-generator/FILES | 10 +- generated/lib/Model/ClientIdentity.php | 793 ++++++++++++ .../Model/ClientIdentityAssessmentsInner.php | 484 +++++++ ...rint.php => ClientIdentityClaimsInner.php} | 266 ++-- .../lib/Model/ClientIdentityEvidenceInner.php | 737 +++++++++++ .../lib/Model/ClientIdentityVerifiedInner.php | 487 +++++++ generated/lib/Model/DetectionFlags.php | 156 ++- generated/lib/Model/HREResult.php | 53 +- generated/lib/Model/HistoryRow.php | 34 + .../lib/Model/IdentificationScoredData.php | 142 ++- .../lib/Model/IdentificationScoredEvent.php | 2 +- .../lib/Model/MultiaccountChangedData.php | 1129 +++++++++++++++++ ...MultiaccountChangedDataEvidenceSummary.php | 491 +++++++ .../Model/MultiaccountChangedDataSource.php | 512 ++++++++ .../lib/Model/MultiaccountChangedEvent.php | 640 ++++++++++ generated/lib/Model/RiskEvent.php | 2 +- generated/lib/Model/Signal.php | 2 +- generated/lib/Model/WebhookPingEvent.php | 2 +- 18 files changed, 5743 insertions(+), 199 deletions(-) create mode 100644 generated/lib/Model/ClientIdentity.php create mode 100644 generated/lib/Model/ClientIdentityAssessmentsInner.php rename generated/lib/Model/{Fingerprint.php => ClientIdentityClaimsInner.php} (61%) create mode 100644 generated/lib/Model/ClientIdentityEvidenceInner.php create mode 100644 generated/lib/Model/ClientIdentityVerifiedInner.php create mode 100644 generated/lib/Model/MultiaccountChangedData.php create mode 100644 generated/lib/Model/MultiaccountChangedDataEvidenceSummary.php create mode 100644 generated/lib/Model/MultiaccountChangedDataSource.php create mode 100644 generated/lib/Model/MultiaccountChangedEvent.php diff --git a/generated/.openapi-generator/FILES b/generated/.openapi-generator/FILES index aa0ffa1..73df3fc 100644 --- a/generated/.openapi-generator/FILES +++ b/generated/.openapi-generator/FILES @@ -7,10 +7,14 @@ lib/ApiException.php lib/Configuration.php lib/FormDataProcessor.php lib/HeaderSelector.php +lib/Model/ClientIdentity.php +lib/Model/ClientIdentityAssessmentsInner.php +lib/Model/ClientIdentityClaimsInner.php +lib/Model/ClientIdentityEvidenceInner.php +lib/Model/ClientIdentityVerifiedInner.php lib/Model/DetectionFlags.php lib/Model/DomainProfile.php lib/Model/ErrorBody.php -lib/Model/Fingerprint.php lib/Model/HRE.php lib/Model/HREResult.php lib/Model/HealthStatus.php @@ -21,6 +25,10 @@ lib/Model/IdentificationScoredEvent.php lib/Model/IpInfo.php lib/Model/LegacySnapshot.php lib/Model/ModelInterface.php +lib/Model/MultiaccountChangedData.php +lib/Model/MultiaccountChangedDataEvidenceSummary.php +lib/Model/MultiaccountChangedDataSource.php +lib/Model/MultiaccountChangedEvent.php lib/Model/RiskEvent.php lib/Model/ScoreDetail.php lib/Model/Signal.php diff --git a/generated/lib/Model/ClientIdentity.php b/generated/lib/Model/ClientIdentity.php new file mode 100644 index 0000000..e79d27a --- /dev/null +++ b/generated/lib/Model/ClientIdentity.php @@ -0,0 +1,793 @@ + + */ +class ClientIdentity implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'ClientIdentity'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'schema_version' => 'string', + 'classification_revision' => 'int', + 'classifier_version' => 'string', + 'registry_revision' => 'string', + 'availability' => 'string', + 'observed_at' => '\DateTime', + 'decided_at' => '\DateTime', + 'claims' => '\ShieldLabs\Generated\Model\ClientIdentityClaimsInner[]', + 'verified' => '\ShieldLabs\Generated\Model\ClientIdentityVerifiedInner[]', + 'assessments' => '\ShieldLabs\Generated\Model\ClientIdentityAssessmentsInner[]', + 'evidence' => '\ShieldLabs\Generated\Model\ClientIdentityEvidenceInner[]' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'schema_version' => null, + 'classification_revision' => null, + 'classifier_version' => null, + 'registry_revision' => null, + 'availability' => null, + 'observed_at' => 'date-time', + 'decided_at' => 'date-time', + 'claims' => null, + 'verified' => null, + 'assessments' => null, + 'evidence' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'schema_version' => false, + 'classification_revision' => false, + 'classifier_version' => false, + 'registry_revision' => false, + 'availability' => false, + 'observed_at' => false, + 'decided_at' => false, + 'claims' => false, + 'verified' => false, + 'assessments' => false, + 'evidence' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'schema_version' => 'schema_version', + 'classification_revision' => 'classification_revision', + 'classifier_version' => 'classifier_version', + 'registry_revision' => 'registry_revision', + 'availability' => 'availability', + 'observed_at' => 'observed_at', + 'decided_at' => 'decided_at', + 'claims' => 'claims', + 'verified' => 'verified', + 'assessments' => 'assessments', + 'evidence' => 'evidence' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'schema_version' => 'setSchemaVersion', + 'classification_revision' => 'setClassificationRevision', + 'classifier_version' => 'setClassifierVersion', + 'registry_revision' => 'setRegistryRevision', + 'availability' => 'setAvailability', + 'observed_at' => 'setObservedAt', + 'decided_at' => 'setDecidedAt', + 'claims' => 'setClaims', + 'verified' => 'setVerified', + 'assessments' => 'setAssessments', + 'evidence' => 'setEvidence' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'schema_version' => 'getSchemaVersion', + 'classification_revision' => 'getClassificationRevision', + 'classifier_version' => 'getClassifierVersion', + 'registry_revision' => 'getRegistryRevision', + 'availability' => 'getAvailability', + 'observed_at' => 'getObservedAt', + 'decided_at' => 'getDecidedAt', + 'claims' => 'getClaims', + 'verified' => 'getVerified', + 'assessments' => 'getAssessments', + 'evidence' => 'getEvidence' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('schema_version', $data ?? [], null); + $this->setIfExists('classification_revision', $data ?? [], null); + $this->setIfExists('classifier_version', $data ?? [], null); + $this->setIfExists('registry_revision', $data ?? [], null); + $this->setIfExists('availability', $data ?? [], null); + $this->setIfExists('observed_at', $data ?? [], null); + $this->setIfExists('decided_at', $data ?? [], null); + $this->setIfExists('claims', $data ?? [], null); + $this->setIfExists('verified', $data ?? [], null); + $this->setIfExists('assessments', $data ?? [], null); + $this->setIfExists('evidence', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['schema_version'] === null) { + $invalidProperties[] = "'schema_version' can't be null"; + } + if ($this->container['classification_revision'] === null) { + $invalidProperties[] = "'classification_revision' can't be null"; + } + if (($this->container['classification_revision'] < 1)) { + $invalidProperties[] = "invalid value for 'classification_revision', must be bigger than or equal to 1."; + } + + if ($this->container['classifier_version'] === null) { + $invalidProperties[] = "'classifier_version' can't be null"; + } + if ($this->container['registry_revision'] === null) { + $invalidProperties[] = "'registry_revision' can't be null"; + } + if ($this->container['availability'] === null) { + $invalidProperties[] = "'availability' can't be null"; + } + if ($this->container['observed_at'] === null) { + $invalidProperties[] = "'observed_at' can't be null"; + } + if ($this->container['decided_at'] === null) { + $invalidProperties[] = "'decided_at' can't be null"; + } + if ($this->container['claims'] === null) { + $invalidProperties[] = "'claims' can't be null"; + } + if ($this->container['verified'] === null) { + $invalidProperties[] = "'verified' can't be null"; + } + if ($this->container['assessments'] === null) { + $invalidProperties[] = "'assessments' can't be null"; + } + if ($this->container['evidence'] === null) { + $invalidProperties[] = "'evidence' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets schema_version + * + * @return string + */ + public function getSchemaVersion() + { + return $this->container['schema_version']; + } + + /** + * Sets schema_version + * + * @param string $schema_version Open version identifier. Version 1 is currently emitted. + * + * @return self + */ + public function setSchemaVersion($schema_version) + { + if (is_null($schema_version)) { + throw new \InvalidArgumentException('non-nullable schema_version cannot be null'); + } + $this->container['schema_version'] = $schema_version; + + return $this; + } + + /** + * Gets classification_revision + * + * @return int + */ + public function getClassificationRevision() + { + return $this->container['classification_revision']; + } + + /** + * Sets classification_revision + * + * @param int $classification_revision classification_revision + * + * @return self + */ + public function setClassificationRevision($classification_revision) + { + if (is_null($classification_revision)) { + throw new \InvalidArgumentException('non-nullable classification_revision cannot be null'); + } + + if (($classification_revision < 1)) { + throw new \InvalidArgumentException('invalid value for $classification_revision when calling ClientIdentity., must be bigger than or equal to 1.'); + } + + $this->container['classification_revision'] = $classification_revision; + + return $this; + } + + /** + * Gets classifier_version + * + * @return string + */ + public function getClassifierVersion() + { + return $this->container['classifier_version']; + } + + /** + * Sets classifier_version + * + * @param string $classifier_version classifier_version + * + * @return self + */ + public function setClassifierVersion($classifier_version) + { + if (is_null($classifier_version)) { + throw new \InvalidArgumentException('non-nullable classifier_version cannot be null'); + } + $this->container['classifier_version'] = $classifier_version; + + return $this; + } + + /** + * Gets registry_revision + * + * @return string + */ + public function getRegistryRevision() + { + return $this->container['registry_revision']; + } + + /** + * Sets registry_revision + * + * @param string $registry_revision registry_revision + * + * @return self + */ + public function setRegistryRevision($registry_revision) + { + if (is_null($registry_revision)) { + throw new \InvalidArgumentException('non-nullable registry_revision cannot be null'); + } + $this->container['registry_revision'] = $registry_revision; + + return $this; + } + + /** + * Gets availability + * + * @return string + */ + public function getAvailability() + { + return $this->container['availability']; + } + + /** + * Sets availability + * + * @param string $availability available or input_unavailable. Missing object means no stored identity data; it does not mean human. + * + * @return self + */ + public function setAvailability($availability) + { + if (is_null($availability)) { + throw new \InvalidArgumentException('non-nullable availability cannot be null'); + } + $this->container['availability'] = $availability; + + return $this; + } + + /** + * Gets observed_at + * + * @return \DateTime + */ + public function getObservedAt() + { + return $this->container['observed_at']; + } + + /** + * Sets observed_at + * + * @param \DateTime $observed_at observed_at + * + * @return self + */ + public function setObservedAt($observed_at) + { + if (is_null($observed_at)) { + throw new \InvalidArgumentException('non-nullable observed_at cannot be null'); + } + $this->container['observed_at'] = $observed_at; + + return $this; + } + + /** + * Gets decided_at + * + * @return \DateTime + */ + public function getDecidedAt() + { + return $this->container['decided_at']; + } + + /** + * Sets decided_at + * + * @param \DateTime $decided_at decided_at + * + * @return self + */ + public function setDecidedAt($decided_at) + { + if (is_null($decided_at)) { + throw new \InvalidArgumentException('non-nullable decided_at cannot be null'); + } + $this->container['decided_at'] = $decided_at; + + return $this; + } + + /** + * Gets claims + * + * @return \ShieldLabs\Generated\Model\ClientIdentityClaimsInner[] + */ + public function getClaims() + { + return $this->container['claims']; + } + + /** + * Sets claims + * + * @param \ShieldLabs\Generated\Model\ClientIdentityClaimsInner[] $claims claims + * + * @return self + */ + public function setClaims($claims) + { + if (is_null($claims)) { + throw new \InvalidArgumentException('non-nullable claims cannot be null'); + } + $this->container['claims'] = $claims; + + return $this; + } + + /** + * Gets verified + * + * @return \ShieldLabs\Generated\Model\ClientIdentityVerifiedInner[] + */ + public function getVerified() + { + return $this->container['verified']; + } + + /** + * Sets verified + * + * @param \ShieldLabs\Generated\Model\ClientIdentityVerifiedInner[] $verified verified + * + * @return self + */ + public function setVerified($verified) + { + if (is_null($verified)) { + throw new \InvalidArgumentException('non-nullable verified cannot be null'); + } + $this->container['verified'] = $verified; + + return $this; + } + + /** + * Gets assessments + * + * @return \ShieldLabs\Generated\Model\ClientIdentityAssessmentsInner[] + */ + public function getAssessments() + { + return $this->container['assessments']; + } + + /** + * Sets assessments + * + * @param \ShieldLabs\Generated\Model\ClientIdentityAssessmentsInner[] $assessments assessments + * + * @return self + */ + public function setAssessments($assessments) + { + if (is_null($assessments)) { + throw new \InvalidArgumentException('non-nullable assessments cannot be null'); + } + $this->container['assessments'] = $assessments; + + return $this; + } + + /** + * Gets evidence + * + * @return \ShieldLabs\Generated\Model\ClientIdentityEvidenceInner[] + */ + public function getEvidence() + { + return $this->container['evidence']; + } + + /** + * Sets evidence + * + * @param \ShieldLabs\Generated\Model\ClientIdentityEvidenceInner[] $evidence evidence + * + * @return self + */ + public function setEvidence($evidence) + { + if (is_null($evidence)) { + throw new \InvalidArgumentException('non-nullable evidence cannot be null'); + } + $this->container['evidence'] = $evidence; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/ClientIdentityAssessmentsInner.php b/generated/lib/Model/ClientIdentityAssessmentsInner.php new file mode 100644 index 0000000..32ad2c4 --- /dev/null +++ b/generated/lib/Model/ClientIdentityAssessmentsInner.php @@ -0,0 +1,484 @@ + + */ +class ClientIdentityAssessmentsInner implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'ClientIdentity_assessments_inner'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'candidate_profile_id' => 'string', + 'status' => 'string', + 'reason' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'candidate_profile_id' => null, + 'status' => null, + 'reason' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'candidate_profile_id' => false, + 'status' => false, + 'reason' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'candidate_profile_id' => 'candidate_profile_id', + 'status' => 'status', + 'reason' => 'reason' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'candidate_profile_id' => 'setCandidateProfileId', + 'status' => 'setStatus', + 'reason' => 'setReason' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'candidate_profile_id' => 'getCandidateProfileId', + 'status' => 'getStatus', + 'reason' => 'getReason' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('candidate_profile_id', $data ?? [], null); + $this->setIfExists('status', $data ?? [], null); + $this->setIfExists('reason', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['status'] === null) { + $invalidProperties[] = "'status' can't be null"; + } + if ($this->container['reason'] === null) { + $invalidProperties[] = "'reason' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets candidate_profile_id + * + * @return string|null + */ + public function getCandidateProfileId() + { + return $this->container['candidate_profile_id']; + } + + /** + * Sets candidate_profile_id + * + * @param string|null $candidate_profile_id candidate_profile_id + * + * @return self + */ + public function setCandidateProfileId($candidate_profile_id) + { + if (is_null($candidate_profile_id)) { + throw new \InvalidArgumentException('non-nullable candidate_profile_id cannot be null'); + } + $this->container['candidate_profile_id'] = $candidate_profile_id; + + return $this; + } + + /** + * Gets status + * + * @return string + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param string $status status + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + throw new \InvalidArgumentException('non-nullable status cannot be null'); + } + $this->container['status'] = $status; + + return $this; + } + + /** + * Gets reason + * + * @return string + */ + public function getReason() + { + return $this->container['reason']; + } + + /** + * Sets reason + * + * @param string $reason reason + * + * @return self + */ + public function setReason($reason) + { + if (is_null($reason)) { + throw new \InvalidArgumentException('non-nullable reason cannot be null'); + } + $this->container['reason'] = $reason; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/Fingerprint.php b/generated/lib/Model/ClientIdentityClaimsInner.php similarity index 61% rename from generated/lib/Model/Fingerprint.php rename to generated/lib/Model/ClientIdentityClaimsInner.php index 408c338..aa195ba 100644 --- a/generated/lib/Model/Fingerprint.php +++ b/generated/lib/Model/ClientIdentityClaimsInner.php @@ -1,6 +1,6 @@ */ -class Fingerprint implements ModelInterface, ArrayAccess, \JsonSerializable +class ClientIdentityClaimsInner implements ModelInterface, ArrayAccess, \JsonSerializable { public const DISCRIMINATOR = null; @@ -51,7 +50,7 @@ class Fingerprint implements ModelInterface, ArrayAccess, \JsonSerializable * * @var string */ - protected static $openAPIModelName = 'Fingerprint'; + protected static $openAPIModelName = 'ClientIdentity_claims_inner'; /** * Array of property to type mappings. Used for (de)serialization @@ -59,13 +58,13 @@ class Fingerprint implements ModelInterface, ArrayAccess, \JsonSerializable * @var string[] */ protected static $openAPITypes = [ - 'outcome' => 'string', - 'record_id' => 'string', - 'hardware_id' => 'string', - 'rules_version' => 'string', - 'sharing' => 'object', - 'takeover' => 'object', - 'travel' => 'object' + 'profile_id' => 'string', + 'provider_id' => 'string', + 'provider_name' => 'string', + 'agent_name' => 'string', + 'client_kind' => 'string', + 'purpose' => 'string', + 'source' => 'string' ]; /** @@ -76,13 +75,13 @@ class Fingerprint implements ModelInterface, ArrayAccess, \JsonSerializable * @psalm-var array */ protected static $openAPIFormats = [ - 'outcome' => null, - 'record_id' => null, - 'hardware_id' => null, - 'rules_version' => null, - 'sharing' => null, - 'takeover' => null, - 'travel' => null + 'profile_id' => null, + 'provider_id' => null, + 'provider_name' => null, + 'agent_name' => null, + 'client_kind' => null, + 'purpose' => null, + 'source' => null ]; /** @@ -91,13 +90,13 @@ class Fingerprint implements ModelInterface, ArrayAccess, \JsonSerializable * @var boolean[] */ protected static array $openAPINullables = [ - 'outcome' => false, - 'record_id' => false, - 'hardware_id' => false, - 'rules_version' => false, - 'sharing' => false, - 'takeover' => false, - 'travel' => false + 'profile_id' => false, + 'provider_id' => false, + 'provider_name' => false, + 'agent_name' => false, + 'client_kind' => false, + 'purpose' => false, + 'source' => false ]; /** @@ -186,13 +185,13 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $attributeMap = [ - 'outcome' => 'outcome', - 'record_id' => 'record_id', - 'hardware_id' => 'hardware_id', - 'rules_version' => 'rules_version', - 'sharing' => 'sharing', - 'takeover' => 'takeover', - 'travel' => 'travel' + 'profile_id' => 'profile_id', + 'provider_id' => 'provider_id', + 'provider_name' => 'provider_name', + 'agent_name' => 'agent_name', + 'client_kind' => 'client_kind', + 'purpose' => 'purpose', + 'source' => 'source' ]; /** @@ -201,13 +200,13 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $setters = [ - 'outcome' => 'setOutcome', - 'record_id' => 'setRecordId', - 'hardware_id' => 'setHardwareId', - 'rules_version' => 'setRulesVersion', - 'sharing' => 'setSharing', - 'takeover' => 'setTakeover', - 'travel' => 'setTravel' + 'profile_id' => 'setProfileId', + 'provider_id' => 'setProviderId', + 'provider_name' => 'setProviderName', + 'agent_name' => 'setAgentName', + 'client_kind' => 'setClientKind', + 'purpose' => 'setPurpose', + 'source' => 'setSource' ]; /** @@ -216,13 +215,13 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $getters = [ - 'outcome' => 'getOutcome', - 'record_id' => 'getRecordId', - 'hardware_id' => 'getHardwareId', - 'rules_version' => 'getRulesVersion', - 'sharing' => 'getSharing', - 'takeover' => 'getTakeover', - 'travel' => 'getTravel' + 'profile_id' => 'getProfileId', + 'provider_id' => 'getProviderId', + 'provider_name' => 'getProviderName', + 'agent_name' => 'getAgentName', + 'client_kind' => 'getClientKind', + 'purpose' => 'getPurpose', + 'source' => 'getSource' ]; /** @@ -282,13 +281,13 @@ public function getModelName() */ public function __construct(?array $data = null) { - $this->setIfExists('outcome', $data ?? [], null); - $this->setIfExists('record_id', $data ?? [], null); - $this->setIfExists('hardware_id', $data ?? [], null); - $this->setIfExists('rules_version', $data ?? [], null); - $this->setIfExists('sharing', $data ?? [], null); - $this->setIfExists('takeover', $data ?? [], null); - $this->setIfExists('travel', $data ?? [], null); + $this->setIfExists('profile_id', $data ?? [], null); + $this->setIfExists('provider_id', $data ?? [], null); + $this->setIfExists('provider_name', $data ?? [], null); + $this->setIfExists('agent_name', $data ?? [], null); + $this->setIfExists('client_kind', $data ?? [], null); + $this->setIfExists('purpose', $data ?? [], null); + $this->setIfExists('source', $data ?? [], null); } /** @@ -318,11 +317,26 @@ public function listInvalidProperties() { $invalidProperties = []; - if ($this->container['outcome'] === null) { - $invalidProperties[] = "'outcome' can't be null"; + if ($this->container['profile_id'] === null) { + $invalidProperties[] = "'profile_id' can't be null"; } - if ($this->container['rules_version'] === null) { - $invalidProperties[] = "'rules_version' can't be null"; + if ($this->container['provider_id'] === null) { + $invalidProperties[] = "'provider_id' can't be null"; + } + if ($this->container['provider_name'] === null) { + $invalidProperties[] = "'provider_name' can't be null"; + } + if ($this->container['agent_name'] === null) { + $invalidProperties[] = "'agent_name' can't be null"; + } + if ($this->container['client_kind'] === null) { + $invalidProperties[] = "'client_kind' can't be null"; + } + if ($this->container['purpose'] === null) { + $invalidProperties[] = "'purpose' can't be null"; + } + if ($this->container['source'] === null) { + $invalidProperties[] = "'source' can't be null"; } return $invalidProperties; } @@ -340,190 +354,190 @@ public function valid() /** - * Gets outcome + * Gets profile_id * * @return string */ - public function getOutcome() + public function getProfileId() { - return $this->container['outcome']; + return $this->container['profile_id']; } /** - * Sets outcome + * Sets profile_id * - * @param string $outcome outcome + * @param string $profile_id profile_id * * @return self */ - public function setOutcome($outcome) + public function setProfileId($profile_id) { - if (is_null($outcome)) { - throw new \InvalidArgumentException('non-nullable outcome cannot be null'); + if (is_null($profile_id)) { + throw new \InvalidArgumentException('non-nullable profile_id cannot be null'); } - $this->container['outcome'] = $outcome; + $this->container['profile_id'] = $profile_id; return $this; } /** - * Gets record_id + * Gets provider_id * - * @return string|null + * @return string */ - public function getRecordId() + public function getProviderId() { - return $this->container['record_id']; + return $this->container['provider_id']; } /** - * Sets record_id + * Sets provider_id * - * @param string|null $record_id record_id + * @param string $provider_id provider_id * * @return self */ - public function setRecordId($record_id) + public function setProviderId($provider_id) { - if (is_null($record_id)) { - throw new \InvalidArgumentException('non-nullable record_id cannot be null'); + if (is_null($provider_id)) { + throw new \InvalidArgumentException('non-nullable provider_id cannot be null'); } - $this->container['record_id'] = $record_id; + $this->container['provider_id'] = $provider_id; return $this; } /** - * Gets hardware_id + * Gets provider_name * - * @return string|null + * @return string */ - public function getHardwareId() + public function getProviderName() { - return $this->container['hardware_id']; + return $this->container['provider_name']; } /** - * Sets hardware_id + * Sets provider_name * - * @param string|null $hardware_id FP21 hardware identity, distinct from device_id. + * @param string $provider_name provider_name * * @return self */ - public function setHardwareId($hardware_id) + public function setProviderName($provider_name) { - if (is_null($hardware_id)) { - throw new \InvalidArgumentException('non-nullable hardware_id cannot be null'); + if (is_null($provider_name)) { + throw new \InvalidArgumentException('non-nullable provider_name cannot be null'); } - $this->container['hardware_id'] = $hardware_id; + $this->container['provider_name'] = $provider_name; return $this; } /** - * Gets rules_version + * Gets agent_name * * @return string */ - public function getRulesVersion() + public function getAgentName() { - return $this->container['rules_version']; + return $this->container['agent_name']; } /** - * Sets rules_version + * Sets agent_name * - * @param string $rules_version rules_version + * @param string $agent_name agent_name * * @return self */ - public function setRulesVersion($rules_version) + public function setAgentName($agent_name) { - if (is_null($rules_version)) { - throw new \InvalidArgumentException('non-nullable rules_version cannot be null'); + if (is_null($agent_name)) { + throw new \InvalidArgumentException('non-nullable agent_name cannot be null'); } - $this->container['rules_version'] = $rules_version; + $this->container['agent_name'] = $agent_name; return $this; } /** - * Gets sharing + * Gets client_kind * - * @return object|null + * @return string */ - public function getSharing() + public function getClientKind() { - return $this->container['sharing']; + return $this->container['client_kind']; } /** - * Sets sharing + * Sets client_kind * - * @param object|null $sharing sharing + * @param string $client_kind client_kind * * @return self */ - public function setSharing($sharing) + public function setClientKind($client_kind) { - if (is_null($sharing)) { - throw new \InvalidArgumentException('non-nullable sharing cannot be null'); + if (is_null($client_kind)) { + throw new \InvalidArgumentException('non-nullable client_kind cannot be null'); } - $this->container['sharing'] = $sharing; + $this->container['client_kind'] = $client_kind; return $this; } /** - * Gets takeover + * Gets purpose * - * @return object|null + * @return string */ - public function getTakeover() + public function getPurpose() { - return $this->container['takeover']; + return $this->container['purpose']; } /** - * Sets takeover + * Sets purpose * - * @param object|null $takeover takeover + * @param string $purpose purpose * * @return self */ - public function setTakeover($takeover) + public function setPurpose($purpose) { - if (is_null($takeover)) { - throw new \InvalidArgumentException('non-nullable takeover cannot be null'); + if (is_null($purpose)) { + throw new \InvalidArgumentException('non-nullable purpose cannot be null'); } - $this->container['takeover'] = $takeover; + $this->container['purpose'] = $purpose; return $this; } /** - * Gets travel + * Gets source * - * @return object|null + * @return string */ - public function getTravel() + public function getSource() { - return $this->container['travel']; + return $this->container['source']; } /** - * Sets travel + * Sets source * - * @param object|null $travel travel + * @param string $source source * * @return self */ - public function setTravel($travel) + public function setSource($source) { - if (is_null($travel)) { - throw new \InvalidArgumentException('non-nullable travel cannot be null'); + if (is_null($source)) { + throw new \InvalidArgumentException('non-nullable source cannot be null'); } - $this->container['travel'] = $travel; + $this->container['source'] = $source; return $this; } diff --git a/generated/lib/Model/ClientIdentityEvidenceInner.php b/generated/lib/Model/ClientIdentityEvidenceInner.php new file mode 100644 index 0000000..3f8fad6 --- /dev/null +++ b/generated/lib/Model/ClientIdentityEvidenceInner.php @@ -0,0 +1,737 @@ + + */ +class ClientIdentityEvidenceInner implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'ClientIdentity_evidence_inner'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'id' => 'string', + 'method' => 'string', + 'source_id' => 'string', + 'source_revision' => 'string', + 'checked_at' => '\DateTime', + 'evaluated_at' => '\DateTime', + 'covered_attributes' => 'string[]', + 'covered_components' => 'string[]', + 'request_binding' => 'string', + 'replay_policy' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'id' => null, + 'method' => null, + 'source_id' => null, + 'source_revision' => null, + 'checked_at' => 'date-time', + 'evaluated_at' => 'date-time', + 'covered_attributes' => null, + 'covered_components' => null, + 'request_binding' => null, + 'replay_policy' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'id' => false, + 'method' => false, + 'source_id' => false, + 'source_revision' => false, + 'checked_at' => false, + 'evaluated_at' => false, + 'covered_attributes' => false, + 'covered_components' => false, + 'request_binding' => false, + 'replay_policy' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'id' => 'id', + 'method' => 'method', + 'source_id' => 'source_id', + 'source_revision' => 'source_revision', + 'checked_at' => 'checked_at', + 'evaluated_at' => 'evaluated_at', + 'covered_attributes' => 'covered_attributes', + 'covered_components' => 'covered_components', + 'request_binding' => 'request_binding', + 'replay_policy' => 'replay_policy' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'id' => 'setId', + 'method' => 'setMethod', + 'source_id' => 'setSourceId', + 'source_revision' => 'setSourceRevision', + 'checked_at' => 'setCheckedAt', + 'evaluated_at' => 'setEvaluatedAt', + 'covered_attributes' => 'setCoveredAttributes', + 'covered_components' => 'setCoveredComponents', + 'request_binding' => 'setRequestBinding', + 'replay_policy' => 'setReplayPolicy' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'id' => 'getId', + 'method' => 'getMethod', + 'source_id' => 'getSourceId', + 'source_revision' => 'getSourceRevision', + 'checked_at' => 'getCheckedAt', + 'evaluated_at' => 'getEvaluatedAt', + 'covered_attributes' => 'getCoveredAttributes', + 'covered_components' => 'getCoveredComponents', + 'request_binding' => 'getRequestBinding', + 'replay_policy' => 'getReplayPolicy' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('id', $data ?? [], null); + $this->setIfExists('method', $data ?? [], null); + $this->setIfExists('source_id', $data ?? [], null); + $this->setIfExists('source_revision', $data ?? [], null); + $this->setIfExists('checked_at', $data ?? [], null); + $this->setIfExists('evaluated_at', $data ?? [], null); + $this->setIfExists('covered_attributes', $data ?? [], null); + $this->setIfExists('covered_components', $data ?? [], null); + $this->setIfExists('request_binding', $data ?? [], null); + $this->setIfExists('replay_policy', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['id'] === null) { + $invalidProperties[] = "'id' can't be null"; + } + if ($this->container['method'] === null) { + $invalidProperties[] = "'method' can't be null"; + } + if ($this->container['source_id'] === null) { + $invalidProperties[] = "'source_id' can't be null"; + } + if ($this->container['source_revision'] === null) { + $invalidProperties[] = "'source_revision' can't be null"; + } + if ($this->container['checked_at'] === null) { + $invalidProperties[] = "'checked_at' can't be null"; + } + if ($this->container['evaluated_at'] === null) { + $invalidProperties[] = "'evaluated_at' can't be null"; + } + if ($this->container['covered_attributes'] === null) { + $invalidProperties[] = "'covered_attributes' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets id + * + * @return string + */ + public function getId() + { + return $this->container['id']; + } + + /** + * Sets id + * + * @param string $id id + * + * @return self + */ + public function setId($id) + { + if (is_null($id)) { + throw new \InvalidArgumentException('non-nullable id cannot be null'); + } + $this->container['id'] = $id; + + return $this; + } + + /** + * Gets method + * + * @return string + */ + public function getMethod() + { + return $this->container['method']; + } + + /** + * Sets method + * + * @param string $method method + * + * @return self + */ + public function setMethod($method) + { + if (is_null($method)) { + throw new \InvalidArgumentException('non-nullable method cannot be null'); + } + $this->container['method'] = $method; + + return $this; + } + + /** + * Gets source_id + * + * @return string + */ + public function getSourceId() + { + return $this->container['source_id']; + } + + /** + * Sets source_id + * + * @param string $source_id source_id + * + * @return self + */ + public function setSourceId($source_id) + { + if (is_null($source_id)) { + throw new \InvalidArgumentException('non-nullable source_id cannot be null'); + } + $this->container['source_id'] = $source_id; + + return $this; + } + + /** + * Gets source_revision + * + * @return string + */ + public function getSourceRevision() + { + return $this->container['source_revision']; + } + + /** + * Sets source_revision + * + * @param string $source_revision source_revision + * + * @return self + */ + public function setSourceRevision($source_revision) + { + if (is_null($source_revision)) { + throw new \InvalidArgumentException('non-nullable source_revision cannot be null'); + } + $this->container['source_revision'] = $source_revision; + + return $this; + } + + /** + * Gets checked_at + * + * @return \DateTime + */ + public function getCheckedAt() + { + return $this->container['checked_at']; + } + + /** + * Sets checked_at + * + * @param \DateTime $checked_at checked_at + * + * @return self + */ + public function setCheckedAt($checked_at) + { + if (is_null($checked_at)) { + throw new \InvalidArgumentException('non-nullable checked_at cannot be null'); + } + $this->container['checked_at'] = $checked_at; + + return $this; + } + + /** + * Gets evaluated_at + * + * @return \DateTime + */ + public function getEvaluatedAt() + { + return $this->container['evaluated_at']; + } + + /** + * Sets evaluated_at + * + * @param \DateTime $evaluated_at evaluated_at + * + * @return self + */ + public function setEvaluatedAt($evaluated_at) + { + if (is_null($evaluated_at)) { + throw new \InvalidArgumentException('non-nullable evaluated_at cannot be null'); + } + $this->container['evaluated_at'] = $evaluated_at; + + return $this; + } + + /** + * Gets covered_attributes + * + * @return string[] + */ + public function getCoveredAttributes() + { + return $this->container['covered_attributes']; + } + + /** + * Sets covered_attributes + * + * @param string[] $covered_attributes covered_attributes + * + * @return self + */ + public function setCoveredAttributes($covered_attributes) + { + if (is_null($covered_attributes)) { + throw new \InvalidArgumentException('non-nullable covered_attributes cannot be null'); + } + $this->container['covered_attributes'] = $covered_attributes; + + return $this; + } + + /** + * Gets covered_components + * + * @return string[]|null + */ + public function getCoveredComponents() + { + return $this->container['covered_components']; + } + + /** + * Sets covered_components + * + * @param string[]|null $covered_components covered_components + * + * @return self + */ + public function setCoveredComponents($covered_components) + { + if (is_null($covered_components)) { + throw new \InvalidArgumentException('non-nullable covered_components cannot be null'); + } + $this->container['covered_components'] = $covered_components; + + return $this; + } + + /** + * Gets request_binding + * + * @return string|null + */ + public function getRequestBinding() + { + return $this->container['request_binding']; + } + + /** + * Sets request_binding + * + * @param string|null $request_binding request_binding + * + * @return self + */ + public function setRequestBinding($request_binding) + { + if (is_null($request_binding)) { + throw new \InvalidArgumentException('non-nullable request_binding cannot be null'); + } + $this->container['request_binding'] = $request_binding; + + return $this; + } + + /** + * Gets replay_policy + * + * @return string|null + */ + public function getReplayPolicy() + { + return $this->container['replay_policy']; + } + + /** + * Sets replay_policy + * + * @param string|null $replay_policy replay_policy + * + * @return self + */ + public function setReplayPolicy($replay_policy) + { + if (is_null($replay_policy)) { + throw new \InvalidArgumentException('non-nullable replay_policy cannot be null'); + } + $this->container['replay_policy'] = $replay_policy; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/ClientIdentityVerifiedInner.php b/generated/lib/Model/ClientIdentityVerifiedInner.php new file mode 100644 index 0000000..e103ac4 --- /dev/null +++ b/generated/lib/Model/ClientIdentityVerifiedInner.php @@ -0,0 +1,487 @@ + + */ +class ClientIdentityVerifiedInner implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'ClientIdentity_verified_inner'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'subject' => 'string', + 'value_id' => 'string', + 'evidence_ids' => 'string[]' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'subject' => null, + 'value_id' => null, + 'evidence_ids' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'subject' => false, + 'value_id' => false, + 'evidence_ids' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'subject' => 'subject', + 'value_id' => 'value_id', + 'evidence_ids' => 'evidence_ids' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'subject' => 'setSubject', + 'value_id' => 'setValueId', + 'evidence_ids' => 'setEvidenceIds' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'subject' => 'getSubject', + 'value_id' => 'getValueId', + 'evidence_ids' => 'getEvidenceIds' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('subject', $data ?? [], null); + $this->setIfExists('value_id', $data ?? [], null); + $this->setIfExists('evidence_ids', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['subject'] === null) { + $invalidProperties[] = "'subject' can't be null"; + } + if ($this->container['value_id'] === null) { + $invalidProperties[] = "'value_id' can't be null"; + } + if ($this->container['evidence_ids'] === null) { + $invalidProperties[] = "'evidence_ids' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets subject + * + * @return string + */ + public function getSubject() + { + return $this->container['subject']; + } + + /** + * Sets subject + * + * @param string $subject The proved attribute: provider, service or infrastructure. Proof does not extend to other attributes. + * + * @return self + */ + public function setSubject($subject) + { + if (is_null($subject)) { + throw new \InvalidArgumentException('non-nullable subject cannot be null'); + } + $this->container['subject'] = $subject; + + return $this; + } + + /** + * Gets value_id + * + * @return string + */ + public function getValueId() + { + return $this->container['value_id']; + } + + /** + * Sets value_id + * + * @param string $value_id value_id + * + * @return self + */ + public function setValueId($value_id) + { + if (is_null($value_id)) { + throw new \InvalidArgumentException('non-nullable value_id cannot be null'); + } + $this->container['value_id'] = $value_id; + + return $this; + } + + /** + * Gets evidence_ids + * + * @return string[] + */ + public function getEvidenceIds() + { + return $this->container['evidence_ids']; + } + + /** + * Sets evidence_ids + * + * @param string[] $evidence_ids evidence_ids + * + * @return self + */ + public function setEvidenceIds($evidence_ids) + { + if (is_null($evidence_ids)) { + throw new \InvalidArgumentException('non-nullable evidence_ids cannot be null'); + } + $this->container['evidence_ids'] = $evidence_ids; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/DetectionFlags.php b/generated/lib/Model/DetectionFlags.php index a722cbf..80d1077 100644 --- a/generated/lib/Model/DetectionFlags.php +++ b/generated/lib/Model/DetectionFlags.php @@ -36,7 +36,7 @@ * DetectionFlags Class Doc Comment * * @category Class - * @description Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the four extension flags are present in schema 2026-10-06. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and `javascript_disabled` are always `false`. + * @description Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and `javascript_disabled` are always `false`. * @package ShieldLabs\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -78,10 +78,12 @@ class DetectionFlags implements ModelInterface, ArrayAccess, \JsonSerializable 'javascript_disabled' => 'bool', 'stun_not_checked' => 'bool', 'check_incomplete' => 'bool', - 'os_mismatch2' => 'bool', 'device_spoofing' => 'bool', 'latency_test' => 'bool', - 'banned_ip' => 'bool' + 'banned_ip' => 'bool', + 'ai_bot' => 'bool', + 'ai_browser' => 'bool', + 'os_mismatch2' => 'bool' ]; /** @@ -111,10 +113,12 @@ class DetectionFlags implements ModelInterface, ArrayAccess, \JsonSerializable 'javascript_disabled' => null, 'stun_not_checked' => null, 'check_incomplete' => null, - 'os_mismatch2' => null, 'device_spoofing' => null, 'latency_test' => null, - 'banned_ip' => null + 'banned_ip' => null, + 'ai_bot' => null, + 'ai_browser' => null, + 'os_mismatch2' => null ]; /** @@ -142,10 +146,12 @@ class DetectionFlags implements ModelInterface, ArrayAccess, \JsonSerializable 'javascript_disabled' => false, 'stun_not_checked' => false, 'check_incomplete' => false, - 'os_mismatch2' => false, 'device_spoofing' => false, 'latency_test' => false, - 'banned_ip' => false + 'banned_ip' => false, + 'ai_bot' => false, + 'ai_browser' => false, + 'os_mismatch2' => false ]; /** @@ -253,10 +259,12 @@ public function isNullableSetToNull(string $property): bool 'javascript_disabled' => 'javascript_disabled', 'stun_not_checked' => 'stun_not_checked', 'check_incomplete' => 'check_incomplete', - 'os_mismatch2' => 'os_mismatch2', 'device_spoofing' => 'device_spoofing', 'latency_test' => 'latency_test', - 'banned_ip' => 'banned_ip' + 'banned_ip' => 'banned_ip', + 'ai_bot' => 'ai_bot', + 'ai_browser' => 'ai_browser', + 'os_mismatch2' => 'os_mismatch2' ]; /** @@ -284,10 +292,12 @@ public function isNullableSetToNull(string $property): bool 'javascript_disabled' => 'setJavascriptDisabled', 'stun_not_checked' => 'setStunNotChecked', 'check_incomplete' => 'setCheckIncomplete', - 'os_mismatch2' => 'setOsMismatch2', 'device_spoofing' => 'setDeviceSpoofing', 'latency_test' => 'setLatencyTest', - 'banned_ip' => 'setBannedIp' + 'banned_ip' => 'setBannedIp', + 'ai_bot' => 'setAiBot', + 'ai_browser' => 'setAiBrowser', + 'os_mismatch2' => 'setOsMismatch2' ]; /** @@ -315,10 +325,12 @@ public function isNullableSetToNull(string $property): bool 'javascript_disabled' => 'getJavascriptDisabled', 'stun_not_checked' => 'getStunNotChecked', 'check_incomplete' => 'getCheckIncomplete', - 'os_mismatch2' => 'getOsMismatch2', 'device_spoofing' => 'getDeviceSpoofing', 'latency_test' => 'getLatencyTest', - 'banned_ip' => 'getBannedIp' + 'banned_ip' => 'getBannedIp', + 'ai_bot' => 'getAiBot', + 'ai_browser' => 'getAiBrowser', + 'os_mismatch2' => 'getOsMismatch2' ]; /** @@ -397,10 +409,12 @@ public function __construct(?array $data = null) $this->setIfExists('javascript_disabled', $data ?? [], null); $this->setIfExists('stun_not_checked', $data ?? [], null); $this->setIfExists('check_incomplete', $data ?? [], null); - $this->setIfExists('os_mismatch2', $data ?? [], null); $this->setIfExists('device_spoofing', $data ?? [], null); $this->setIfExists('latency_test', $data ?? [], null); $this->setIfExists('banned_ip', $data ?? [], null); + $this->setIfExists('ai_bot', $data ?? [], null); + $this->setIfExists('ai_browser', $data ?? [], null); + $this->setIfExists('os_mismatch2', $data ?? [], null); } /** @@ -704,7 +718,7 @@ public function getOsMismatch() /** * Sets os_mismatch * - * @param bool $os_mismatch The operating system seen on the network differs from the one the browser reports. + * @param bool $os_mismatch Either internal OS consistency check detected a mismatch. The public flag combines browser/network and TCP behaviour checks. * * @return self */ @@ -812,7 +826,7 @@ public function getBrowserAutomation() /** * Sets browser_automation * - * @param bool $browser_automation Browser automation was detected, for example a WebDriver-controlled browser. + * @param bool $browser_automation Browser automation was detected, for example a WebDriver-controlled browser. Catalogue weight is 90 for task 204 scoring releases; historical result versions may retain the earlier weight 60. * * @return self */ @@ -1015,33 +1029,6 @@ public function setCheckIncomplete($check_incomplete) return $this; } - /** - * Gets os_mismatch2 - * - * @return bool|null - */ - public function getOsMismatch2() - { - return $this->container['os_mismatch2']; - } - - /** - * Sets os_mismatch2 - * - * @param bool|null $os_mismatch2 os_mismatch2 - * - * @return self - */ - public function setOsMismatch2($os_mismatch2) - { - if (is_null($os_mismatch2)) { - throw new \InvalidArgumentException('non-nullable os_mismatch2 cannot be null'); - } - $this->container['os_mismatch2'] = $os_mismatch2; - - return $this; - } - /** * Gets device_spoofing * @@ -1122,6 +1109,89 @@ public function setBannedIp($banned_ip) return $this; } + + /** + * Gets ai_bot + * + * @return bool|null + */ + public function getAiBot() + { + return $this->container['ai_bot']; + } + + /** + * Sets ai_bot + * + * @param bool|null $ai_bot An accepted bot is classified as AI training or user-requested fetch. ChatGPT-User, Claude-User and Perplexity-User are AI bots, not AI browsers. Weight 0. The flag is optional on older payloads; its absence is not an evaluated negative result. Provider claims alone do not grant zero risk. + * + * @return self + */ + public function setAiBot($ai_bot) + { + if (is_null($ai_bot)) { + throw new \InvalidArgumentException('non-nullable ai_bot cannot be null'); + } + $this->container['ai_bot'] = $ai_bot; + + return $this; + } + + /** + * Gets ai_browser + * + * @return bool|null + */ + public function getAiBrowser() + { + return $this->container['ai_browser']; + } + + /** + * Sets ai_browser + * + * @param bool|null $ai_browser A browser is identified by separate verified browser infrastructure. A generic browser User-Agent or a verified provider alone is insufficient. Weight 0. Optional on older payloads; absence is not an evaluated negative. + * + * @return self + */ + public function setAiBrowser($ai_browser) + { + if (is_null($ai_browser)) { + throw new \InvalidArgumentException('non-nullable ai_browser cannot be null'); + } + $this->container['ai_browser'] = $ai_browser; + + return $this; + } + + /** + * Gets os_mismatch2 + * + * @return bool|null + * @deprecated + */ + public function getOsMismatch2() + { + return $this->container['os_mismatch2']; + } + + /** + * Sets os_mismatch2 + * + * @param bool|null $os_mismatch2 Legacy 2026-10-06 only; current payloads combine this check into os_mismatch. + * + * @return self + * @deprecated + */ + public function setOsMismatch2($os_mismatch2) + { + if (is_null($os_mismatch2)) { + throw new \InvalidArgumentException('non-nullable os_mismatch2 cannot be null'); + } + $this->container['os_mismatch2'] = $os_mismatch2; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/lib/Model/HREResult.php b/generated/lib/Model/HREResult.php index 030ae03..ba467ad 100644 --- a/generated/lib/Model/HREResult.php +++ b/generated/lib/Model/HREResult.php @@ -62,7 +62,8 @@ class HREResult implements ModelInterface, ArrayAccess, \JsonSerializable 'level' => 'string', 'reason' => 'string', 'devices' => 'int', - 'min_devices' => 'int' + 'min_devices' => 'int', + 'cluster_id' => 'string' ]; /** @@ -77,7 +78,8 @@ class HREResult implements ModelInterface, ArrayAccess, \JsonSerializable 'level' => null, 'reason' => null, 'devices' => null, - 'min_devices' => null + 'min_devices' => null, + 'cluster_id' => null ]; /** @@ -90,7 +92,8 @@ class HREResult implements ModelInterface, ArrayAccess, \JsonSerializable 'level' => true, 'reason' => false, 'devices' => false, - 'min_devices' => false + 'min_devices' => false, + 'cluster_id' => true ]; /** @@ -183,7 +186,8 @@ public function isNullableSetToNull(string $property): bool 'level' => 'level', 'reason' => 'reason', 'devices' => 'devices', - 'min_devices' => 'min_devices' + 'min_devices' => 'min_devices', + 'cluster_id' => 'cluster_id' ]; /** @@ -196,7 +200,8 @@ public function isNullableSetToNull(string $property): bool 'level' => 'setLevel', 'reason' => 'setReason', 'devices' => 'setDevices', - 'min_devices' => 'setMinDevices' + 'min_devices' => 'setMinDevices', + 'cluster_id' => 'setClusterId' ]; /** @@ -209,7 +214,8 @@ public function isNullableSetToNull(string $property): bool 'level' => 'getLevel', 'reason' => 'getReason', 'devices' => 'getDevices', - 'min_devices' => 'getMinDevices' + 'min_devices' => 'getMinDevices', + 'cluster_id' => 'getClusterId' ]; /** @@ -274,6 +280,7 @@ public function __construct(?array $data = null) $this->setIfExists('reason', $data ?? [], null); $this->setIfExists('devices', $data ?? [], null); $this->setIfExists('min_devices', $data ?? [], null); + $this->setIfExists('cluster_id', $data ?? [], null); } /** @@ -486,6 +493,40 @@ public function setMinDevices($min_devices) return $this; } + + /** + * Gets cluster_id + * + * @return string|null + */ + public function getClusterId() + { + return $this->container['cluster_id']; + } + + /** + * Sets cluster_id + * + * @param string|null $cluster_id Authoritative cluster ID for this HRE result. Null when no cluster was published for the result, including old stored verdicts; never a device ID. Reused on retries. + * + * @return self + */ + public function setClusterId($cluster_id) + { + if (is_null($cluster_id)) { + array_push($this->openAPINullablesSetToNull, 'cluster_id'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('cluster_id', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['cluster_id'] = $cluster_id; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/lib/Model/HistoryRow.php b/generated/lib/Model/HistoryRow.php index 6938273..b784123 100644 --- a/generated/lib/Model/HistoryRow.php +++ b/generated/lib/Model/HistoryRow.php @@ -59,6 +59,7 @@ class HistoryRow implements ModelInterface, ArrayAccess, \JsonSerializable * @var string[] */ protected static $openAPITypes = [ + 'client_identity' => '\ShieldLabs\Generated\Model\ClientIdentity', 'request_id' => 'string', 'session_id' => 'string', 'cookie_id' => 'string', @@ -122,6 +123,7 @@ class HistoryRow implements ModelInterface, ArrayAccess, \JsonSerializable * @psalm-var array */ protected static $openAPIFormats = [ + 'client_identity' => null, 'request_id' => 'uuid', 'session_id' => 'uuid', 'cookie_id' => 'uuid', @@ -183,6 +185,7 @@ class HistoryRow implements ModelInterface, ArrayAccess, \JsonSerializable * @var boolean[] */ protected static array $openAPINullables = [ + 'client_identity' => false, 'request_id' => false, 'session_id' => false, 'cookie_id' => false, @@ -324,6 +327,7 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $attributeMap = [ + 'client_identity' => 'client_identity', 'request_id' => 'request_id', 'session_id' => 'session_id', 'cookie_id' => 'cookie_id', @@ -385,6 +389,7 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $setters = [ + 'client_identity' => 'setClientIdentity', 'request_id' => 'setRequestId', 'session_id' => 'setSessionId', 'cookie_id' => 'setCookieId', @@ -446,6 +451,7 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $getters = [ + 'client_identity' => 'getClientIdentity', 'request_id' => 'getRequestId', 'session_id' => 'getSessionId', 'cookie_id' => 'getCookieId', @@ -558,6 +564,7 @@ public function getModelName() */ public function __construct(?array $data = null) { + $this->setIfExists('client_identity', $data ?? [], null); $this->setIfExists('request_id', $data ?? [], null); $this->setIfExists('session_id', $data ?? [], null); $this->setIfExists('cookie_id', $data ?? [], null); @@ -783,6 +790,33 @@ public function valid() } + /** + * Gets client_identity + * + * @return \ShieldLabs\Generated\Model\ClientIdentity|null + */ + public function getClientIdentity() + { + return $this->container['client_identity']; + } + + /** + * Sets client_identity + * + * @param \ShieldLabs\Generated\Model\ClientIdentity|null $client_identity client_identity + * + * @return self + */ + public function setClientIdentity($client_identity) + { + if (is_null($client_identity)) { + throw new \InvalidArgumentException('non-nullable client_identity cannot be null'); + } + $this->container['client_identity'] = $client_identity; + + return $this; + } + /** * Gets request_id * diff --git a/generated/lib/Model/IdentificationScoredData.php b/generated/lib/Model/IdentificationScoredData.php index 819567e..d5bc38f 100644 --- a/generated/lib/Model/IdentificationScoredData.php +++ b/generated/lib/Model/IdentificationScoredData.php @@ -36,7 +36,7 @@ * IdentificationScoredData Class Doc Comment * * @category Class - * @description Final identification. risk_score is this scan only; no all-time entity risk. New extension fields are required by version 2026-10-06; legacy bodies remain accepted. + * @description Final identification. risk_score is this scan only. Version 2026-10-07 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted. * @package ShieldLabs\Generated * @author OpenAPI Generator team * @link https://openapi-generator.tech @@ -59,6 +59,7 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri * @var string[] */ protected static $openAPITypes = [ + 'client_identity' => '\ShieldLabs\Generated\Model\ClientIdentity', 'request_id' => 'string', 'visitor_id' => 'string', 'device_id' => 'string', @@ -81,7 +82,9 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri 'scoring_version' => 'string', 'risk_events' => '\ShieldLabs\Generated\Model\RiskEvent[]', 'hre' => '\ShieldLabs\Generated\Model\HRE', - 'fingerprint' => '\ShieldLabs\Generated\Model\Fingerprint' + 'search_bot_owner' => 'string', + 'ai_bot_owner' => 'string', + 'ai_browser_owner' => 'string' ]; /** @@ -92,6 +95,7 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri * @psalm-var array */ protected static $openAPIFormats = [ + 'client_identity' => null, 'request_id' => 'uuid', 'visitor_id' => 'uuid', 'device_id' => 'uuid', @@ -114,7 +118,9 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri 'scoring_version' => null, 'risk_events' => null, 'hre' => null, - 'fingerprint' => null + 'search_bot_owner' => null, + 'ai_bot_owner' => null, + 'ai_browser_owner' => null ]; /** @@ -123,6 +129,7 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri * @var boolean[] */ protected static array $openAPINullables = [ + 'client_identity' => false, 'request_id' => false, 'visitor_id' => false, 'device_id' => false, @@ -145,7 +152,9 @@ class IdentificationScoredData implements ModelInterface, ArrayAccess, \JsonSeri 'scoring_version' => false, 'risk_events' => false, 'hre' => false, - 'fingerprint' => false + 'search_bot_owner' => false, + 'ai_bot_owner' => false, + 'ai_browser_owner' => false ]; /** @@ -234,6 +243,7 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $attributeMap = [ + 'client_identity' => 'client_identity', 'request_id' => 'request_id', 'visitor_id' => 'visitor_id', 'device_id' => 'device_id', @@ -256,7 +266,9 @@ public function isNullableSetToNull(string $property): bool 'scoring_version' => 'scoring_version', 'risk_events' => 'risk_events', 'hre' => 'hre', - 'fingerprint' => 'fingerprint' + 'search_bot_owner' => 'search_bot_owner', + 'ai_bot_owner' => 'ai_bot_owner', + 'ai_browser_owner' => 'ai_browser_owner' ]; /** @@ -265,6 +277,7 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $setters = [ + 'client_identity' => 'setClientIdentity', 'request_id' => 'setRequestId', 'visitor_id' => 'setVisitorId', 'device_id' => 'setDeviceId', @@ -287,7 +300,9 @@ public function isNullableSetToNull(string $property): bool 'scoring_version' => 'setScoringVersion', 'risk_events' => 'setRiskEvents', 'hre' => 'setHre', - 'fingerprint' => 'setFingerprint' + 'search_bot_owner' => 'setSearchBotOwner', + 'ai_bot_owner' => 'setAiBotOwner', + 'ai_browser_owner' => 'setAiBrowserOwner' ]; /** @@ -296,6 +311,7 @@ public function isNullableSetToNull(string $property): bool * @var string[] */ protected static $getters = [ + 'client_identity' => 'getClientIdentity', 'request_id' => 'getRequestId', 'visitor_id' => 'getVisitorId', 'device_id' => 'getDeviceId', @@ -318,7 +334,9 @@ public function isNullableSetToNull(string $property): bool 'scoring_version' => 'getScoringVersion', 'risk_events' => 'getRiskEvents', 'hre' => 'getHre', - 'fingerprint' => 'getFingerprint' + 'search_bot_owner' => 'getSearchBotOwner', + 'ai_bot_owner' => 'getAiBotOwner', + 'ai_browser_owner' => 'getAiBrowserOwner' ]; /** @@ -378,6 +396,7 @@ public function getModelName() */ public function __construct(?array $data = null) { + $this->setIfExists('client_identity', $data ?? [], null); $this->setIfExists('request_id', $data ?? [], null); $this->setIfExists('visitor_id', $data ?? [], null); $this->setIfExists('device_id', $data ?? [], null); @@ -400,7 +419,9 @@ public function __construct(?array $data = null) $this->setIfExists('scoring_version', $data ?? [], null); $this->setIfExists('risk_events', $data ?? [], null); $this->setIfExists('hre', $data ?? [], null); - $this->setIfExists('fingerprint', $data ?? [], null); + $this->setIfExists('search_bot_owner', $data ?? [], null); + $this->setIfExists('ai_bot_owner', $data ?? [], null); + $this->setIfExists('ai_browser_owner', $data ?? [], null); } /** @@ -511,6 +532,33 @@ public function valid() } + /** + * Gets client_identity + * + * @return \ShieldLabs\Generated\Model\ClientIdentity|null + */ + public function getClientIdentity() + { + return $this->container['client_identity']; + } + + /** + * Sets client_identity + * + * @param \ShieldLabs\Generated\Model\ClientIdentity|null $client_identity client_identity + * + * @return self + */ + public function setClientIdentity($client_identity) + { + if (is_null($client_identity)) { + throw new \InvalidArgumentException('non-nullable client_identity cannot be null'); + } + $this->container['client_identity'] = $client_identity; + + return $this; + } + /** * Gets request_id * @@ -1077,6 +1125,7 @@ public function setScoringVersion($scoring_version) * Gets risk_events * * @return \ShieldLabs\Generated\Model\RiskEvent[]|null + * @deprecated */ public function getRiskEvents() { @@ -1086,9 +1135,10 @@ public function getRiskEvents() /** * Sets risk_events * - * @param \ShieldLabs\Generated\Model\RiskEvent[]|null $risk_events risk_events + * @param \ShieldLabs\Generated\Model\RiskEvent[]|null $risk_events Legacy 2026-10-06 only; absent from current scored events. * * @return self + * @deprecated */ public function setRiskEvents($risk_events) { @@ -1128,28 +1178,82 @@ public function setHre($hre) } /** - * Gets fingerprint + * Gets search_bot_owner + * + * @return string|null + */ + public function getSearchBotOwner() + { + return $this->container['search_bot_owner']; + } + + /** + * Sets search_bot_owner + * + * @param string|null $search_bot_owner Owner label from accepted search-bot detection; omitted when unknown or inactive. + * + * @return self + */ + public function setSearchBotOwner($search_bot_owner) + { + if (is_null($search_bot_owner)) { + throw new \InvalidArgumentException('non-nullable search_bot_owner cannot be null'); + } + $this->container['search_bot_owner'] = $search_bot_owner; + + return $this; + } + + /** + * Gets ai_bot_owner * - * @return \ShieldLabs\Generated\Model\Fingerprint|null + * @return string|null + */ + public function getAiBotOwner() + { + return $this->container['ai_bot_owner']; + } + + /** + * Sets ai_bot_owner + * + * @param string|null $ai_bot_owner Provider company label from accepted AI-bot detection; omitted when unknown or inactive. + * + * @return self + */ + public function setAiBotOwner($ai_bot_owner) + { + if (is_null($ai_bot_owner)) { + throw new \InvalidArgumentException('non-nullable ai_bot_owner cannot be null'); + } + $this->container['ai_bot_owner'] = $ai_bot_owner; + + return $this; + } + + /** + * Gets ai_browser_owner + * + * @return string|null */ - public function getFingerprint() + public function getAiBrowserOwner() { - return $this->container['fingerprint']; + return $this->container['ai_browser_owner']; } /** - * Sets fingerprint + * Sets ai_browser_owner * - * @param \ShieldLabs\Generated\Model\Fingerprint|null $fingerprint fingerprint + * @param string|null $ai_browser_owner Owner label from accepted AI-browser detection; omitted when unknown or inactive. * * @return self */ - public function setFingerprint($fingerprint) + public function setAiBrowserOwner($ai_browser_owner) { - if (is_null($fingerprint)) { - throw new \InvalidArgumentException('non-nullable fingerprint cannot be null'); + if (is_null($ai_browser_owner)) { + throw new \InvalidArgumentException('non-nullable ai_browser_owner cannot be null'); } - $this->container['fingerprint'] = $fingerprint; + $this->container['ai_browser_owner'] = $ai_browser_owner; return $this; } diff --git a/generated/lib/Model/IdentificationScoredEvent.php b/generated/lib/Model/IdentificationScoredEvent.php index 49cc5aa..885cd3e 100644 --- a/generated/lib/Model/IdentificationScoredEvent.php +++ b/generated/lib/Model/IdentificationScoredEvent.php @@ -394,7 +394,7 @@ public function getSchemaVersion() /** * Sets schema_version * - * @param string $schema_version Webhook contract version. Current release 2026-10-06; parsers also accept legacy 2026-06-01. + * @param string $schema_version Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. * * @return self */ diff --git a/generated/lib/Model/MultiaccountChangedData.php b/generated/lib/Model/MultiaccountChangedData.php new file mode 100644 index 0000000..021ca0e --- /dev/null +++ b/generated/lib/Model/MultiaccountChangedData.php @@ -0,0 +1,1129 @@ + + */ +class MultiaccountChangedData implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'MultiaccountChangedData'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'kind' => 'mixed', + 'domain' => 'string', + 'action' => 'string', + 'status' => 'mixed', + 'detected' => 'bool', + 'level' => 'string', + 'cluster_id' => 'string', + 'revision' => 'string', + 'previous_revision' => 'string', + 'user_hids' => 'string[]', + 'users_count' => 'int', + 'members_complete' => 'mixed', + 'evaluated_at' => '\DateTime', + 'rules_version' => 'string', + 'source' => '\ShieldLabs\Generated\Model\MultiaccountChangedDataSource', + 'evidence_summary' => '\ShieldLabs\Generated\Model\MultiaccountChangedDataEvidenceSummary' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'kind' => null, + 'domain' => null, + 'action' => null, + 'status' => null, + 'detected' => null, + 'level' => null, + 'cluster_id' => 'uuid', + 'revision' => null, + 'previous_revision' => null, + 'user_hids' => null, + 'users_count' => null, + 'members_complete' => null, + 'evaluated_at' => 'date-time', + 'rules_version' => null, + 'source' => null, + 'evidence_summary' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'kind' => true, + 'domain' => false, + 'action' => false, + 'status' => true, + 'detected' => false, + 'level' => true, + 'cluster_id' => false, + 'revision' => false, + 'previous_revision' => false, + 'user_hids' => false, + 'users_count' => false, + 'members_complete' => true, + 'evaluated_at' => false, + 'rules_version' => false, + 'source' => false, + 'evidence_summary' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'kind' => 'kind', + 'domain' => 'domain', + 'action' => 'action', + 'status' => 'status', + 'detected' => 'detected', + 'level' => 'level', + 'cluster_id' => 'cluster_id', + 'revision' => 'revision', + 'previous_revision' => 'previous_revision', + 'user_hids' => 'user_hids', + 'users_count' => 'users_count', + 'members_complete' => 'members_complete', + 'evaluated_at' => 'evaluated_at', + 'rules_version' => 'rules_version', + 'source' => 'source', + 'evidence_summary' => 'evidence_summary' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'kind' => 'setKind', + 'domain' => 'setDomain', + 'action' => 'setAction', + 'status' => 'setStatus', + 'detected' => 'setDetected', + 'level' => 'setLevel', + 'cluster_id' => 'setClusterId', + 'revision' => 'setRevision', + 'previous_revision' => 'setPreviousRevision', + 'user_hids' => 'setUserHids', + 'users_count' => 'setUsersCount', + 'members_complete' => 'setMembersComplete', + 'evaluated_at' => 'setEvaluatedAt', + 'rules_version' => 'setRulesVersion', + 'source' => 'setSource', + 'evidence_summary' => 'setEvidenceSummary' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'kind' => 'getKind', + 'domain' => 'getDomain', + 'action' => 'getAction', + 'status' => 'getStatus', + 'detected' => 'getDetected', + 'level' => 'getLevel', + 'cluster_id' => 'getClusterId', + 'revision' => 'getRevision', + 'previous_revision' => 'getPreviousRevision', + 'user_hids' => 'getUserHids', + 'users_count' => 'getUsersCount', + 'members_complete' => 'getMembersComplete', + 'evaluated_at' => 'getEvaluatedAt', + 'rules_version' => 'getRulesVersion', + 'source' => 'getSource', + 'evidence_summary' => 'getEvidenceSummary' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + public const ACTION_DETECTED = 'detected'; + public const ACTION_UPDATED = 'updated'; + public const ACTION_RESOLVED = 'resolved'; + public const LEVEL_MEDIUM = 'medium'; + public const LEVEL_HIGH = 'high'; + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getActionAllowableValues() + { + return [ + self::ACTION_DETECTED, + self::ACTION_UPDATED, + self::ACTION_RESOLVED, + ]; + } + + /** + * Gets allowable values of the enum + * + * @return string[] + */ + public function getLevelAllowableValues() + { + return [ + self::LEVEL_MEDIUM, + self::LEVEL_HIGH, + ]; + } + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('kind', $data ?? [], null); + $this->setIfExists('domain', $data ?? [], null); + $this->setIfExists('action', $data ?? [], null); + $this->setIfExists('status', $data ?? [], null); + $this->setIfExists('detected', $data ?? [], null); + $this->setIfExists('level', $data ?? [], null); + $this->setIfExists('cluster_id', $data ?? [], null); + $this->setIfExists('revision', $data ?? [], null); + $this->setIfExists('previous_revision', $data ?? [], null); + $this->setIfExists('user_hids', $data ?? [], null); + $this->setIfExists('users_count', $data ?? [], null); + $this->setIfExists('members_complete', $data ?? [], null); + $this->setIfExists('evaluated_at', $data ?? [], null); + $this->setIfExists('rules_version', $data ?? [], null); + $this->setIfExists('source', $data ?? [], null); + $this->setIfExists('evidence_summary', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['kind'] === null && !$this->isNullableSetToNull('kind')) { + $invalidProperties[] = "'kind' is required"; + } + if ($this->container['domain'] === null) { + $invalidProperties[] = "'domain' can't be null"; + } + if ((mb_strlen($this->container['domain']) < 1)) { + $invalidProperties[] = "invalid value for 'domain', the character length must be bigger than or equal to 1."; + } + + if ($this->container['action'] === null) { + $invalidProperties[] = "'action' can't be null"; + } + $allowedValues = $this->getActionAllowableValues(); + if (!is_null($this->container['action']) && !in_array($this->container['action'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'action', must be one of '%s'", + $this->container['action'], + implode("', '", $allowedValues) + ); + } + + if ($this->container['status'] === null && !$this->isNullableSetToNull('status')) { + $invalidProperties[] = "'status' is required"; + } + if ($this->container['detected'] === null) { + $invalidProperties[] = "'detected' can't be null"; + } + if ($this->container['level'] === null && !$this->isNullableSetToNull('level')) { + $invalidProperties[] = "'level' is required"; + } + $allowedValues = $this->getLevelAllowableValues(); + if (!is_null($this->container['level']) && !in_array($this->container['level'], $allowedValues, true)) { + $invalidProperties[] = sprintf( + "invalid value '%s' for 'level', must be one of '%s'", + $this->container['level'], + implode("', '", $allowedValues) + ); + } + + if ($this->container['cluster_id'] === null) { + $invalidProperties[] = "'cluster_id' can't be null"; + } + if ($this->container['revision'] === null) { + $invalidProperties[] = "'revision' can't be null"; + } + if (!preg_match("/^[1-9][0-9]{0,19}$/", $this->container['revision'])) { + $invalidProperties[] = "invalid value for 'revision', must be conform to the pattern /^[1-9][0-9]{0,19}$/."; + } + + if ($this->container['previous_revision'] === null) { + $invalidProperties[] = "'previous_revision' can't be null"; + } + if (!preg_match("/^(0|[1-9][0-9]{0,19})$/", $this->container['previous_revision'])) { + $invalidProperties[] = "invalid value for 'previous_revision', must be conform to the pattern /^(0|[1-9][0-9]{0,19})$/."; + } + + if ($this->container['user_hids'] === null) { + $invalidProperties[] = "'user_hids' can't be null"; + } + if ((count($this->container['user_hids']) < 3)) { + $invalidProperties[] = "invalid value for 'user_hids', number of items must be greater than or equal to 3."; + } + + if ($this->container['users_count'] === null) { + $invalidProperties[] = "'users_count' can't be null"; + } + if (($this->container['users_count'] < 3)) { + $invalidProperties[] = "invalid value for 'users_count', must be bigger than or equal to 3."; + } + + if ($this->container['members_complete'] === null && !$this->isNullableSetToNull('members_complete')) { + $invalidProperties[] = "'members_complete' is required"; + } + if ($this->container['evaluated_at'] === null) { + $invalidProperties[] = "'evaluated_at' can't be null"; + } + if (!preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/", $this->container['evaluated_at'])) { + $invalidProperties[] = "invalid value for 'evaluated_at', must be conform to the pattern /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/."; + } + + if ($this->container['rules_version'] === null) { + $invalidProperties[] = "'rules_version' can't be null"; + } + if ((mb_strlen($this->container['rules_version']) < 1)) { + $invalidProperties[] = "invalid value for 'rules_version', the character length must be bigger than or equal to 1."; + } + + if ($this->container['source'] === null) { + $invalidProperties[] = "'source' can't be null"; + } + if ($this->container['evidence_summary'] === null) { + $invalidProperties[] = "'evidence_summary' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets kind + * + * @return mixed|null + */ + public function getKind() + { + return $this->container['kind']; + } + + /** + * Sets kind + * + * @param mixed|null $kind kind + * + * @return self + */ + public function setKind($kind) + { + if (is_null($kind)) { + array_push($this->openAPINullablesSetToNull, 'kind'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('kind', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['kind'] = $kind; + + return $this; + } + + /** + * Gets domain + * + * @return string + */ + public function getDomain() + { + return $this->container['domain']; + } + + /** + * Sets domain + * + * @param string $domain domain + * + * @return self + */ + public function setDomain($domain) + { + if (is_null($domain)) { + throw new \InvalidArgumentException('non-nullable domain cannot be null'); + } + + if ((mb_strlen($domain) < 1)) { + throw new \InvalidArgumentException('invalid length for $domain when calling MultiaccountChangedData., must be bigger than or equal to 1.'); + } + + $this->container['domain'] = $domain; + + return $this; + } + + /** + * Gets action + * + * @return string + */ + public function getAction() + { + return $this->container['action']; + } + + /** + * Sets action + * + * @param string $action action + * + * @return self + */ + public function setAction($action) + { + if (is_null($action)) { + throw new \InvalidArgumentException('non-nullable action cannot be null'); + } + $allowedValues = $this->getActionAllowableValues(); + if (!in_array($action, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'action', must be one of '%s'", + $action, + implode("', '", $allowedValues) + ) + ); + } + $this->container['action'] = $action; + + return $this; + } + + /** + * Gets status + * + * @return mixed|null + */ + public function getStatus() + { + return $this->container['status']; + } + + /** + * Sets status + * + * @param mixed|null $status The result comes from a complete, verified detector source cut. Pending or unavailable work does not produce this event. + * + * @return self + */ + public function setStatus($status) + { + if (is_null($status)) { + array_push($this->openAPINullablesSetToNull, 'status'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('status', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['status'] = $status; + + return $this; + } + + /** + * Gets detected + * + * @return bool + */ + public function getDetected() + { + return $this->container['detected']; + } + + /** + * Sets detected + * + * @param bool $detected detected + * + * @return self + */ + public function setDetected($detected) + { + if (is_null($detected)) { + throw new \InvalidArgumentException('non-nullable detected cannot be null'); + } + $this->container['detected'] = $detected; + + return $this; + } + + /** + * Gets level + * + * @return string|null + */ + public function getLevel() + { + return $this->container['level']; + } + + /** + * Sets level + * + * @param string|null $level level + * + * @return self + */ + public function setLevel($level) + { + if (is_null($level)) { + array_push($this->openAPINullablesSetToNull, 'level'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('level', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $allowedValues = $this->getLevelAllowableValues(); + if (!is_null($level) && !in_array($level, $allowedValues, true)) { + throw new \InvalidArgumentException( + sprintf( + "Invalid value '%s' for 'level', must be one of '%s'", + $level, + implode("', '", $allowedValues) + ) + ); + } + $this->container['level'] = $level; + + return $this; + } + + /** + * Gets cluster_id + * + * @return string + */ + public function getClusterId() + { + return $this->container['cluster_id']; + } + + /** + * Sets cluster_id + * + * @param string $cluster_id ID of the exact sorted user set within this site. A membership change resolves the old group and detects a new group. + * + * @return self + */ + public function setClusterId($cluster_id) + { + if (is_null($cluster_id)) { + throw new \InvalidArgumentException('non-nullable cluster_id cannot be null'); + } + $this->container['cluster_id'] = $cluster_id; + + return $this; + } + + /** + * Gets revision + * + * @return string + */ + public function getRevision() + { + return $this->container['revision']; + } + + /** + * Sets revision + * + * @param string $revision Publication version of this transition, as a decimal string to preserve UInt64 precision. + * + * @return self + */ + public function setRevision($revision) + { + if (is_null($revision)) { + throw new \InvalidArgumentException('non-nullable revision cannot be null'); + } + + if ((!preg_match("/^[1-9][0-9]{0,19}$/", ObjectSerializer::toString($revision)))) { + throw new \InvalidArgumentException("invalid value for \$revision when calling MultiaccountChangedData., must conform to the pattern /^[1-9][0-9]{0,19}$/."); + } + + $this->container['revision'] = $revision; + + return $this; + } + + /** + * Gets previous_revision + * + * @return string + */ + public function getPreviousRevision() + { + return $this->container['previous_revision']; + } + + /** + * Sets previous_revision + * + * @param string $previous_revision Previous transition version while the group was active; null for a newly detected or reappearing group. + * + * @return self + */ + public function setPreviousRevision($previous_revision) + { + if (is_null($previous_revision)) { + throw new \InvalidArgumentException('non-nullable previous_revision cannot be null'); + } + + if ((!preg_match("/^(0|[1-9][0-9]{0,19})$/", ObjectSerializer::toString($previous_revision)))) { + throw new \InvalidArgumentException("invalid value for \$previous_revision when calling MultiaccountChangedData., must conform to the pattern /^(0|[1-9][0-9]{0,19})$/."); + } + + $this->container['previous_revision'] = $previous_revision; + + return $this; + } + + /** + * Gets user_hids + * + * @return string[] + */ + public function getUserHids() + { + return $this->container['user_hids']; + } + + /** + * Sets user_hids + * + * @param string[] $user_hids Complete sorted group membership. Users from another site are never included. This list is not truncated. + * + * @return self + */ + public function setUserHids($user_hids) + { + if (is_null($user_hids)) { + throw new \InvalidArgumentException('non-nullable user_hids cannot be null'); + } + + + if ((count($user_hids) < 3)) { + throw new \InvalidArgumentException('invalid length for $user_hids when calling MultiaccountChangedData., number of items must be greater than or equal to 3.'); + } + $this->container['user_hids'] = $user_hids; + + return $this; + } + + /** + * Gets users_count + * + * @return int + */ + public function getUsersCount() + { + return $this->container['users_count']; + } + + /** + * Sets users_count + * + * @param int $users_count Equal to the length of user_hids. + * + * @return self + */ + public function setUsersCount($users_count) + { + if (is_null($users_count)) { + throw new \InvalidArgumentException('non-nullable users_count cannot be null'); + } + + if (($users_count < 3)) { + throw new \InvalidArgumentException('invalid value for $users_count when calling MultiaccountChangedData., must be bigger than or equal to 3.'); + } + + $this->container['users_count'] = $users_count; + + return $this; + } + + /** + * Gets members_complete + * + * @return mixed|null + */ + public function getMembersComplete() + { + return $this->container['members_complete']; + } + + /** + * Sets members_complete + * + * @param mixed|null $members_complete members_complete + * + * @return self + */ + public function setMembersComplete($members_complete) + { + if (is_null($members_complete)) { + array_push($this->openAPINullablesSetToNull, 'members_complete'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('members_complete', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['members_complete'] = $members_complete; + + return $this; + } + + /** + * Gets evaluated_at + * + * @return \DateTime + */ + public function getEvaluatedAt() + { + return $this->container['evaluated_at']; + } + + /** + * Sets evaluated_at + * + * @param \DateTime $evaluated_at RFC 3339 timestamp in UTC with up to 9 fractional digits (trailing zeros trimmed), for example `2026-09-30T12:34:57.482913041Z`. Parse it with a parser that accepts nanoseconds. + * + * @return self + */ + public function setEvaluatedAt($evaluated_at) + { + if (is_null($evaluated_at)) { + throw new \InvalidArgumentException('non-nullable evaluated_at cannot be null'); + } + + if ((!preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/", ObjectSerializer::toString($evaluated_at)))) { + throw new \InvalidArgumentException("invalid value for \$evaluated_at when calling MultiaccountChangedData., must conform to the pattern /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/."); + } + + $this->container['evaluated_at'] = $evaluated_at; + + return $this; + } + + /** + * Gets rules_version + * + * @return string + */ + public function getRulesVersion() + { + return $this->container['rules_version']; + } + + /** + * Sets rules_version + * + * @param string $rules_version rules_version + * + * @return self + */ + public function setRulesVersion($rules_version) + { + if (is_null($rules_version)) { + throw new \InvalidArgumentException('non-nullable rules_version cannot be null'); + } + + if ((mb_strlen($rules_version) < 1)) { + throw new \InvalidArgumentException('invalid length for $rules_version when calling MultiaccountChangedData., must be bigger than or equal to 1.'); + } + + $this->container['rules_version'] = $rules_version; + + return $this; + } + + /** + * Gets source + * + * @return \ShieldLabs\Generated\Model\MultiaccountChangedDataSource + */ + public function getSource() + { + return $this->container['source']; + } + + /** + * Sets source + * + * @param \ShieldLabs\Generated\Model\MultiaccountChangedDataSource $source source + * + * @return self + */ + public function setSource($source) + { + if (is_null($source)) { + throw new \InvalidArgumentException('non-nullable source cannot be null'); + } + $this->container['source'] = $source; + + return $this; + } + + /** + * Gets evidence_summary + * + * @return \ShieldLabs\Generated\Model\MultiaccountChangedDataEvidenceSummary + */ + public function getEvidenceSummary() + { + return $this->container['evidence_summary']; + } + + /** + * Sets evidence_summary + * + * @param \ShieldLabs\Generated\Model\MultiaccountChangedDataEvidenceSummary $evidence_summary evidence_summary + * + * @return self + */ + public function setEvidenceSummary($evidence_summary) + { + if (is_null($evidence_summary)) { + throw new \InvalidArgumentException('non-nullable evidence_summary cannot be null'); + } + $this->container['evidence_summary'] = $evidence_summary; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/MultiaccountChangedDataEvidenceSummary.php b/generated/lib/Model/MultiaccountChangedDataEvidenceSummary.php new file mode 100644 index 0000000..424462a --- /dev/null +++ b/generated/lib/Model/MultiaccountChangedDataEvidenceSummary.php @@ -0,0 +1,491 @@ + + */ +class MultiaccountChangedDataEvidenceSummary implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'MultiaccountChangedData_evidence_summary'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'basis_types' => 'string[]', + 'rules' => 'string[]', + 'detail_truncated' => 'bool' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'basis_types' => null, + 'rules' => null, + 'detail_truncated' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'basis_types' => false, + 'rules' => false, + 'detail_truncated' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'basis_types' => 'basis_types', + 'rules' => 'rules', + 'detail_truncated' => 'detail_truncated' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'basis_types' => 'setBasisTypes', + 'rules' => 'setRules', + 'detail_truncated' => 'setDetailTruncated' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'basis_types' => 'getBasisTypes', + 'rules' => 'getRules', + 'detail_truncated' => 'getDetailTruncated' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('basis_types', $data ?? [], null); + $this->setIfExists('rules', $data ?? [], null); + $this->setIfExists('detail_truncated', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['basis_types'] === null) { + $invalidProperties[] = "'basis_types' can't be null"; + } + if ($this->container['rules'] === null) { + $invalidProperties[] = "'rules' can't be null"; + } + if ($this->container['detail_truncated'] === null) { + $invalidProperties[] = "'detail_truncated' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets basis_types + * + * @return string[] + */ + public function getBasisTypes() + { + return $this->container['basis_types']; + } + + /** + * Sets basis_types + * + * @param string[] $basis_types basis_types + * + * @return self + */ + public function setBasisTypes($basis_types) + { + if (is_null($basis_types)) { + throw new \InvalidArgumentException('non-nullable basis_types cannot be null'); + } + + + $this->container['basis_types'] = $basis_types; + + return $this; + } + + /** + * Gets rules + * + * @return string[] + */ + public function getRules() + { + return $this->container['rules']; + } + + /** + * Sets rules + * + * @param string[] $rules rules + * + * @return self + */ + public function setRules($rules) + { + if (is_null($rules)) { + throw new \InvalidArgumentException('non-nullable rules cannot be null'); + } + + + $this->container['rules'] = $rules; + + return $this; + } + + /** + * Gets detail_truncated + * + * @return bool + */ + public function getDetailTruncated() + { + return $this->container['detail_truncated']; + } + + /** + * Sets detail_truncated + * + * @param bool $detail_truncated Whether the detector's underlying detailed evidence was truncated. Group membership remains complete. + * + * @return self + */ + public function setDetailTruncated($detail_truncated) + { + if (is_null($detail_truncated)) { + throw new \InvalidArgumentException('non-nullable detail_truncated cannot be null'); + } + $this->container['detail_truncated'] = $detail_truncated; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/MultiaccountChangedDataSource.php b/generated/lib/Model/MultiaccountChangedDataSource.php new file mode 100644 index 0000000..83701bd --- /dev/null +++ b/generated/lib/Model/MultiaccountChangedDataSource.php @@ -0,0 +1,512 @@ + + */ +class MultiaccountChangedDataSource implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'MultiaccountChangedData_source'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'publication_epoch' => 'mixed', + 'publication_version' => 'string', + 'coverage_id' => 'string' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'publication_epoch' => null, + 'publication_version' => null, + 'coverage_id' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'publication_epoch' => true, + 'publication_version' => false, + 'coverage_id' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'publication_epoch' => 'publication_epoch', + 'publication_version' => 'publication_version', + 'coverage_id' => 'coverage_id' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'publication_epoch' => 'setPublicationEpoch', + 'publication_version' => 'setPublicationVersion', + 'coverage_id' => 'setCoverageId' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'publication_epoch' => 'getPublicationEpoch', + 'publication_version' => 'getPublicationVersion', + 'coverage_id' => 'getCoverageId' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('publication_epoch', $data ?? [], null); + $this->setIfExists('publication_version', $data ?? [], null); + $this->setIfExists('coverage_id', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['publication_epoch'] === null && !$this->isNullableSetToNull('publication_epoch')) { + $invalidProperties[] = "'publication_epoch' is required"; + } + if ($this->container['publication_version'] === null) { + $invalidProperties[] = "'publication_version' can't be null"; + } + if (!preg_match("/^[1-9][0-9]{0,19}$/", $this->container['publication_version'])) { + $invalidProperties[] = "invalid value for 'publication_version', must be conform to the pattern /^[1-9][0-9]{0,19}$/."; + } + + if ($this->container['coverage_id'] === null) { + $invalidProperties[] = "'coverage_id' can't be null"; + } + if (!preg_match("/^[a-f0-9]{64}$/", $this->container['coverage_id'])) { + $invalidProperties[] = "invalid value for 'coverage_id', must be conform to the pattern /^[a-f0-9]{64}$/."; + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets publication_epoch + * + * @return mixed|null + */ + public function getPublicationEpoch() + { + return $this->container['publication_epoch']; + } + + /** + * Sets publication_epoch + * + * @param mixed|null $publication_epoch publication_epoch + * + * @return self + */ + public function setPublicationEpoch($publication_epoch) + { + if (is_null($publication_epoch)) { + array_push($this->openAPINullablesSetToNull, 'publication_epoch'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('publication_epoch', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['publication_epoch'] = $publication_epoch; + + return $this; + } + + /** + * Gets publication_version + * + * @return string + */ + public function getPublicationVersion() + { + return $this->container['publication_version']; + } + + /** + * Sets publication_version + * + * @param string $publication_version The exact immutable publication containing this result and event. + * + * @return self + */ + public function setPublicationVersion($publication_version) + { + if (is_null($publication_version)) { + throw new \InvalidArgumentException('non-nullable publication_version cannot be null'); + } + + if ((!preg_match("/^[1-9][0-9]{0,19}$/", ObjectSerializer::toString($publication_version)))) { + throw new \InvalidArgumentException("invalid value for \$publication_version when calling MultiaccountChangedDataSource., must conform to the pattern /^[1-9][0-9]{0,19}$/."); + } + + $this->container['publication_version'] = $publication_version; + + return $this; + } + + /** + * Gets coverage_id + * + * @return string + */ + public function getCoverageId() + { + return $this->container['coverage_id']; + } + + /** + * Sets coverage_id + * + * @param string $coverage_id Immutable binding to the trusted source completeness proof. + * + * @return self + */ + public function setCoverageId($coverage_id) + { + if (is_null($coverage_id)) { + throw new \InvalidArgumentException('non-nullable coverage_id cannot be null'); + } + + if ((!preg_match("/^[a-f0-9]{64}$/", ObjectSerializer::toString($coverage_id)))) { + throw new \InvalidArgumentException("invalid value for \$coverage_id when calling MultiaccountChangedDataSource., must conform to the pattern /^[a-f0-9]{64}$/."); + } + + $this->container['coverage_id'] = $coverage_id; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/MultiaccountChangedEvent.php b/generated/lib/Model/MultiaccountChangedEvent.php new file mode 100644 index 0000000..112f43e --- /dev/null +++ b/generated/lib/Model/MultiaccountChangedEvent.php @@ -0,0 +1,640 @@ + + */ +class MultiaccountChangedEvent implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'MultiaccountChangedEvent'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'event_id' => 'string', + 'event_type' => 'mixed', + 'schema_version' => 'mixed', + 'site_id' => 'int', + 'created_at' => '\DateTime', + 'data' => '\ShieldLabs\Generated\Model\MultiaccountChangedData' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'event_id' => null, + 'event_type' => null, + 'schema_version' => null, + 'site_id' => null, + 'created_at' => 'date-time', + 'data' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'event_id' => false, + 'event_type' => true, + 'schema_version' => true, + 'site_id' => false, + 'created_at' => false, + 'data' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'event_id' => 'event_id', + 'event_type' => 'event_type', + 'schema_version' => 'schema_version', + 'site_id' => 'site_id', + 'created_at' => 'created_at', + 'data' => 'data' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'event_id' => 'setEventId', + 'event_type' => 'setEventType', + 'schema_version' => 'setSchemaVersion', + 'site_id' => 'setSiteId', + 'created_at' => 'setCreatedAt', + 'data' => 'setData' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'event_id' => 'getEventId', + 'event_type' => 'getEventType', + 'schema_version' => 'getSchemaVersion', + 'site_id' => 'getSiteId', + 'created_at' => 'getCreatedAt', + 'data' => 'getData' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[]|null $data Associated array of property values + * initializing the model + */ + public function __construct(?array $data = null) + { + $this->setIfExists('event_id', $data ?? [], null); + $this->setIfExists('event_type', $data ?? [], null); + $this->setIfExists('schema_version', $data ?? [], null); + $this->setIfExists('site_id', $data ?? [], null); + $this->setIfExists('created_at', $data ?? [], null); + $this->setIfExists('data', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['event_id'] === null) { + $invalidProperties[] = "'event_id' can't be null"; + } + if (!preg_match("/^[a-f0-9]{40}$/", $this->container['event_id'])) { + $invalidProperties[] = "invalid value for 'event_id', must be conform to the pattern /^[a-f0-9]{40}$/."; + } + + if ($this->container['event_type'] === null && !$this->isNullableSetToNull('event_type')) { + $invalidProperties[] = "'event_type' is required"; + } + if ($this->container['schema_version'] === null && !$this->isNullableSetToNull('schema_version')) { + $invalidProperties[] = "'schema_version' is required"; + } + if ($this->container['site_id'] === null) { + $invalidProperties[] = "'site_id' can't be null"; + } + if (($this->container['site_id'] < 1)) { + $invalidProperties[] = "invalid value for 'site_id', must be bigger than or equal to 1."; + } + + if ($this->container['created_at'] === null) { + $invalidProperties[] = "'created_at' can't be null"; + } + if (!preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/", $this->container['created_at'])) { + $invalidProperties[] = "invalid value for 'created_at', must be conform to the pattern /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/."; + } + + if ($this->container['data'] === null) { + $invalidProperties[] = "'data' can't be null"; + } + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets event_id + * + * @return string + */ + public function getEventId() + { + return $this->container['event_id']; + } + + /** + * Sets event_id + * + * @param string $event_id Stable logical event ID. Delivery retries preserve this ID and the signed body. + * + * @return self + */ + public function setEventId($event_id) + { + if (is_null($event_id)) { + throw new \InvalidArgumentException('non-nullable event_id cannot be null'); + } + + if ((!preg_match("/^[a-f0-9]{40}$/", ObjectSerializer::toString($event_id)))) { + throw new \InvalidArgumentException("invalid value for \$event_id when calling MultiaccountChangedEvent., must conform to the pattern /^[a-f0-9]{40}$/."); + } + + $this->container['event_id'] = $event_id; + + return $this; + } + + /** + * Gets event_type + * + * @return mixed|null + */ + public function getEventType() + { + return $this->container['event_type']; + } + + /** + * Sets event_type + * + * @param mixed|null $event_type event_type + * + * @return self + */ + public function setEventType($event_type) + { + if (is_null($event_type)) { + array_push($this->openAPINullablesSetToNull, 'event_type'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('event_type', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['event_type'] = $event_type; + + return $this; + } + + /** + * Gets schema_version + * + * @return mixed|null + */ + public function getSchemaVersion() + { + return $this->container['schema_version']; + } + + /** + * Sets schema_version + * + * @param mixed|null $schema_version schema_version + * + * @return self + */ + public function setSchemaVersion($schema_version) + { + if (is_null($schema_version)) { + array_push($this->openAPINullablesSetToNull, 'schema_version'); + } else { + $nullablesSetToNull = $this->getOpenAPINullablesSetToNull(); + $index = array_search('schema_version', $nullablesSetToNull); + if ($index !== FALSE) { + unset($nullablesSetToNull[$index]); + $this->setOpenAPINullablesSetToNull($nullablesSetToNull); + } + } + $this->container['schema_version'] = $schema_version; + + return $this; + } + + /** + * Gets site_id + * + * @return int + */ + public function getSiteId() + { + return $this->container['site_id']; + } + + /** + * Sets site_id + * + * @param int $site_id site_id + * + * @return self + */ + public function setSiteId($site_id) + { + if (is_null($site_id)) { + throw new \InvalidArgumentException('non-nullable site_id cannot be null'); + } + + if (($site_id < 1)) { + throw new \InvalidArgumentException('invalid value for $site_id when calling MultiaccountChangedEvent., must be bigger than or equal to 1.'); + } + + $this->container['site_id'] = $site_id; + + return $this; + } + + /** + * Gets created_at + * + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->container['created_at']; + } + + /** + * Sets created_at + * + * @param \DateTime $created_at RFC 3339 timestamp in UTC with up to 9 fractional digits (trailing zeros trimmed), for example `2026-09-30T12:34:57.482913041Z`. Parse it with a parser that accepts nanoseconds. + * + * @return self + */ + public function setCreatedAt($created_at) + { + if (is_null($created_at)) { + throw new \InvalidArgumentException('non-nullable created_at cannot be null'); + } + + if ((!preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/", ObjectSerializer::toString($created_at)))) { + throw new \InvalidArgumentException("invalid value for \$created_at when calling MultiaccountChangedEvent., must conform to the pattern /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?Z$/."); + } + + $this->container['created_at'] = $created_at; + + return $this; + } + + /** + * Gets data + * + * @return \ShieldLabs\Generated\Model\MultiaccountChangedData + */ + public function getData() + { + return $this->container['data']; + } + + /** + * Sets data + * + * @param \ShieldLabs\Generated\Model\MultiaccountChangedData $data data + * + * @return self + */ + public function setData($data) + { + if (is_null($data)) { + throw new \InvalidArgumentException('non-nullable data cannot be null'); + } + $this->container['data'] = $data; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer|string $offset Offset + * + * @return boolean + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer|string $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer|string $offset Offset + * + * @return void + */ + public function offsetUnset(mixed $offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/lib/Model/RiskEvent.php b/generated/lib/Model/RiskEvent.php index de36fda..0fc58d0 100644 --- a/generated/lib/Model/RiskEvent.php +++ b/generated/lib/Model/RiskEvent.php @@ -346,7 +346,7 @@ public function getCode() /** * Sets code * - * @param string $code code + * @param string $code Open catalogue of independent source signals. ai_bot and ai_browser carry weight 0; search_bot remains separate. browser_automation carries weight 90 in task 204 releases. UI Good bot and Bad bot groups are not risk event codes. Keep unknown codes and use the payload scoring_version/result_version for historical interpretation. * * @return self */ diff --git a/generated/lib/Model/Signal.php b/generated/lib/Model/Signal.php index 578921a..e0deea9 100644 --- a/generated/lib/Model/Signal.php +++ b/generated/lib/Model/Signal.php @@ -321,7 +321,7 @@ public function getName() /** * Sets name * - * @param string $name Signal name. The set is open: new names can appear at any time, so keep unknown names and use them for display and logging only. Known names: - `tor`: the request came through Tor; - `vpn`: a VPN was detected; - `privacy_relay`: a privacy relay such as iCloud Private Relay; - `proxy`: a proxy was detected; - `datacenter_ip`: the IP belongs to a datacenter or hosting range; - `abuser`: the IP has a record of abuse; - `browser_vpn_proxy`: a VPN or proxy inside the browser; - `antidetect_browser`: an anti-detect browser (the matching flag is `anti_detect_browser`); - `proxy_routed_antidetect`: the network check was routed through a proxy in a way typical for anti-detect browsers; - `port_scan_routed_via_proxy`: `proxy_routed_antidetect` carried forward from an earlier identification of the same device and IP; - `browser_automation`: browser automation, for example a WebDriver-controlled browser; - `javascript_disabled`: JavaScript or the browser APIs the checks need were unavailable; - `os_mismatch`: the operating system seen on the network differs from the one the browser reports; - `os_not_detected`: the operating system could not be determined; - `timezone_mismatch`: the browser timezone differs from the IP location timezone; - `stun_not_checked`: the network (STUN) check did not complete; - `stun_late_correction`: a late network result arrived; negative weight that cancels `stun_not_checked`; - `rate_limited`: the rate-limit marker, weight 999. A verdict carried forward from an earlier identification of the same device and IP (for example `antidetect_browser`) keeps a name derived from the original signal and can carry a partial weight. + * @param string $name Signal name. The set is open: new names can appear at any time, so keep unknown names and use them for display and logging only. Known names: - `tor`: the request came through Tor; - `vpn`: a VPN was detected; - `privacy_relay`: a privacy relay such as iCloud Private Relay; - `proxy`: a proxy was detected; - `datacenter_ip`: the IP belongs to a datacenter or hosting range; - `abuser`: the IP has a record of abuse; - `browser_vpn_proxy`: a VPN or proxy inside the browser; - `antidetect_browser`: an anti-detect browser (the matching flag is `anti_detect_browser`); - `proxy_routed_antidetect`: the network check was routed through a proxy in a way typical for anti-detect browsers; - `port_scan_routed_via_proxy`: `proxy_routed_antidetect` carried forward from an earlier identification of the same device and IP; - `browser_automation`: browser automation, for example a WebDriver-controlled browser (90 in task 204 scoring releases); - `javascript_disabled`: JavaScript or the browser APIs the checks need were unavailable; - `os_mismatch`: the operating system seen on the network differs from the one the browser reports; - `os_not_detected`: the operating system could not be determined; - `timezone_mismatch`: the browser timezone differs from the IP location timezone; - `stun_not_checked`: the network (STUN) check did not complete; - `stun_late_correction`: a late network result arrived; negative weight that cancels `stun_not_checked`; - `rate_limited`: the rate-limit marker, weight 999. A verdict carried forward from an earlier identification of the same device and IP (for example `antidetect_browser`) keeps a name derived from the original signal and can carry a partial weight. * * @return self */ diff --git a/generated/lib/Model/WebhookPingEvent.php b/generated/lib/Model/WebhookPingEvent.php index e289480..19902ed 100644 --- a/generated/lib/Model/WebhookPingEvent.php +++ b/generated/lib/Model/WebhookPingEvent.php @@ -405,7 +405,7 @@ public function getSchemaVersion() /** * Sets schema_version * - * @param string $schema_version Webhook contract version. Current release 2026-10-06; parsers also accept legacy 2026-06-01. + * @param string $schema_version Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. * * @return self */