- first implementation of REST

Sat, 29 Nov 2014 05:04:07 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Sat, 29 Nov 2014 05:04:07 +0200
changeset 92
da291d9426ea
parent 91
60ead38a61af
child 93
125f3c97071e

- first implementation of REST

cobalt.py file | annotate | diff | comparison | revisions
irc.py file | annotate | diff | comparison | revisions
rest.py file | annotate | diff | comparison | revisions
--- a/cobalt.py	Mon Nov 17 22:25:18 2014 +0200
+++ b/cobalt.py	Sat Nov 29 05:04:07 2014 +0200
@@ -37,6 +37,7 @@
 import hgpoll as HgPoll
 import bt as Bt
 import irc as Irc
+import rest as Rest
 
 if __name__ != '__main__':
 	raise ImportError ('cobalt may not be imported as a module')
@@ -103,6 +104,9 @@
 			else:
 				raise ValueError ("unknown autoconnect entry %s" % (aconn))
 		
+		# Start the REST server
+		Rest.RESTServer()
+		
 		g_BotActive = True
 		asyncore.loop()
 	except KeyboardInterrupt:
@@ -113,4 +117,4 @@
 		excepterm (e.message)
 
 if __name__ == '__main__':
-	main()
\ No newline at end of file
+	main()
--- a/irc.py	Mon Nov 17 22:25:18 2014 +0200
+++ b/irc.py	Sat Nov 29 05:04:07 2014 +0200
@@ -120,7 +120,7 @@
 							self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default='')))
 
 						umode = self.cfg.get_value ('umode', '')
-						
+
 						if umode != '':
 							self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', '')))
 					elif words[1] == "PRIVMSG":
@@ -151,17 +151,17 @@
 	def handle_privmsg (self, line):
 		rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) PRIVMSG ([^ ]+) :(.+)$')
 		match = rex.match (line)
-		
+
 		if not match:
 			broadcast ("Recieved bad PRIVMSG: %s" % line)
-		
+
 		sender = match.group (1)
 		user = match.group (2)
 		host = match.group (3)
 		channel = match.group (4)
 		message = match.group (5)
 		replyto = channel if channel != self.mynick else sender
-		
+
 		# Check for command.
 		if len(message) >= 2 and message[0] == self.commandprefix and message[1] != self.commandprefix:
 			stuff = message[1:].split(' ')
@@ -169,20 +169,20 @@
 			args = stuff[1:]
 			self.handle_command (sender, user, host, replyto, command, args, message)
 			return
-		
+
 		Bt.process_message (self, line, replyto)
 
 	def add_irc_channel (self, channame):
 		for channel in self.channels:
 			if channel.get_value ('name').upper() == channame.upper():
 				return
-		
+
 		channel = self.cfg.append_nodelist ('channels')
 		channel.set_value ('name', channame)
 		self.channels = self.cfg.get_nodelist ('channels')
 		self.write ('JOIN ' + channame)
 		self.cfg.save()
-	
+
 	def remove_irc_channel (self, channame):
 		for channel in self.channels:
 			if channel.get_value ('name') == channame:
@@ -190,29 +190,29 @@
 				break
 		else:
 			return
-		
+
 		self.write ('PART ' + channame)
 		self.cfg.save()
-	
+
 	def handle_command (self, sender, ident, host, replyto, command, args, message):
 		kvargs = {'sender': sender, 'ident': ident, 'host': host, 'replyto': replyto, 'cmdname': command, 'message': message}
-		
+
 		try:
 			result = ModuleCore.call_command (self, **kvargs)
-			
+
 			if result:
 				return
 		except ModuleCore.CommandError as e:
 			lines = str (e).split ('\n')
 			self.privmsg (replyto, 'error: %s' % lines[0])
-			
+
 			for line in lines[1:]:
 				self.privmsg (replyto, '  ' + line)
 			return
