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