mirror of
https://github.com/matrix-construct/construct
synced 2024-11-29 18:22:50 +01:00
modules: Preliminary webhook module.
This commit is contained in:
parent
b2caba08a9
commit
89cd08ac22
2 changed files with 389 additions and 0 deletions
|
@ -55,11 +55,13 @@ AM_LDFLAGS = \
|
|||
moduledir = @moduledir@
|
||||
|
||||
webroot_la_SOURCES = webroot.cc
|
||||
webhook_la_SOURCES = webhook.cc
|
||||
console_la_SOURCES = console.cc
|
||||
vm_la_SOURCES = vm.cc
|
||||
|
||||
module_LTLIBRARIES = \
|
||||
webroot.la \
|
||||
webhook.la \
|
||||
console.la \
|
||||
vm.la \
|
||||
###
|
||||
|
|
387
modules/webhook.cc
Normal file
387
modules/webhook.cc
Normal file
|
@ -0,0 +1,387 @@
|
|||
// Matrix Construct
|
||||
//
|
||||
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice is present in all copies. The
|
||||
// full license for this software is available in the LICENSE file.
|
||||
|
||||
using namespace ircd;
|
||||
|
||||
mapi::header
|
||||
IRCD_MODULE
|
||||
{
|
||||
"Webhook Handler"
|
||||
};
|
||||
|
||||
conf::item<std::string>
|
||||
webhook_secret
|
||||
{
|
||||
{ "name", "webhook.secret" }
|
||||
};
|
||||
|
||||
conf::item<std::string>
|
||||
webhook_user
|
||||
{
|
||||
{ "name", "webhook.user" }
|
||||
};
|
||||
|
||||
conf::item<std::string>
|
||||
webhook_room
|
||||
{
|
||||
{ "name", "webhook.room" }
|
||||
};
|
||||
|
||||
conf::item<std::string>
|
||||
webhook_url
|
||||
{
|
||||
{ "name", "webhook.url" },
|
||||
{ "default", "/webhook" }
|
||||
};
|
||||
|
||||
resource
|
||||
webhook_resource
|
||||
{
|
||||
string_view{webhook_url},
|
||||
{
|
||||
"Webhook Resource",
|
||||
webhook_resource.DIRECTORY
|
||||
}
|
||||
};
|
||||
|
||||
static resource::response
|
||||
post__webhook(client &,
|
||||
const resource::request &);
|
||||
|
||||
resource::method
|
||||
webhook_post
|
||||
{
|
||||
webhook_resource, "POST", post__webhook
|
||||
};
|
||||
|
||||
static void
|
||||
github_handle(client &,
|
||||
const resource::request &);
|
||||
|
||||
resource::response
|
||||
post__webhook(client &client,
|
||||
const resource::request &request)
|
||||
{
|
||||
if(has(http::headers(request.head.headers), "X-GitHub-Event"_sv))
|
||||
github_handle(client, request);
|
||||
|
||||
return resource::response
|
||||
{
|
||||
client, http::OK
|
||||
};
|
||||
}
|
||||
|
||||
static bool
|
||||
github_validate(const string_view &sig,
|
||||
const const_buffer &content,
|
||||
const string_view &secret);
|
||||
|
||||
static string_view
|
||||
github_find_commit_hash(const json::object &content);
|
||||
|
||||
static string_view
|
||||
github_find_issue_number(const json::object &content);
|
||||
|
||||
static string_view
|
||||
github_find_party(const json::object &content);
|
||||
|
||||
static std::ostream &
|
||||
github_handle__push(std::ostream &,
|
||||
const json::object &content);
|
||||
|
||||
static std::ostream &
|
||||
github_heading(std::ostream &,
|
||||
const string_view &type,
|
||||
const json::object &content);
|
||||
|
||||
void
|
||||
github_handle(client &client,
|
||||
const resource::request &request)
|
||||
{
|
||||
const http::headers &headers
|
||||
{
|
||||
request.head.headers
|
||||
};
|
||||
|
||||
const auto sig
|
||||
{
|
||||
headers.at("X-Hub-Signature")
|
||||
};
|
||||
|
||||
if(!github_validate(sig, request.content, webhook_secret))
|
||||
throw http::error
|
||||
{
|
||||
http::UNAUTHORIZED, "X-Hub-Signature verification failed"
|
||||
};
|
||||
|
||||
const string_view &type
|
||||
{
|
||||
headers.at("X-GitHub-Event")
|
||||
};
|
||||
|
||||
const string_view &delivery
|
||||
{
|
||||
headers.at("X-GitHub-Delivery")
|
||||
};
|
||||
|
||||
thread_local char buf[2048];
|
||||
std::stringstream out;
|
||||
pubsetbuf(out, buf);
|
||||
|
||||
github_heading(out, type, request.content);
|
||||
|
||||
if(type == "push")
|
||||
github_handle__push(out, request.content);
|
||||
|
||||
if(!string_view(webhook_room))
|
||||
return;
|
||||
|
||||
const auto room_id
|
||||
{
|
||||
m::room_id(string_view(webhook_room))
|
||||
};
|
||||
|
||||
if(!string_view(webhook_user))
|
||||
return;
|
||||
|
||||
const m::user::id::buf user_id
|
||||
{
|
||||
string_view(webhook_user), my_host()
|
||||
};
|
||||
|
||||
const auto evid
|
||||
{
|
||||
m::msghtml(room_id, user_id, view(out, buf), "No alt text")
|
||||
};
|
||||
|
||||
log::info
|
||||
{
|
||||
"Webhook [%s] '%s' delivered to %s %s",
|
||||
delivery,
|
||||
type,
|
||||
string_view{room_id},
|
||||
string_view{evid}
|
||||
};
|
||||
}
|
||||
|
||||
static std::ostream &
|
||||
github_heading(std::ostream &out,
|
||||
const string_view &type,
|
||||
const json::object &content)
|
||||
{
|
||||
const json::object repository
|
||||
{
|
||||
content["repository"]
|
||||
};
|
||||
|
||||
out << "<a href=\\\"" << unquote(repository["html_url"]) << "\\\">"
|
||||
<< "<b>" << unquote(repository["full_name"]) << "</b>"
|
||||
<< "</a>";
|
||||
|
||||
const string_view commit_hash
|
||||
{
|
||||
github_find_commit_hash(content)
|
||||
};
|
||||
|
||||
if(commit_hash && type == "push")
|
||||
out << " <b><font color=\\\"#FF5733\\\">";
|
||||
else if(commit_hash && type == "pull_request")
|
||||
out << " <b><font color=\\\"fuschia\\\">";
|
||||
else if(commit_hash)
|
||||
out << " <b>";
|
||||
|
||||
if(commit_hash)
|
||||
out << commit_hash.substr(0, 8)
|
||||
<< "</font></b>";
|
||||
|
||||
const string_view issue_number
|
||||
{
|
||||
github_find_issue_number(content)
|
||||
};
|
||||
|
||||
if(issue_number)
|
||||
out << " <b>#" << issue_number << "</b>";
|
||||
else
|
||||
out << " " << type;
|
||||
|
||||
const string_view party
|
||||
{
|
||||
github_find_party(content)
|
||||
};
|
||||
|
||||
if(party)
|
||||
out << " by " << party;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::ostream &
|
||||
github_handle__push(std::ostream &out,
|
||||
const json::object &content)
|
||||
{
|
||||
const json::array commits
|
||||
{
|
||||
content["commits"]
|
||||
};
|
||||
|
||||
const auto count
|
||||
{
|
||||
size(commits)
|
||||
};
|
||||
|
||||
if(!count)
|
||||
{
|
||||
out << " <font color=\\\"#FF0000\\\">";
|
||||
|
||||
if(content["ref"])
|
||||
out << " " << unquote(content["ref"]);
|
||||
|
||||
out << " deleted</font>";
|
||||
return out;
|
||||
}
|
||||
|
||||
if(content["ref"])
|
||||
out << " " << unquote(content["ref"]);
|
||||
|
||||
out << " <a href=\\\"" << unquote(content["compare"]) << "\\\">"
|
||||
<< "<b>" << count << " commits</b>"
|
||||
<< "</a>";
|
||||
|
||||
if(content["forced"] == "true")
|
||||
out << " (rebase)";
|
||||
|
||||
out << "<pre>";
|
||||
for(const json::object &commit : commits)
|
||||
{
|
||||
const auto url(unquote(commit["url"]));
|
||||
const auto id(unquote(commit["id"]));
|
||||
const auto sid(id.substr(0, 8));
|
||||
out << " <a href=\\\"" << url << "\\\">"
|
||||
<< "<b>" << sid << "</b>"
|
||||
<< "</a>";
|
||||
|
||||
const json::object author(commit["author"]);
|
||||
out << " " << unquote(author["name"]);
|
||||
|
||||
const json::object committer(commit["committer"]);
|
||||
if(committer["email"] != author["email"])
|
||||
out << " via " << committer["name"];
|
||||
|
||||
const auto summary(token(unquote(commit["message"]), '\n', 0));
|
||||
out << " <u>"
|
||||
<< summary
|
||||
<< "</u>";
|
||||
|
||||
out << "<br />";
|
||||
}
|
||||
|
||||
out << "</pre>";
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Researched from yestifico bot
|
||||
static string_view
|
||||
github_find_party(const json::object &content)
|
||||
{
|
||||
const json::object pusher(content["pusher"]);
|
||||
if(!empty(pusher))
|
||||
return unquote(pusher["name"]);
|
||||
|
||||
const json::object sender(content["sender"]);
|
||||
if(!empty(sender))
|
||||
return unquote(sender["login"]);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Researched from yestifico bot
|
||||
static string_view
|
||||
github_find_issue_number(const json::object &content)
|
||||
{
|
||||
const json::object issue(content["issue"]);
|
||||
if(!empty(issue))
|
||||
return unquote(issue["number"]);
|
||||
|
||||
if(content["number"])
|
||||
return unquote(content["number"]);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Researched from yestifico bot
|
||||
static string_view
|
||||
github_find_commit_hash(const json::object &content)
|
||||
{
|
||||
if(content["sha"])
|
||||
return unquote(content["sha"]);
|
||||
|
||||
const json::object commit(content["commit"]);
|
||||
if(!empty(commit))
|
||||
return unquote(commit["sha"]);
|
||||
|
||||
const json::object head(content["head"]);
|
||||
if(!empty(head))
|
||||
return unquote(head["commit"]);
|
||||
|
||||
const json::object head_commit(content["head_commit"]);
|
||||
if(!empty(head_commit))
|
||||
return unquote(head_commit["id"]);
|
||||
|
||||
const json::object comment(content["comment"]);
|
||||
if(!empty(comment))
|
||||
return unquote(comment["commit_id"]);
|
||||
|
||||
if(content["commit"])
|
||||
return unquote(content["commit"]);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool
|
||||
github_validate(const string_view &sigheader,
|
||||
const const_buffer &content,
|
||||
const string_view &secret)
|
||||
try
|
||||
{
|
||||
const auto sig
|
||||
{
|
||||
split(sigheader, "=")
|
||||
};
|
||||
|
||||
crh::hmac hmac
|
||||
{
|
||||
sig.first, secret
|
||||
};
|
||||
|
||||
hmac.update(content);
|
||||
|
||||
char ubuf[32];
|
||||
assert(sizeof(ubuf) >= hmac.length());
|
||||
const const_buffer hmac_bin
|
||||
{
|
||||
hmac.finalize(ubuf)
|
||||
};
|
||||
|
||||
char abuf[64];
|
||||
static_assert(sizeof(abuf) >= sizeof(ubuf) * 2);
|
||||
const string_view hmac_hex
|
||||
{
|
||||
u2a(abuf, hmac_bin)
|
||||
};
|
||||
|
||||
return hmac_hex == sig.second;
|
||||
}
|
||||
catch(const crh::error &e)
|
||||
{
|
||||
throw http::error
|
||||
{
|
||||
http::NOT_IMPLEMENTED, "The signature algorithm is not supported"
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue