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