-	
+
 	def handle_error(self):
 		raise RestartError (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
-	
+
 	def restart(self):
 		raise RestartError('')
 
@@ -232,4 +232,4 @@
 		self.close_connection ('Caught exception')
 
 	def keyboardinterrupt (self):
-		self.close_connection ('KeyboardInterrupt')
\ No newline at end of file
+		self.close_connection ('KeyboardInterrupt')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rest.py	Sat Nov 29 05:04:07 2014 +0200
@@ -0,0 +1,194 @@
+import ssl
+import socket
+import errno
+import select
+import asyncore
+import base64
+import re
+import json
+import urllib
+import irc
+from datetime import datetime, timedelta
+from configfile import Config
+
+g_credentials = None
+g_portnumber = None
+g_throttle = []
+
+valid_repos = ['Torr_Samaho/zandronum', 'Torr_Samaho/zandronum-stable',
+	'crimsondusk/zandronum-sandbox', 'crimsondusk/zandronum-sandbox-stable',
+	'crimsondusk/testrepo']
+
+def is_throttled (address):
+	i = 0
+
+	while i < len (g_throttle):
+		if g_throttle[i][1] <= datetime.utcnow():
+			print 'Throttle of %s expired' % g_throttle[i][0][0]
+			item = g_throttle.pop (i) # expired
+
+			if item[0][0] == address[0]:
+				return False # this address expired
+
+			continue
+
+		if g_throttle[i][0][0] == address[0]:
+			return True # is throttled
+
+		i += 1
+
+	return False # not throttled
+
+def throttle (address):
+	tt = datetime.utcnow() + timedelta (0, 30)
+
+	try:
+		# attempt to just update the item
+		g_throttle[g_throttle.index (address)][1] = tt
+	except ValueError:
+		# not in list
+		g_throttle.append ([address, tt])
+
+	Irc.broadcast ('Throttling %s:%s for 30 seconds' % address)
+
+def handle_rest_http (data, address):
+	global g_credentials
+	displayaddress = '%s:%s' % address
+	authrex = re.compile (r'^authorization: Basic (.+)$')
+	payloadrex = re.compile (r'^payload=(.+)$')
+	authenticated = False
+	payload = ''
+
+	if not g_credentials:
+		g_credentials = base64.b64encode (Config.get_node ('rest').get_value ('credentials'))
+
+	# Authenticate and find the payload
+	for line in data:
+		match = authrex.match (line)
+
+		if match and match.group (1) == g_credentials)
+			authenticated = True
+			continue
+
+		match = payloadrex.match (line)
+		if match:
+			payload = match.group (1)
+
+	if not authenticated:
+		Irc.broadcast ('%s:%s failed to authenticate' % address)
+		throttle (address)
+		return
+
+	jsonstring = urllib.unquote_plus (payload).decode ('utf-8')
+
+	try:
+		jsondata = json.loads (jsonstring)
+		repodata = jsondata['repository']
+		repopath = '%s/%s' % (repodata['owner'], repodata['name']
+
+		if repopath not in valid_repos:
+			raise ValueError ('unknown repository %s' % repopath)
+
+		if 'commits' in jsondata:
+			for commit in jsondata['commits']:
+				Irc.broadcast ('%s: new commit %s' % (address, commit['node']))
+	except Exception as e:
+		Irc.broadcast ('%s provided bad JSON: %s' % (address, e))
+
+		try:
+			with open ('rejected_json.txt', 'w') as fp:
+				fp.write (jsonstring)
+			Irc.broadcast ('bad json written to rejected_json.txt')
+		except:
+			Irc.broadcast ('failed to log json')
+			pass
+
+		throttle (address)
+		return
+
+class RESTConnection (asyncore.dispatcher):
+	httpdata = ''
+	address=None
+	writebuffer = ''
+
+	def __init__ (self, conn, address):
+		asyncore.dispatcher.__init__ (self, conn)
+		self.socket = ssl.wrap_socket (conn, server_side=True, keyfile='key.pem',
+			certfile='cert.pem', do_handshake_on_connect=False)
+		self.socket.setblocking (0)
+		self.address = address
+
+		while True:
+			try:
+				self.socket.do_handshake()
+				break
+			except ssl.SSLError, err:
+				if err.args[0] == ssl.SSL_ERROR_WANT_READ:
+					select.select([self.socket], [], [])
+				elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
+					select.select([], [self.socket], [])
+				else:
+					Irc.broadcast ('%s:%s: SSL error: %s' % (self.address[0], self.address[1], err))
+					throttle (self.address)
+					self.close()
+					return
+
+	def write (self, msg):
+		self.writebuffer += msg
+
+	def readable (self):
+		return True
+
+	def writable (self):
+		return self.writebuffer
+
+	def handle_close (self):
+		self.finish()
+
+	def handle_read (self):
+		while 1:
+			try:
+				data = self.recv (4096)
+			except ssl.SSLError, err:
+				# EOF
+				self.finish()
+				return
+
+			self.httpdata += data.replace ('\r', '')
+
+	def finish (self):
+		handle_rest_http (self.httpdata.split ('\n'), self.address)
+		self.close()
+
+	def handle_write (self):
+		self.send (self.writebuffer)
+		self.writebuffer=''
+
+	def handle_error (self):
+		raise
+
+class RESTServer (asyncore.dispatcher):
+	def __init__ (self):
+		global g_portnumber
+
+		if g_portnumber == None:
+			g_portnumber = Config.get_node ('rest').get_value ('port')
+
+		asyncore.dispatcher.__init__ (self)
+		self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+		self.bind (('', g_portnumber))
+		self.listen (5)
+
+	def handle_accept (self):
+		sock, address = self.accept()
+
+		if is_throttled (address):
+			# throttled
+			sock.close()
+			return
+
+		Irc.broadcast ('REST connection from %s:%s' % address)
+		RESTConnection (sock, address)
+
+	def handle_error (self):
+		return

mercurial