]> git.ipfire.org Git - ipfire.org.git/blame - src/web/base.py
Migrate to libloc
[ipfire.org.git] / src / web / base.py
CommitLineData
940227cb
MT
1#!/usr/bin/python
2
cc3b928d 3import datetime
66862195 4import dateutil.parser
cfe7d74c 5import functools
11347e46 6import http.client
a69e87a1 7import ipaddress
47ed77ed 8import logging
940227cb
MT
9import time
10import tornado.locale
11import tornado.web
12
f110a9ff 13from ..decorators import *
a95c2f97 14from .. import util
60024cc8 15
cfe7d74c 16def blacklisted(method):
cfe7d74c 17 @functools.wraps(method)
9fdf4fb7 18 async def wrapper(self, *args, **kwargs):
cfe7d74c 19 # Check if remote is blacklisted
9fdf4fb7 20 is_blacklisted = await self.remote.is_blacklisted()
cfe7d74c
MT
21
22 # If so, redirect to the blocked page
23 if is_blacklisted:
24 logging.warning("%s is blacklisted" % self.remote)
25
26 return self.redirect("https://www.ipfire.org/blocked")
27
28 return method(self, *args, **kwargs)
29
30 return wrapper
31
372ef119
MT
32class ratelimit(object):
33 def __init__(self, minutes=15, requests=180):
34 self.minutes = minutes
35 self.requests = requests
36
37 def __call__(self, method):
38 @functools.wraps(method)
39 def wrapper(handler, *args, **kwargs):
40 # Pass the request to the rate limiter and get a request object
41 req = handler.backend.ratelimiter.handle_request(handler.request,
42 handler, minutes=self.minutes, limit=self.requests)
43
44 # If the rate limit has been reached, we won't allow
45 # processing the request and therefore send HTTP error code 429.
46 if req.is_ratelimited():
47 raise tornado.web.HTTPError(429, "Rate limit exceeded")
48
49 return method(handler, *args, **kwargs)
50
51 return wrapper
52
cfe7d74c 53
940227cb 54class BaseHandler(tornado.web.RequestHandler):
f6ed3d4d
MT
55 def set_expires(self, seconds):
56 # For HTTP/1.1
b592d7c8 57 self.add_header("Cache-Control", "max-age=%s, must-revalidate" % seconds)
f6ed3d4d
MT
58
59 # For HTTP/1.0
60 expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds)
b592d7c8 61 self.add_header("Expires", expires)
f6ed3d4d 62
37ed7c3c
MT
63 def write_error(self, status_code, **kwargs):
64 # Translate code into message
65 try:
11347e46 66 message = http.client.responses[status_code]
37ed7c3c
MT
67 except KeyError:
68 message = None
69
70 self.render("error.html", status_code=status_code, message=message, **kwargs)
71
d3d02231
MT
72 def xsrf_form_html(self, *args, **kwargs):
73 # Set Vary: Cookie header
74 self.add_header("Vary", "Cookie")
75
76 return super().xsrf_form_html(*args, **kwargs)
77
4ca1a601
MT
78 @property
79 def hostname(self):
80 # Remove the development prefix
81 return self.request.host.replace(".dev.", ".")
82
bff08cb0
MT
83 def get_template_namespace(self):
84 ns = tornado.web.RequestHandler.get_template_namespace(self)
85
911064cf 86 now = datetime.date.today()
cc3b928d 87
bff08cb0 88 ns.update({
6c13ca2d 89 "backend" : self.backend,
5613b94b 90 "debug" : self.application.settings.get("debug", False),
a95c2f97
MT
91 "format_size" : util.format_size,
92 "format_time" : util.format_time,
4ca1a601 93 "hostname" : self.hostname,
911064cf
MT
94 "now" : now,
95 "year" : now.year,
bff08cb0 96 })
940227cb 97
bff08cb0 98 return ns
940227cb 99
9068dba1
MT
100 def get_remote_ip(self):
101 # Fix for clients behind a proxy that sends "X-Forwarded-For".
66862195 102 remote_ips = self.request.remote_ip.split(", ")
494d80e6 103
66862195
MT
104 for remote_ip in remote_ips:
105 try:
a69e87a1 106 addr = ipaddress.ip_address(remote_ip)
66862195
MT
107 except ValueError:
108 # Skip invalid IP addresses.
109 continue
9068dba1 110
66862195
MT
111 # Check if the given IP address is from a
112 # private network.
113 if addr.is_private:
114 continue
9068dba1 115
66862195 116 return remote_ip
9068dba1 117
494d80e6
MT
118 # Return the last IP if nothing else worked
119 return remote_ips.pop()
120
cfe7d74c 121 @lazy_property
440aba92 122 def current_address(self):
cfe7d74c
MT
123 address = self.get_remote_ip()
124
125 if address:
440aba92 126 return util.Address(self.backend, address)
cfe7d74c 127
f110a9ff
MT
128 @lazy_property
129 def current_country_code(self):
440aba92
MT
130 if self.current_address:
131 return self.current_address.country_code
9068dba1 132
bd2723d4
MT
133 def get_argument_int(self, *args, **kwargs):
134 arg = self.get_argument(*args, **kwargs)
135
136 if arg is None or arg == "":
137 return
138
139 try:
140 return int(arg)
141 except ValueError:
a5f94966
MT
142 raise tornado.web.HTTPError(400, "Could not convert integer: %s" % arg)
143
144 def get_argument_float(self, *args, **kwargs):
145 arg = self.get_argument(*args, **kwargs)
146
147 if arg is None or arg == "":
148 return
149
150 try:
151 return float(arg)
152 except ValueError:
153 raise tornado.web.HTTPError(400, "Could not convert float: %s" % arg)
bd2723d4 154
66862195
MT
155 def get_argument_date(self, arg, *args, **kwargs):
156 value = self.get_argument(arg, *args, **kwargs)
157 if value is None:
158 return
159
160 try:
161 return dateutil.parser.parse(value)
162 except ValueError:
163 raise tornado.web.HTTPError(400)
164
5cc10421
MT
165 def get_file(self, name):
166 try:
167 file = self.request.files[name][0]
168
169 return file["filename"], file["body"], file["content_type"]
170 except KeyError:
171 return None
172
66862195
MT
173 # Login stuff
174
175 def get_current_user(self):
176 session_id = self.get_cookie("session_id")
177 if not session_id:
178 return
179
180 # Get account from the session object
181 account = self.backend.accounts.get_by_session(session_id, self.request.host)
182
183 # If the account was not found or the session was not valid
184 # any more, we will remove the cookie.
185 if not account:
186 self.clear_cookie("session_id")
187
188 return account
189
a6dc0bad
MT
190 @property
191 def backend(self):
192 return self.application.backend
193
9068dba1
MT
194 @property
195 def db(self):
196 return self.backend.db
197
940227cb
MT
198 @property
199 def accounts(self):
a6dc0bad 200 return self.backend.accounts
940227cb
MT
201
202 @property
9068dba1
MT
203 def downloads(self):
204 return self.backend.downloads
205
66862195
MT
206 @property
207 def fireinfo(self):
208 return self.backend.fireinfo
209
9068dba1
MT
210 @property
211 def iuse(self):
212 return self.backend.iuse
940227cb 213
e28b082e
MT
214 @property
215 def memcached(self):
216 return self.backend.memcache
217
940227cb
MT
218 @property
219 def mirrors(self):
9068dba1
MT
220 return self.backend.mirrors
221
222 @property
223 def netboot(self):
224 return self.backend.netboot
940227cb 225
940227cb
MT
226 @property
227 def releases(self):
9068dba1 228 return self.backend.releases
940227cb 229
940227cb 230 @property
66862195
MT
231 def talk(self):
232 return self.backend.talk
940227cb 233
66862195 234
689effd0
MT
235class APIHandler(BaseHandler):
236 def check_xsrf_cookie(self):
237 """
238 Do nothing here, because we cannot verify the XSRF token
239 """
240 pass
241
242
3403dc5e
MT
243class NotFoundHandler(BaseHandler):
244 def prepare(self):
245 # Raises 404 as soon as it is called
246 raise tornado.web.HTTPError(404)
247
3403dc5e 248
37ed7c3c
MT
249class ErrorHandler(BaseHandler):
250 """
251 Raises any error we want
252 """
253 def get(self, code):
254 try:
255 code = int(code)
256 except:
257 raise tornado.web.HTTPError(400)
258
259 raise tornado.web.HTTPError(code)
baa693a3
MT
260
261
262class BlockedHandler(BaseHandler):
263 def get(self):
264 # 403 - Forbidden
265 self.set_status(403)
266
267 self.render("static/blocked.html", address=self.get_remote_ip())