diff options
author | kongr45gpen <electrovesta@gmail.com> | 2016-03-15 16:41:58 +0200 |
---|---|---|
committer | kongr45gpen <electrovesta@gmail.com> | 2016-03-15 16:41:58 +0200 |
commit | 23d851aff066a39e3ceb01f85c30539e467a70ea (patch) | |
tree | 2feeadac920c62161163b3b4d7ee0ae8361787b9 | |
parent | e56a487b2a6672284a8f18a816782b5fdebf3b08 (diff) | |
download | supybot_github-23d851aff066a39e3ceb01f85c30539e467a70ea.tar.gz supybot_github-23d851aff066a39e3ceb01f85c30539e467a70ea.tar.bz2 supybot_github-23d851aff066a39e3ceb01f85c30539e467a70ea.zip |
Add support for github secrets
-rw-r--r-- | local/globals.py | 2 | ||||
-rw-r--r-- | local/handler/GithubHandler.py | 37 | ||||
-rw-r--r-- | local/utility.py | 22 | ||||
-rw-r--r-- | plugin.py | 64 | ||||
-rwxr-xr-x | request.sh | 11 |
5 files changed, 126 insertions, 10 deletions
diff --git a/local/globals.py b/local/globals.py index 6cc8c7b..b389cab 100644 --- a/local/globals.py +++ b/local/globals.py @@ -2,7 +2,9 @@ def init(): global messageList global configOverrides global travisStatuses + global secretDB messageList = [] configOverrides = {} travisStatuses = {} + secretDB = None diff --git a/local/handler/GithubHandler.py b/local/handler/GithubHandler.py index 4d420e3..53ea740 100644 --- a/local/handler/GithubHandler.py +++ b/local/handler/GithubHandler.py @@ -1,10 +1,12 @@ import os import re +import hmac import json import time import random import urllib import urllib2 +import hashlib import urlparse import threading import BaseHTTPServer @@ -37,17 +39,12 @@ class GithubHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_POST(s): """Respond to a POST request.""" length = int(s.headers['Content-Length']) + payload = s.rfile.read(length).decode('utf-8') if 'content-type' not in s.headers or s.headers['content-type'] == 'application/x-www-form-urlencoded': - post_data = urlparse.parse_qs(s.rfile.read(length).decode('utf-8')) + post_data = urlparse.parse_qs(payload) data = json.loads(post_data['payload'][0]) else: - data = json.loads(s.rfile.read(length).decode('utf-8')) - - s.send_response(200) - s.send_header('Content-type', 'text/html') - s.end_headers() - s.wfile.write("Thanks, you're awesome.\n") - s.wfile.write(s.path.split('/')) + data = json.loads(payload) if 'X-GitHub-Event' in s.headers: eventType = s.headers['X-GitHub-Event'] @@ -58,7 +55,7 @@ class GithubHandler(BaseHTTPServer.BaseHTTPRequestHandler): if not os.path.exists('requests/'): os.makedirs('requests') - f = open('requests/' + eventType + strftime("%Y-%m-%d %H:%M:%S") + '.json', 'w') + f = open('requests/' + eventType.replace('/','_') + strftime("%Y-%m-%d %H:%M:%S") + '.json', 'w') f.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))) f.close() @@ -92,11 +89,33 @@ class GithubHandler(BaseHTTPServer.BaseHTTPRequestHandler): i+=1 + s.send_response(200) + s.send_header('Content-type', 'text/html') + s.end_headers() + s.wfile.write("Thanks, you're awesome.\n") + s.wfile.write(s.path.split('/')) + if requireCode and receivedcode != configValue('passcode'): # The password is wrong s.wfile.write("The password is wrong") return + secret = getChannelSecret(channel) + if secret is not None: + if not 'X-Hub-Signature' in s.headers: + s.wfile.write("This channel requires a secret") + return + + digest = "sha1=%s" % (hmac.new(secret, payload, hashlib.sha1).hexdigest(),) + log.debug("expected digest: %s", digest) + + provided = s.headers['X-Hub-Signature'] + log.debug("provided digest: %s", provided) + + if not secureCompare(digest, provided): + s.wfile.write("Invalid secret key") + return + brackets = parseBrackets(configValue('brackets')) themeName = configValue('theme') diff --git a/local/utility.py b/local/utility.py index de540a6..2594b61 100644 --- a/local/utility.py +++ b/local/utility.py @@ -1,5 +1,7 @@ import re import math +import random +import string import urllib2 import supybot.conf as conf @@ -134,6 +136,26 @@ def isStatusVisible(repo, status): globals.travisStatuses[repo] = status return changed +def randomString(length): + """Returns a securely generated random string of a specific length""" + return ''.join(random.SystemRandom().choice( + string.ascii_uppercase + string.ascii_lowercase + string.digits + ) for _ in range(length)) + +def secureCompare(s1, s2): + """Securely compare two strings""" + return sum(i != j for i, j in zip(s1, s2)) is 0 + +def getChannelSecret(channel): + """Returns a secret for a channel, or None if that channel has no secret""" + if globals.secretDB is None: + return None + try: + record = globals.secretDB.get(channel, 1) + return record.secret + except KeyError: + return None + def hexToMirc(hash): colors = { 'white': (255, 255, 255), @@ -34,6 +34,7 @@ import urlparse import threading import BaseHTTPServer +import supybot.dbi as dbi import supybot.log as log import supybot.conf as conf import supybot.utils as utils @@ -61,6 +62,11 @@ class Github(callbacks.Plugin): messages = [] pass + add = None + search = None + stats = None + change = None + def ServerStart(self, httpd): try: log.info('Server Starts - %s:%s' % ('', self.port)) @@ -68,6 +74,7 @@ class Github(callbacks.Plugin): except: return + def __init__(self, irc): self.__parent = super(Github, self) self.__parent.__init__(irc) @@ -117,6 +124,63 @@ class Github(callbacks.Plugin): # Debug command get = wrap(get, ['lowered', optional('lowered'), optional('text')]) if world.testing else False + def abs_val(self,irc,msg,args): + pass + abs_val = wrap(abs_val,[]) + + class secret(callbacks.Commands): + class DB(plugins.DbiChannelDB): + class DB(dbi.DB): + class Record(dbi.Record): + __fields__ = [ + 'secret' + ] + def add(self, secret, **kwargs): + record = self.Record(secret = secret, **kwargs) + return super(self.__class__, self).add(record) + def set(self, id, secret, **kwargs): + record = self.Record(secret = secret, **kwargs) + return super(self.__class__, self).set(id, record) + + def __init__(self, irc): + # self.db = self.DB(("%s-secret") % (self.name(),)) + print(conf.supybot.databases()) + super(Github.secret, self).__init__(irc) + self.db = plugins.DB(("github-secret"), {'flat': self.DB})() + globals.secretDB = self.db + + def set(self, irc, msg, args, channel, secret): + """[<channel>] secret + + Sets a Github secret for a channel to a specific value. + <channel> is only necessary if the message isn't sent in the channel itself. + """ + self.db.set(channel, 1, secret) + # record = Github.secret.DB.DB.Record(secret = sec) + # self.db.set(channel, 1, secret) + irc.replySuccess() + set = wrap(set, ['channel', 'text']) + + def reset(self, irc, msg, args, channel): + """[<channel>] + + Removes a Github secret for a channel. + <channel> is only necessary if the message isn't sent in the channel itself. + """ + self.db.remove(channel, 1) + irc.reply("Channel %s no longer has a secret." % channel) + reset = wrap(reset, ['channel']) + + def generate(self, irc, msg, args, channel): + """<channel> + + Generates a Github secret for a channel. + <channel> is only necessary if the message isn't sent in the channel itself. + """ + secret = Utility.randomString(40) + irc.reply("Setting secret for %s to: %s" % (channel, secret)) + self.db.set(channel, 1, secret) + generate = wrap(generate, ['channel']) Class = Github @@ -1,3 +1,12 @@ #!/usr/bin/env bash -curl --data "payload=`cat $1`" http://localhost:8093/ +DATA="payload=`cat $1`" + +echo $DATA + +hash=$(echo -n "$DATA" | openssl dgst -sha1 -hmac "$2" | awk '{print $2}') +echo $hash + +echo -n "$DATA" > ~/repos/supybot-github/cmpr + +curl --header "X-GitHub-Event: $1" --header "X-Hub-Signature: sha1=$hash" --data "$DATA" http://localhost:8093/ |