get_manager()->table_exists(self::TABLE)) { return null; } $rows = $DB->get_records(self::TABLE, null, 'id ASC', '*', 0, 1); return $rows ? reset($rows) : null; } /** * @return \stdClass */ public static function ensure_identity(): \stdClass { global $DB; $row = self::get_record(); if ($row && !empty($row->installation_id) && !empty($row->private_key_pem)) { return $row; } $kp = crypto::generate_installation_keypair(); $now = time(); if ($row) { $row->installation_id = $row->installation_id ?: crypto::random_installation_id(); $row->private_key_pem = $kp['private_pem']; $row->public_key_pem = $kp['public_pem']; $row->timemodified = $now; $DB->update_record(self::TABLE, $row); return $row; } $rec = (object)[ 'installation_id' => crypto::random_installation_id(), 'private_key_pem' => $kp['private_pem'], 'public_key_pem' => $kp['public_pem'], 'auth_json' => null, 'license_uuid' => null, 'status' => 'inactive', 'last_error' => null, 'last_validated' => 0, 'timecreated' => $now, 'timemodified' => $now, ]; $rec->id = $DB->insert_record(self::TABLE, $rec); return $rec; } /** * @param array $fields */ public static function update(array $fields): \stdClass { global $DB; $row = self::ensure_identity(); foreach ($fields as $k => $v) { $row->{$k} = $v; } $row->timemodified = time(); $DB->update_record(self::TABLE, $row); return $row; } /** * @return array{payload:array,signature:string}|null */ public static function get_authorization(): ?array { $row = self::get_record(); if (!$row || empty($row->auth_json)) { return null; } $decoded = json_decode($row->auth_json, true); if (!is_array($decoded) || empty($decoded['payload']) || empty($decoded['signature'])) { return null; } return $decoded; } /** * @return array{code:string,message:string,details?:?array,request_id?:?string,at:int}|null */ public static function get_last_error(): ?array { $row = self::get_record(); if (!$row || empty($row->last_error)) { return null; } $raw = (string)$row->last_error; $decoded = json_decode($raw, true); if (is_array($decoded) && !empty($decoded['code'])) { return [ 'code' => (string)$decoded['code'], 'message' => (string)($decoded['message'] ?? $decoded['code']), 'details' => isset($decoded['details']) && is_array($decoded['details']) ? $decoded['details'] : null, 'request_id' => isset($decoded['request_id']) ? (string)$decoded['request_id'] : null, 'at' => (int)($decoded['at'] ?? ($row->timemodified ?? 0)), ]; } return [ 'code' => 'UNKNOWN', 'message' => $raw, 'details' => null, 'request_id' => null, 'at' => (int)($row->timemodified ?? 0), ]; } public static function rotate_identity(): \stdClass { $kp = crypto::generate_installation_keypair(); return self::update([ 'installation_id' => crypto::random_installation_id(), 'private_key_pem' => $kp['private_pem'], 'public_key_pem' => $kp['public_pem'], 'auth_json' => null, 'license_uuid' => null, 'status' => 'inactive', 'last_error' => null, 'last_validated' => 0, ]); } }