Sqlite is not needed for python3
[friends.git] / friends / server.py
1 import tornado.ioloop
2 import tornado.web
3 import os, os.path
4 import tornado.httpserver
5 import tornado.httpclient as httpclient
6 import sqlite3
7 import arrow
8 import datetime
9 from rd import RD, Link
10 import hashlib
11 import hmac
12 db = None
13 #insert into user (name,email) values('mikael','mikael@frykholm.com');
14 #insert into entry (userid,text) values (1,'My thoughts on ostatus');
15 import tornado.options
16
17 settings = {
18 "static_path": os.path.join(os.path.dirname(__file__), "static"),
19 "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
20 "login_url": "/login",
21 "xsrf_cookies": False,
22 "domain":"https://ronin.frykholm.com",
23
24 }
25 class PushHandler(tornado.web.RequestHandler):
26 #curl -v -k "https://ronin.frykholm.com/hub" -d "hub.callback=a" -d "hub.mode=b" -d "hub.topic=c" -d "hub.verify=d"
27 def post(self):
28 """ Someone wants to subscribe to hub_topic feed"""
29 hub_callback = self.get_argument('hub.callback')
30 hub_mode = self.get_argument('hub.mode')
31 hub_topic = self.get_argument('hub.topic')
32 hub_verify = self.get_argument('hub.verify')
33 hub_lease_seconds = self.get_argument('hub.lease_seconds','')
34 hub_secret = self.get_argument('hub.secret','')
35 hub_verify_token = self.get_argument('hub.verify_token','')
36 print(self.request.body)
37 if hub_mode == 'unsubscribe':
38 pass #FIXME
39 path = hub_topic.split(self.settings['domain'])[1]
40 user = path.split('user/')[1]
41 row = db.execute("select id from user where name=?",(user,)).fetchone()
42 expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=int(hub_lease_seconds))
43 if row:
44 db.execute("INSERT into subscriptions (userid, expires, callback, secret, verified) "
45 "values (?,?,?,?,?)",(row['id'],expire,hub_callback,hub_secret,False))
46 db.commit()
47 self.set_status(202)
48 http_client = httpclient.HTTPClient()
49 try:
50 response = http_client.fetch(hub_callback+"?hub.mode={}&hub.topic={}&hub.secret".format(hub_mode,hub_topic,hub_secret))
51 print(response.body)
52 except httpclient.HTTPError as e:
53 # HTTPError is raised for non-200 responses; the response
54 # can be found in e.response.
55 print("Error: " + str(e))
56 except Exception as e:
57 # Other errors are possible, such as IOError.
58 print("Error: " + str(e))
59 http_client.close()
60 #TODO add secret to outgoing feeds with hmac
61
62 class XrdHandler(tornado.web.RequestHandler):
63 def get(self):
64 self.render("templates/xrd.xml", hostname="ronin.frykholm.com", url=self.settings['domain'])
65
66 class FingerHandler(tornado.web.RequestHandler):
67 def get(self):
68 user = self.get_argument('resource')
69 user = user.split('acct:')[1]
70 (user,domain) = user.split('@')
71 rows = db.execute("select id from user where user.name=?",(user,)).fetchone()
72 if not rows:
73 self.set_status(404)
74 self.write("Not found")
75 self.finish()
76 return
77 lnk = Link(rel='http://spec.example.net/photo/1.0',
78 type='image/jpeg',
79 href='{}/static/{}.jpg'.format(self.settings['domain'],user))
80 lnk.titles.append(('User Photo', 'en'))
81 lnk.titles.append(('Benutzerfoto', 'de'))
82 lnk.properties.append(('http://spec.example.net/created/1.0', '1970-01-01'))
83 lnk2 = Link(rel='http://schemas.google.com/g/2010#updates-from',
84 type='application/atom+xml',
85 href='{}/user/{}'.format(self.settings['domain'],user))
86
87 rd = RD(subject='{}/{}'.format(self.settings['domain'],user))
88 rd.properties.append('http://spec.example.net/type/person')
89 rd.links.append(lnk)
90 rd.links.append(lnk2)
91 self.write(rd.to_json())
92
93 class UserHandler(tornado.web.RequestHandler):
94 def get(self, user):
95 entries = db.execute("select entry.id,text,ts from user,entry where user.id=entry.userid and user.name=?",(user,))
96 #import pdb;pdb.set_trace()
97 self.set_header("Content-Type", 'application/atom+xml')
98 out = self.render("templates/feed.xml",
99 user=user,
100 feed_url="{}/user/{}".format(self.settings['domain'], user),
101 hub_url="{}/hub".format(self.settings['domain']),
102 entries=entries,
103 arrow=arrow )
104 #digest = hmac.new()
105
106 def post(self, user):
107 entries = db.execute("select entry.id,text,ts from user,entry where user.id=entry.userid and user.name=?",(user,))
108
109 self.set_header("Content-Type", 'application/atom+xml')
110 out = self.render_string("templates/feed.xml",
111 user=user,
112 feed_url="{}/user/{}".format(self.settings['domain'], user),
113 hub_url="{}/hub".format(self.settings['domain']),
114 entries=entries,
115 arrow=arrow)
116 #import pdb;pdb.set_trace()
117 subscribers = db.execute("select callback, secret from subscriptions, user where user.id=subscriptions.userid and user.name=?",(user,))
118 for url,secret in subscribers:
119 digest = hmac.new(secret.encode('utf8'), out, digestmod='sha1').hexdigest()
120
121 req = httpclient.HTTPRequest(url=url, allow_nonstandard_methods=True,method='POST', body=out, headers={"X-Hub-Signature":"sha1={}".format(digest),"Content-Type": 'application/atom+xml',"Content-Length":len(out)})
122 apa = httpclient.HTTPClient()
123 apa.fetch(req)
124
125 application = tornado.web.Application([
126 (r"/.well-known/host-meta", XrdHandler),
127 (r"/.well-known/webfinger", FingerHandler),
128 (r"/user/(.+)", UserHandler),
129 (r"/hub", PushHandler),
130 ],debug=True,**settings)
131 srv = tornado.httpserver.HTTPServer(application, )
132 def setup_db(path):
133 print("No db found, creating in {}".format(path))
134 con = sqlite3.connect(path)
135 con.execute(""" create table user (id integer primary key,
136 name varchar,
137 email varchar);
138 create table entry (id integer primary key,
139 userid INTEGER,
140 text varchar,
141 ts timestamp default current_timestamp,
142 FOREIGN KEY(userid) REFERENCES user(id));
143 create table subscriptions (id integer primary key,
144 userid integer,
145 expires datetime,
146 callback varchar,
147 secret varchar,
148 verified bool,
149 FOREIGN KEY(userid) REFERENCES user(id));""")
150 con.commit()
151
152 if __name__ == "__main__":
153 dbPath = 'friends.db'
154 tornado.options.parse_command_line()
155 if not os.path.exists(dbPath):
156 setup_db(dbPath)
157 db = sqlite3.connect(dbPath, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
158 db.row_factory = sqlite3.Row
159 srv.listen(8080)
160 tornado.ioloop.IOLoop.instance().start()
161