6 import tornado
.httpserver
7 import tornado
.httpclient
as httpclient
12 from rd
import RD
, Link
14 from tornado
.options
import options
, define
17 # insert into user (name,email) values('mikael','mikael@frykholm.com');
18 # insert into entry (userid,text) values (1,'My thoughts on ostatus');
22 "static_path": os
.path
.join(os
.path
.dirname(__file__
), "static"),
23 "cookie_secret": "supersecret123",
24 "login_url": "/login",
25 "xsrf_cookies": False,
26 "domain":"https://ronin.frykholm.com",
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"
32 class PushHandler(tornado
.web
.RequestHandler
):
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','')
40 hub_secret
= self
.get_argument('hub.secret','')
41 hub_verify_token
= self
.get_argument('hub.verify_token','')
42 print(self
.request
.body
)
43 if hub_mode
== 'unsubscribe':
45 path
= hub_topic
.split(self
.settings
['domain'])[1]
46 user
= path
.split('user/')[1]
47 row
= db
.execute("select id from author where name=?",(user
,)).fetchone()
48 expire
= datetime
.datetime
.utcnow() + datetime
.timedelta(seconds
=int(hub_lease_seconds
))
50 db
.execute("INSERT into subscriptions (author, expires, callback, secret, verified) "
51 "values (?,?,?,?,?)",(row
['id'],expire
,hub_callback
,hub_secret
,False))
54 http_client
= httpclient
.HTTPClient()
56 response
= http_client
.fetch(hub_callback
+"?hub.mode={}&hub.topic={}&hub.secret".format(hub_mode
,hub_topic
,hub_secret
))
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
))
66 #TODO add secret to outgoing feeds with hmac
68 class XrdHandler(tornado
.web
.RequestHandler
):
70 self
.render("templates/xrd.xml", hostname
="ronin.frykholm.com", url
=self
.settings
['domain'])
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="""
77 class SalmonHandler(tornado
.web
.RequestHandler
):
79 sp
= salmoning
.SalmonProtocol()
80 sp
.key_retriever
= apa()
81 data
= sp
.ParseSalmon(self
.request
.body
)
84 class FingerHandler(tornado
.web
.RequestHandler
):
86 user
= self
.get_argument('resource')
87 user
= user
.split('acct:')[1]
88 (user
,domain
) = user
.split('@')
89 row
= db
.execute("select id,salmon_pubkey from author where author.name=?",(user
,)).fetchone()
92 self
.write("Not found")
95 lnk
= Link(rel
='http://spec.example.net/photo/1.0',
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
))
105 rd
= RD(subject
='{}/{}'.format(self
.settings
['domain'],user
))
106 rd
.properties
.append('http://spec.example.net/type/person')
108 rd
.links
.append(lnk2
)
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
)))
117 self
.write(rd
.to_json())
119 class UserHandler(tornado
.web
.RequestHandler
):
121 entries
= db
.execute("select entry.id,text,ts from author,entry where author.id=entry.author and author.name=?",(user
,))
122 # import pdb;pdb.set_trace()
123 self
.set_header("Content-Type", 'application/atom+xml')
124 out
= self
.render("templates/feed.xml",
126 feed_url
="{}/user/{}".format(self
.settings
['domain'], user
),
127 hub_url
="{}/hub".format(self
.settings
['domain']),
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
,))
135 self
.set_header("Content-Type", 'application/atom+xml')
136 out
= self
.render_string("templates/feed.xml",
138 feed_url
="{}/user/{}".format(self
.settings
['domain'], user
),
139 hub_url
="{}/hub".format(self
.settings
['domain']),
142 #import pdb;pdb.set_trace()
144 subscribers
= db
.execute("select callback, secret from subscriptions, author where author.id=subscriptions.author and author.name=?",(user
,))
145 for url
,secret
in subscribers
:
146 digest
= hmac
.new(secret
.encode('utf8'), out
, digestmod
='sha1').hexdigest()
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()
152 application
= tornado
.web
.Application([
153 (r
"/.well-known/host-meta", XrdHandler
),
154 (r
"/.well-known/webfinger", FingerHandler
),
155 (r
"/salmon/(.+)", SalmonHandler
),
156 (r
"/user/(.+)", UserHandler
),
157 (r
"/hub", PushHandler
),
158 ],debug
=True,**settings
)
159 srv
= tornado
.httpserver
.HTTPServer(application
, )
162 gen_log
= logging
.getLogger("tornado.general")
163 gen_log
.warn("No db found, creating in {}".format(path
))
164 con
= sqlite3
.connect(path
)
165 con
.execute("""create table author (id integer primary key,
169 salmon_pubkey varchar, -- base64_urlencoded public modulus +.+ base64_urlencoded public exponent
170 salmon_privkey varchar -- base64_urlencoded private exponent
172 con
.execute(""" create table entry (id integer primary key,
174 text varchar, -- xml atom <entry>
176 ts timestamp default current_timestamp,
177 FOREIGN KEY(author) REFERENCES author(id));""")
179 create table subscriptions (id integer primary key,
185 FOREIGN KEY(author) REFERENCES author(id));""")
189 options
.define("config_file", default
="/etc/friends/friends.conf", type=str)
190 options
.define("webroot", default
="/srv/friends/", type=str)
192 if __name__
== "__main__":
193 dbPath
= 'friends.db'
194 # options.log_file_prefix="/tmp/friends"
195 tornado
.options
.parse_config_file(options
.config_file
)
196 tornado
.options
.parse_command_line()
197 gen_log
= logging
.getLogger("tornado.general")
198 gen_log
.info("Reading config from: %s", options
.config_file
,)
199 if not os
.path
.exists(dbPath
):
201 db
= sqlite3
.connect(dbPath
, detect_types
=sqlite3
.PARSE_DECLTYPES|sqlite3
.PARSE_COLNAMES
)
202 db
.row_factory
= sqlite3
.Row
204 tornado
.ioloop
.IOLoop
.instance().start()