Change schema to store xml directly to db
[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]
6fd5e534 46 row = db.execute("select id from author where name=?",(user,)).fetchone()
ca027dea
MF
47 expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=int(hub_lease_seconds))
48 if row:
6fd5e534 49 db.execute("INSERT into subscriptions (author, expires, callback, secret, verified) "
ca027dea 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
6fd5e534
MF
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
ddf4123d
MF
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('@')
6fd5e534
MF
88 row = db.execute("select id,salmon_pubkey from author where author.name=?",(user,)).fetchone()
89 if not row:
ddf4123d
MF
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)
6fd5e534
MF
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)))
ddf4123d 116 self.write(rd.to_json())
b3102128
MF
117
118class UserHandler(tornado.web.RequestHandler):
119 def get(self, user):
6fd5e534 120 entries = db.execute("select entry.id,text,ts from author,entry where author.id=entry.author and author.name=?",(user,))
5d2af2f4 121 # import pdb;pdb.set_trace()
ddf4123d 122 self.set_header("Content-Type", 'application/atom+xml')
ca027dea 123 out = self.render("templates/feed.xml",
ddf4123d
MF
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 )
ca027dea 129 #digest = hmac.new()
b3102128 130
86f2d7fc
MF
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()
6fd5e534
MF
142 # Notify subscribers
143 subscribers = db.execute("select callback, secret from subscriptions, author where author.id=subscriptions.author and author.name=?",(user,))
86f2d7fc
MF
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
b3102128
MF
151application = tornado.web.Application([
152 (r"/.well-known/host-meta", XrdHandler),
ddf4123d 153 (r"/.well-known/webfinger", FingerHandler),
b3102128 154 (r"/user/(.+)", UserHandler),
ddf4123d
MF
155 (r"/hub", PushHandler),
156 ],debug=True,**settings)
ca027dea 157srv = tornado.httpserver.HTTPServer(application, )
5d2af2f4 158
ddf4123d 159def setup_db(path):
5d2af2f4
MF
160 gen_log = logging.getLogger("tornado.general")
161 gen_log.warn("No db found, creating in {}".format(path))
ddf4123d 162 con = sqlite3.connect(path)
6fd5e534
MF
163 con.execute("""create table author (id integer primary key,
164 uri varchar,
ddf4123d 165 name varchar,
6fd5e534
MF
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,
ddf4123d 174 ts timestamp default current_timestamp,
6fd5e534
MF
175 FOREIGN KEY(author) REFERENCES author(id));""")
176 con.execute("""
ddf4123d 177 create table subscriptions (id integer primary key,
6fd5e534 178 author integer,
ddf4123d
MF
179 expires datetime,
180 callback varchar,
087a104b 181 secret varchar,
ddf4123d 182 verified bool,
6fd5e534 183 FOREIGN KEY(author) REFERENCES author(id));""")
ddf4123d 184 con.commit()
b3102128 185
5d2af2f4
MF
186
187options.define("config_file", default="/etc/friends/friends.conf", type=str)
188options.define("webroot", default="/srv/friends/", type=str)
189
b3102128 190if __name__ == "__main__":
ddf4123d 191 dbPath = 'friends.db'
5d2af2f4
MF
192# options.log_file_prefix="/tmp/friends"
193 tornado.options.parse_config_file(options.config_file)
ddf4123d 194 tornado.options.parse_command_line()
5d2af2f4
MF
195 gen_log = logging.getLogger("tornado.general")
196 gen_log.info("Reading config from: %s", options.config_file,)
ddf4123d
MF
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
5d2af2f4 201 srv.listen(80)
3ffcd3f8 202 tornado.ioloop.IOLoop.instance().start()
86f2d7fc 203