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