]>
Commit | Line | Data |
---|---|---|
940227cb MT |
1 | #!/usr/bin/python |
2 | ||
cc3b928d | 3 | import datetime |
66862195 | 4 | import dateutil.parser |
cfe7d74c | 5 | import functools |
11347e46 | 6 | import http.client |
a69e87a1 | 7 | import ipaddress |
47ed77ed | 8 | import logging |
940227cb MT |
9 | import time |
10 | import tornado.locale | |
11 | import tornado.web | |
12 | ||
f110a9ff | 13 | from ..decorators import * |
a95c2f97 | 14 | from .. import util |
60024cc8 | 15 | |
cfe7d74c MT |
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 | ||
372ef119 MT |
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 | ||
cfe7d74c | 54 | |
940227cb | 55 | class BaseHandler(tornado.web.RequestHandler): |
f6ed3d4d MT |
56 | def set_expires(self, seconds): |
57 | # For HTTP/1.1 | |
b592d7c8 | 58 | self.add_header("Cache-Control", "max-age=%s, must-revalidate" % seconds) |
f6ed3d4d MT |
59 | |
60 | # For HTTP/1.0 | |
61 | expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds) | |
b592d7c8 | 62 | self.add_header("Expires", expires) |
f6ed3d4d | 63 | |
37ed7c3c MT |
64 | def write_error(self, status_code, **kwargs): |
65 | # Translate code into message | |
66 | try: | |
11347e46 | 67 | message = http.client.responses[status_code] |
37ed7c3c MT |
68 | except KeyError: |
69 | message = None | |
70 | ||
71 | self.render("error.html", status_code=status_code, message=message, **kwargs) | |
72 | ||
d3d02231 MT |
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 | ||
4ca1a601 MT |
79 | @property |
80 | def hostname(self): | |
81 | # Remove the development prefix | |
82 | return self.request.host.replace(".dev.", ".") | |
83 | ||
bff08cb0 MT |
84 | def get_template_namespace(self): |
85 | ns = tornado.web.RequestHandler.get_template_namespace(self) | |
86 | ||
911064cf | 87 | now = datetime.date.today() |
cc3b928d | 88 | |
bff08cb0 | 89 | ns.update({ |
6c13ca2d | 90 | "backend" : self.backend, |
5613b94b | 91 | "debug" : self.application.settings.get("debug", False), |
a95c2f97 MT |
92 | "format_size" : util.format_size, |
93 | "format_time" : util.format_time, | |
4ca1a601 | 94 | "hostname" : self.hostname, |
911064cf MT |
95 | "now" : now, |
96 | "year" : now.year, | |
bff08cb0 | 97 | }) |
940227cb | 98 | |
bff08cb0 | 99 | return ns |
940227cb | 100 | |
9068dba1 MT |
101 | def get_remote_ip(self): |
102 | # Fix for clients behind a proxy that sends "X-Forwarded-For". | |
66862195 | 103 | remote_ips = self.request.remote_ip.split(", ") |
494d80e6 | 104 | |
66862195 MT |
105 | for remote_ip in remote_ips: |
106 | try: | |
a69e87a1 | 107 | addr = ipaddress.ip_address(remote_ip) |
66862195 MT |
108 | except ValueError: |
109 | # Skip invalid IP addresses. | |
110 | continue | |
9068dba1 | 111 | |
66862195 MT |
112 | # Check if the given IP address is from a |
113 | # private network. | |
114 | if addr.is_private: | |
115 | continue | |
9068dba1 | 116 | |
66862195 | 117 | return remote_ip |
9068dba1 | 118 | |
494d80e6 MT |
119 | # Return the last IP if nothing else worked |
120 | return remote_ips.pop() | |
121 | ||
cfe7d74c MT |
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 | ||
f110a9ff MT |
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 | ||
9068dba1 MT |
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 | ||
bd2723d4 MT |
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) | |
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 MT |
229 | |
230 | @property | |
9068dba1 MT |
231 | def geoip(self): |
232 | return self.backend.geoip | |
940227cb MT |
233 | |
234 | @property | |
66862195 MT |
235 | def talk(self): |
236 | return self.backend.talk | |
940227cb | 237 | |
66862195 | 238 | |
3403dc5e MT |
239 | class NotFoundHandler(BaseHandler): |
240 | def prepare(self): | |
241 | # Raises 404 as soon as it is called | |
242 | raise tornado.web.HTTPError(404) | |
243 | ||
3403dc5e | 244 | |
37ed7c3c MT |
245 | class ErrorHandler(BaseHandler): |
246 | """ | |
247 | Raises any error we want | |
248 | """ | |
249 | def get(self, code): | |
250 | try: | |
251 | code = int(code) | |
252 | except: | |
253 | raise tornado.web.HTTPError(400) | |
254 | ||
255 | raise tornado.web.HTTPError(code) | |
baa693a3 MT |
256 | |
257 | ||
258 | class BlockedHandler(BaseHandler): | |
259 | def get(self): | |
260 | # 403 - Forbidden | |
261 | self.set_status(403) | |
262 | ||
263 | self.render("static/blocked.html", address=self.get_remote_ip()) |