ansible/roles/mediawiki/files/fedmsg-emit.php

281 lines
8.5 KiB
PHP
Raw Normal View History

<?php
/*
* fedmsg-emit.php
* -------------------------
*
* A MediaWiki plugin that emits messages to the Fedora Infrastructure Message
* Bus.
*
* Installation Instructions
* -------------------------
*
* You need to enable the following in /etc/php.ini for this to fly:
*
* extension=zmq.so
*
* To install the plugin for mediawiki, you need to copy this file to:
*
* /usr/share/mediawiki/fedmsg-emit.php
*
* And you also need to enable it by adding the following to the bottom of
* /var/www/html/wiki/LocalSettings.php
*
* require_once("$IP/fedmsg-emit.php");
*
* This corner of the fedmsg topology requires that an instance of fedmsg-relay
* be running somewhere. The reason being that multiple php processes get run
* by apache at the same time which would necessitate `n` different bind
* addresses for each process. Here, as opposed to 'normal' fedmsg
* configurations in our python webapps, each php process actively connects to
* the relay and emits messages. Our 'normal' python webapps establish a
* passive endpoint on which they broadcast messages.
*
* Miscellaneous
* -------------
*
* Version: 0.2.0
* License: LGPLv2+
* Author: Ralph Bean
* Source: http://github.com/ralphbean/fedmsg
*/
if (!defined('MEDIAWIKI')) {echo("Cannot be run outside MediaWiki"); die(1);}
// globals
$config = 0;
$queue = 0;
// A utility function, taken from the comments at
// http://php.net/manual/en/language.types.boolean.php
function to_bool ($_val) {
$_trueValues = array('yes', 'y', 'true');
$_forceLowercase = true;
if (is_string($_val)) {
return (in_array(
($_forceLowercase?strtolower($_val):$_val),
$_trueValues
));
} else {
return (boolean) $_val;
}
}
// A utility function to recursively sort an associative
// array by key. Kind of like ordereddict from Python.
// Used for encoding and signing messages.
function deep_ksort(&$arr) {
ksort($arr);
foreach ($arr as &$a) {
if (is_array($a) && !empty($a)) {
deep_ksort($a);
}
}
}
function initialize() {
global $config, $queue;
/* Load the config. Create a publishing socket. */
// Danger! Danger!
$json = shell_exec("fedmsg-config");
$config = json_decode($json, true);
/* Just make sure everything is sane with the fedmsg config */
if (!array_key_exists('relay_inbound', $config)) {
echo("fedmsg-config has no 'relay_inbound'");
return false;
}
$context = new ZMQContext(1, true);
$queue = $context->getSocket(ZMQ::SOCKET_PUB, "pub-a-dub-dub");
$queue->setSockOpt(ZMQ::SOCKOPT_LINGER, $config['zmq_linger']);
if (is_array($config['relay_inbound'])) {
// API for fedmsg >= 0.5.2
// TODO - be more robust here and if connecting to the first one fails, try
// the next, and the next, and etc...
$queue->connect($config['relay_inbound'][0]);
} else {
// API for fedmsg <= 0.5.1
$queue->connect($config['relay_inbound']);
}
# Go to sleep for a brief moment.. just long enough to let our zmq socket
# initialize.
if (array_key_exists('post_init_sleep', $config)) {
usleep($config['post_init_sleep'] * 1000000);
}
return true;
}
# Register our hooks with mediawiki
$wgHooks['PageContentSaveComplete'][] = 'article_save';
$wgHooks['UploadComplete'][] = 'upload_complete';
# This is a reimplementation of the python code in fedmsg/crypto.py
# That file is authoritative. Changes there should be reflected here.
function sign_message($message_obj) {
global $config;
# Stuff a little note in there about how we're signing this
$message_obj['crypto'] = 'x509';
# This is required so that the string we sign is identical in python and in
# php. Ordereddict is used there; ksort here.
deep_ksort($message_obj);
# It would be best to pass JSON_UNESCAPE_SLASHES as an option here, but it is
# not available until php-5.4
$message = json_encode($message_obj);
# In the meantime, we'll remove escaped slashes ourselves. This is
# necessary in order to produce the exact same encoding as python (so that our
# signatures match for validation).
$message = stripcslashes($message);
# Use this to debug message signature problems. What a pain...
#wfErrorLog($message . "\r\n", '/var/tmp/wiki-fedmsg-messages.log' );
2014-11-14 21:33:35 +00:00
# Step 0) - Find our cert.
$fqdn = gethostname();
$tokens = explode('.', $fqdn);
$hostname = $tokens[0];
$ssldir = $config['ssldir'];
$certname = $config['certnames']['mediawiki.'.$hostname];
# Step 1) - Load and encode the X509 cert
$cert_obj = openssl_x509_read(file_get_contents(
$ssldir.'/'.$certname.".crt"
));
$cert = "";
openssl_x509_export($cert_obj, $cert);
$cert = base64_encode($cert);
# Step 2) - Load and sign the jsonified message with the RSA private key
$rsa_private = openssl_get_privatekey(file_get_contents(
$ssldir.'/'.$certname.".key"
));
$signature = "";
openssl_sign($message, $signature, $rsa_private);
$signature = base64_encode($signature);
# Step 3) - Stuff it back in the message and return
$message_obj['signature'] = $signature;
$message_obj['certificate'] = $cert;
return $message_obj;
}
function emit_message($subtopic, $message) {
global $config, $queue;
# Re-implement some of the logc from fedmsg/core.py
# We'll have to be careful to keep this up to date.
$prefix = "org.fedoraproject." . $config['environment'] . ".wiki.";
$topic = $prefix . $subtopic;
$message_obj = array(
"topic" => $topic,
"msg" => $message,
"timestamp" => round(time(), 3),
"msg_id" => date("Y") . "-" . uuid_create(),
"username" => "apache",
# TODO -> we don't have a good way to increment this counter from php yet.
"i" => 1,
);
if (array_key_exists('sign_messages', $config) and to_bool($config['sign_messages'])) {
$message_obj = sign_message($message_obj);
}
$envelope = json_encode($message_obj);
$queue->send($topic, ZMQ::MODE_SNDMORE);
$queue->send($envelope);
}
function article_save(
&$article,
&$user,
$text,
$summary,
$minoredit,
$watchthis,
$sectionanchor,
&$flags,
$revision,
&$status,
$baseRevId,
$undidRevId=0
) {
# If for some reason or another we can't create our socket, then bail.
if (!initialize()) { return false; }
$topic = "article.edit";
$title = $article->getTitle();
if ( $title->getNsText() ) {
$titletext = $title->getNsText() . ":" . $title->getText();
} else {
$titletext = $title->getText();
}
if ( is_object($revision) ) {
$url = $title->getFullURL('diff=prev&oldid=' . $revision->getId());
} else {
$url = $title->getFullURL();
}
# Just send on all the information we can... change the attr names to be
# more pythonic in style, though.
$msg = array(
"title" => $titletext,
"user" => $user->getName(),
"minor_edit" => $minoredit,
"watch_this" => $watchthis,
"section_anchor" => $sectionanchor,
"revision" => $revision,
"base_rev_id" => $baseRevId,
"url" => $url,
#"summary" => $summary, # We *used* to send this, but it mucked things up.
# https://fedorahosted.org/fedora-infrastructure/ticket/3738#comment:7
#"text" => $text, # We *could* send this, but it's a lot of spam.
# TODO - flags?
# TODO - status?
);
emit_message($topic, $msg);
return true;
}
function upload_complete(&$image) {
# If for some reason or another we can't create our socket, then bail.
if (!initialize()) { return false; }
$topic = "upload.complete";
$msg = array(
"file_exists" => $image->getLocalFile()->fileExists, // 1 or 0
"media_type" => $image->getLocalFile()->media_type, // examples: "AUDIO", "VIDEO", ...
"mime" => $image->getLocalFile()->mime, // example: audio/mp3
"major_mime" => $image->getLocalFile()->major_mime, // e.g. audio
"minor_mime" => $image->getLocalFile()->minor_mime, // e.g. mp3
"size" => $image->getLocalFile()->size, //in bytes, e.g. 2412586
"user_id" => $image->getLocalFile()->getUser('id'), // int userId
"user_text" => $image->getLocalFile()->getUser('text'), // the username
"description" => $image->getLocalFile()->getDescriptionText(),
"url" => $image->getLocalFile()->url, // gives the relavive url for direct access of the uploaded media
);
# I know it seems peculiar to have an array here with only one element.
# https://github.com/fedora-infra/fedmsg_meta_fedora_infrastructure/pull/174
2014-11-14 22:14:45 +00:00
$msg['title'] = array(
2014-11-14 22:18:58 +00:00
"mPrefixedText" => $image->getLocalFile()->getTitle()->getPrefixedText(),
);
emit_message($topic, $msg);
return true;
}
?>