/** * Validates JSON data against a schema. * * The schema may be passed as file path or as object returned from * `json_decode($schemaFile)`. * * @param mixed $data The decoded JSON data * @param string|object|null $schema The schema file or object. If `null`, * the validator will look for a `$schema` * property * * @return string[] The errors found during validation. Returns an empty * array if no errors were found * * @throws InvalidSchemaException If the schema is invalid */ public function validate($data, $schema = null) { if (null === $schema && isset($data->{'$schema'})) { $schema = $data->{'$schema'}; } if (is_string($schema)) { $schema = $this->loadSchema($schema); } elseif (is_object($schema)) { $this->assertSchemaValid($schema); } else { throw new InvalidSchemaException(sprintf('The schema must be given as string, object or in the "$schema" ' . 'property of the JSON data. Got: %s', is_object($schema) ? get_class($schema) : gettype($schema))); } $this->validator->reset(); try { $this->validator->check($data, $schema); } catch (InvalidArgumentException $e) { throw new InvalidSchemaException(sprintf('The schema is invalid: %s', $e->getMessage()), 0, $e); } $errors = array(); if (!$this->validator->isValid()) { $errors = (array) $this->validator->getErrors(); foreach ($errors as $key => $error) { $prefix = $error['property'] ? $error['property'] . ': ' : ''; $errors[$key] = $prefix . $error['message']; } } return $errors; }
/** * @param mixed $data * @param \stdClass $schema * @param string $location (possible values: query, path, body, headers) */ protected function validate($data, $schema, $location) { $this->validator->check($data, $schema); if (!$this->validator->isValid()) { $violations = array_map(function ($error) use($location) { return new ConstraintViolation($error['property'], $error['message'], $error['constraint'], $location); }, $this->validator->getErrors()); foreach ($violations as $violation) { $this->addViolation($violation); } } $this->validator->reset(); }
/** * Validates JSON data against a schema. * * The schema may be passed as file path or as object returned from * `json_decode($schemaFile)`. * * @param mixed $data The decoded JSON data. * @param string|object $schema The schema file or object. * * @return string[] The errors found during validation. Returns an empty * array if no errors were found. * * @throws InvalidSchemaException If the schema is invalid. */ public function validate($data, $schema) { if (is_string($schema)) { $schema = $this->loadSchema($schema); } else { $this->assertSchemaValid($schema); } $this->validator->reset(); try { $this->validator->check($data, $schema); } catch (InvalidArgumentException $e) { throw new InvalidSchemaException(sprintf('The schema is invalid: %s', $e->getMessage()), 0, $e); } $errors = array(); if (!$this->validator->isValid()) { $errors = (array) $this->validator->getErrors(); foreach ($errors as $key => $error) { $prefix = $error['property'] ? $error['property'] . ': ' : ''; $errors[$key] = $prefix . $error['message']; } } return $errors; }
/** * Handles all hooks. * * @param Request $request HTTP request * @param Response $response HTTP response * @param array $args URL args */ public function handle(Request $request, Response $response, array $args) { $response->setHeader("content-type", "text/plain"); $token = $request->getQueryVars()["token"] ?? ""; if (!$token || !is_string($token)) { $response->setStatus(401); $response->send("Failure: No token was provided."); return; } // use @ so we don't have to check for invalid strings manually $token = (string) @hex2bin($token); $hook = (yield $this->hookRepository->get($args["id"])); if (!$hook) { $response->setStatus(404); $response->send("Failure: Hook does not exist."); return; } if (!hash_equals($hook->token, $token)) { $response->setStatus(403); $response->send("Failure: Provided token doesn't match."); return; } $name = $args["service"]; if (!isset($this->services[$name])) { $response->setStatus(404); $response->send("Failure: Unknown service."); return; } $contentType = strtok($request->getHeader("content-type"), ";"); $body = (yield $request->getBody()); switch ($contentType) { case "application/json": $payload = json_decode($body); break; case "application/x-www-form-urlencoded": parse_str($body, $payload); $payload = json_decode(json_encode($payload)); break; default: $response->setStatus(415); $response->send("Failure: Content-type not supported."); return; } $service = $this->services[$name]; $headers = $request->getAllHeaders(); $event = $service->getEventName($headers, $payload); if (!isset($this->schemas[$name][$event])) { $response->setStatus(400); $response->send("Failure: Event not supported."); return; } $schema = $this->schemas[$name][$event]; $this->validator->reset(); $this->validator->check($payload, $schema); if (!$this->validator->isValid()) { $errors = $this->validator->getErrors(); $errors = array_reduce($errors, function (string $carry, array $item) : string { if ($item["property"]) { return $carry . sprintf("\n%s: %s", $item["property"], $item["message"]); } else { return $carry . "\n" . $item["message"]; } }, ""); $response->setStatus(400); $response->send("Failure: Payload validation failed." . $errors); return; } $message = $service->handle($headers, $payload); try { if ($message) { $req = (new HttpRequest())->setMethod("PUT")->setUri($this->config["api"] . "/messages")->setHeader("authorization", "Basic " . base64_encode("{$this->config['user_id']}:{$this->config['token']}"))->setBody(json_encode(["room_id" => $hook->room_id, "text" => $message->getText(), "data" => $message->getData()])); $resp = (yield $this->http->request($req)); if (intval($resp->getStatus() / 100) !== 2) { $message = "API request failed: " . $resp->getStatus(); if ($resp->getBody()) { $message .= "\n" . $resp->getBody(); } throw new Exception($message); } } $response->send("Success: " . ($message ? "Message sent." : "Message skipped.")); } catch (Exception $e) { $response->setStatus(500); $response->send("Failure: Couldn't persist message."); } }