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