Show More
@@ -0,0 +1,34 b'' | |||
|
1 | import logging | |
|
2 | import datetime | |
|
3 | ||
|
4 | from sqlalchemy import * | |
|
5 | from sqlalchemy.exc import DatabaseError | |
|
6 | from sqlalchemy.orm import relation, backref, class_mapper, joinedload | |
|
7 | from sqlalchemy.orm.session import Session | |
|
8 | from sqlalchemy.ext.declarative import declarative_base | |
|
9 | ||
|
10 | from rhodecode.lib.dbmigrate.migrate import * | |
|
11 | from rhodecode.lib.dbmigrate.migrate.changeset import * | |
|
12 | ||
|
13 | from rhodecode.model.meta import Base | |
|
14 | from rhodecode.model import meta | |
|
15 | ||
|
16 | log = logging.getLogger(__name__) | |
|
17 | ||
|
18 | ||
|
19 | def upgrade(migrate_engine): | |
|
20 | """ | |
|
21 | Upgrade operations go here. | |
|
22 | Don't create your own engine; bind migrate_engine to your metadata | |
|
23 | """ | |
|
24 | #========================================================================== | |
|
25 | # USER LOGS | |
|
26 | #========================================================================== | |
|
27 | from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserIpMap | |
|
28 | tbl = UserIpMap.__table__ | |
|
29 | tbl.create() | |
|
30 | ||
|
31 | ||
|
32 | def downgrade(migrate_engine): | |
|
33 | meta = MetaData() | |
|
34 | meta.bind = migrate_engine |
This diff has been collapsed as it changes many lines, (1901 lines changed) Show them Hide them | |||
@@ -0,0 +1,1901 b'' | |||
|
1 | # Copyright 2007 Google Inc. | |
|
2 | # Licensed to PSF under a Contributor Agreement. | |
|
3 | # | |
|
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
|
5 | # you may not use this file except in compliance with the License. | |
|
6 | # You may obtain a copy of the License at | |
|
7 | # | |
|
8 | # http://www.apache.org/licenses/LICENSE-2.0 | |
|
9 | # | |
|
10 | # Unless required by applicable law or agreed to in writing, software | |
|
11 | # distributed under the License is distributed on an "AS IS" BASIS, | |
|
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
|
13 | # implied. See the License for the specific language governing | |
|
14 | # permissions and limitations under the License. | |
|
15 | ||
|
16 | """A fast, lightweight IPv4/IPv6 manipulation library in Python. | |
|
17 | ||
|
18 | This library is used to create/poke/manipulate IPv4 and IPv6 addresses | |
|
19 | and networks. | |
|
20 | ||
|
21 | """ | |
|
22 | ||
|
23 | __version__ = 'trunk' | |
|
24 | ||
|
25 | import struct | |
|
26 | ||
|
27 | IPV4LENGTH = 32 | |
|
28 | IPV6LENGTH = 128 | |
|
29 | ||
|
30 | ||
|
31 | class AddressValueError(ValueError): | |
|
32 | """A Value Error related to the address.""" | |
|
33 | ||
|
34 | ||
|
35 | class NetmaskValueError(ValueError): | |
|
36 | """A Value Error related to the netmask.""" | |
|
37 | ||
|
38 | ||
|
39 | def IPAddress(address, version=None): | |
|
40 | """Take an IP string/int and return an object of the correct type. | |
|
41 | ||
|
42 | Args: | |
|
43 | address: A string or integer, the IP address. Either IPv4 or | |
|
44 | IPv6 addresses may be supplied; integers less than 2**32 will | |
|
45 | be considered to be IPv4 by default. | |
|
46 | version: An Integer, 4 or 6. If set, don't try to automatically | |
|
47 | determine what the IP address type is. important for things | |
|
48 | like IPAddress(1), which could be IPv4, '0.0.0.1', or IPv6, | |
|
49 | '::1'. | |
|
50 | ||
|
51 | Returns: | |
|
52 | An IPv4Address or IPv6Address object. | |
|
53 | ||
|
54 | Raises: | |
|
55 | ValueError: if the string passed isn't either a v4 or a v6 | |
|
56 | address. | |
|
57 | ||
|
58 | """ | |
|
59 | if version: | |
|
60 | if version == 4: | |
|
61 | return IPv4Address(address) | |
|
62 | elif version == 6: | |
|
63 | return IPv6Address(address) | |
|
64 | ||
|
65 | try: | |
|
66 | return IPv4Address(address) | |
|
67 | except (AddressValueError, NetmaskValueError): | |
|
68 | pass | |
|
69 | ||
|
70 | try: | |
|
71 | return IPv6Address(address) | |
|
72 | except (AddressValueError, NetmaskValueError): | |
|
73 | pass | |
|
74 | ||
|
75 | raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % | |
|
76 | address) | |
|
77 | ||
|
78 | ||
|
79 | def IPNetwork(address, version=None, strict=False): | |
|
80 | """Take an IP string/int and return an object of the correct type. | |
|
81 | ||
|
82 | Args: | |
|
83 | address: A string or integer, the IP address. Either IPv4 or | |
|
84 | IPv6 addresses may be supplied; integers less than 2**32 will | |
|
85 | be considered to be IPv4 by default. | |
|
86 | version: An Integer, if set, don't try to automatically | |
|
87 | determine what the IP address type is. important for things | |
|
88 | like IPNetwork(1), which could be IPv4, '0.0.0.1/32', or IPv6, | |
|
89 | '::1/128'. | |
|
90 | ||
|
91 | Returns: | |
|
92 | An IPv4Network or IPv6Network object. | |
|
93 | ||
|
94 | Raises: | |
|
95 | ValueError: if the string passed isn't either a v4 or a v6 | |
|
96 | address. Or if a strict network was requested and a strict | |
|
97 | network wasn't given. | |
|
98 | ||
|
99 | """ | |
|
100 | if version: | |
|
101 | if version == 4: | |
|
102 | return IPv4Network(address, strict) | |
|
103 | elif version == 6: | |
|
104 | return IPv6Network(address, strict) | |
|
105 | ||
|
106 | try: | |
|
107 | return IPv4Network(address, strict) | |
|
108 | except (AddressValueError, NetmaskValueError): | |
|
109 | pass | |
|
110 | ||
|
111 | try: | |
|
112 | return IPv6Network(address, strict) | |
|
113 | except (AddressValueError, NetmaskValueError): | |
|
114 | pass | |
|
115 | ||
|
116 | raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % | |
|
117 | address) | |
|
118 | ||
|
119 | ||
|
120 | def v4_int_to_packed(address): | |
|
121 | """The binary representation of this address. | |
|
122 | ||
|
123 | Args: | |
|
124 | address: An integer representation of an IPv4 IP address. | |
|
125 | ||
|
126 | Returns: | |
|
127 | The binary representation of this address. | |
|
128 | ||
|
129 | Raises: | |
|
130 | ValueError: If the integer is too large to be an IPv4 IP | |
|
131 | address. | |
|
132 | """ | |
|
133 | if address > _BaseV4._ALL_ONES: | |
|
134 | raise ValueError('Address too large for IPv4') | |
|
135 | return Bytes(struct.pack('!I', address)) | |
|
136 | ||
|
137 | ||
|
138 | def v6_int_to_packed(address): | |
|
139 | """The binary representation of this address. | |
|
140 | ||
|
141 | Args: | |
|
142 | address: An integer representation of an IPv6 IP address. | |
|
143 | ||
|
144 | Returns: | |
|
145 | The binary representation of this address. | |
|
146 | """ | |
|
147 | return Bytes(struct.pack('!QQ', address >> 64, address & (2 ** 64 - 1))) | |
|
148 | ||
|
149 | ||
|
150 | def _find_address_range(addresses): | |
|
151 | """Find a sequence of addresses. | |
|
152 | ||
|
153 | Args: | |
|
154 | addresses: a list of IPv4 or IPv6 addresses. | |
|
155 | ||
|
156 | Returns: | |
|
157 | A tuple containing the first and last IP addresses in the sequence. | |
|
158 | ||
|
159 | """ | |
|
160 | first = last = addresses[0] | |
|
161 | for ip in addresses[1:]: | |
|
162 | if ip._ip == last._ip + 1: | |
|
163 | last = ip | |
|
164 | else: | |
|
165 | break | |
|
166 | return (first, last) | |
|
167 | ||
|
168 | ||
|
169 | def _get_prefix_length(number1, number2, bits): | |
|
170 | """Get the number of leading bits that are same for two numbers. | |
|
171 | ||
|
172 | Args: | |
|
173 | number1: an integer. | |
|
174 | number2: another integer. | |
|
175 | bits: the maximum number of bits to compare. | |
|
176 | ||
|
177 | Returns: | |
|
178 | The number of leading bits that are the same for two numbers. | |
|
179 | ||
|
180 | """ | |
|
181 | for i in range(bits): | |
|
182 | if number1 >> i == number2 >> i: | |
|
183 | return bits - i | |
|
184 | return 0 | |
|
185 | ||
|
186 | ||
|
187 | def _count_righthand_zero_bits(number, bits): | |
|
188 | """Count the number of zero bits on the right hand side. | |
|
189 | ||
|
190 | Args: | |
|
191 | number: an integer. | |
|
192 | bits: maximum number of bits to count. | |
|
193 | ||
|
194 | Returns: | |
|
195 | The number of zero bits on the right hand side of the number. | |
|
196 | ||
|
197 | """ | |
|
198 | if number == 0: | |
|
199 | return bits | |
|
200 | for i in range(bits): | |
|
201 | if (number >> i) % 2: | |
|
202 | return i | |
|
203 | ||
|
204 | ||
|
205 | def summarize_address_range(first, last): | |
|
206 | """Summarize a network range given the first and last IP addresses. | |
|
207 | ||
|
208 | Example: | |
|
209 | >>> summarize_address_range(IPv4Address('1.1.1.0'), | |
|
210 | IPv4Address('1.1.1.130')) | |
|
211 | [IPv4Network('1.1.1.0/25'), IPv4Network('1.1.1.128/31'), | |
|
212 | IPv4Network('1.1.1.130/32')] | |
|
213 | ||
|
214 | Args: | |
|
215 | first: the first IPv4Address or IPv6Address in the range. | |
|
216 | last: the last IPv4Address or IPv6Address in the range. | |
|
217 | ||
|
218 | Returns: | |
|
219 | The address range collapsed to a list of IPv4Network's or | |
|
220 | IPv6Network's. | |
|
221 | ||
|
222 | Raise: | |
|
223 | TypeError: | |
|
224 | If the first and last objects are not IP addresses. | |
|
225 | If the first and last objects are not the same version. | |
|
226 | ValueError: | |
|
227 | If the last object is not greater than the first. | |
|
228 | If the version is not 4 or 6. | |
|
229 | ||
|
230 | """ | |
|
231 | if not (isinstance(first, _BaseIP) and isinstance(last, _BaseIP)): | |
|
232 | raise TypeError('first and last must be IP addresses, not networks') | |
|
233 | if first.version != last.version: | |
|
234 | raise TypeError("%s and %s are not of the same version" % ( | |
|
235 | str(first), str(last))) | |
|
236 | if first > last: | |
|
237 | raise ValueError('last IP address must be greater than first') | |
|
238 | ||
|
239 | networks = [] | |
|
240 | ||
|
241 | if first.version == 4: | |
|
242 | ip = IPv4Network | |
|
243 | elif first.version == 6: | |
|
244 | ip = IPv6Network | |
|
245 | else: | |
|
246 | raise ValueError('unknown IP version') | |
|
247 | ||
|
248 | ip_bits = first._max_prefixlen | |
|
249 | first_int = first._ip | |
|
250 | last_int = last._ip | |
|
251 | while first_int <= last_int: | |
|
252 | nbits = _count_righthand_zero_bits(first_int, ip_bits) | |
|
253 | current = None | |
|
254 | while nbits >= 0: | |
|
255 | addend = 2 ** nbits - 1 | |
|
256 | current = first_int + addend | |
|
257 | nbits -= 1 | |
|
258 | if current <= last_int: | |
|
259 | break | |
|
260 | prefix = _get_prefix_length(first_int, current, ip_bits) | |
|
261 | net = ip('%s/%d' % (str(first), prefix)) | |
|
262 | networks.append(net) | |
|
263 | if current == ip._ALL_ONES: | |
|
264 | break | |
|
265 | first_int = current + 1 | |
|
266 | first = IPAddress(first_int, version=first._version) | |
|
267 | return networks | |
|
268 | ||
|
269 | ||
|
270 | def _collapse_address_list_recursive(addresses): | |
|
271 | """Loops through the addresses, collapsing concurrent netblocks. | |
|
272 | ||
|
273 | Example: | |
|
274 | ||
|
275 | ip1 = IPv4Network('1.1.0.0/24') | |
|
276 | ip2 = IPv4Network('1.1.1.0/24') | |
|
277 | ip3 = IPv4Network('1.1.2.0/24') | |
|
278 | ip4 = IPv4Network('1.1.3.0/24') | |
|
279 | ip5 = IPv4Network('1.1.4.0/24') | |
|
280 | ip6 = IPv4Network('1.1.0.1/22') | |
|
281 | ||
|
282 | _collapse_address_list_recursive([ip1, ip2, ip3, ip4, ip5, ip6]) -> | |
|
283 | [IPv4Network('1.1.0.0/22'), IPv4Network('1.1.4.0/24')] | |
|
284 | ||
|
285 | This shouldn't be called directly; it is called via | |
|
286 | collapse_address_list([]). | |
|
287 | ||
|
288 | Args: | |
|
289 | addresses: A list of IPv4Network's or IPv6Network's | |
|
290 | ||
|
291 | Returns: | |
|
292 | A list of IPv4Network's or IPv6Network's depending on what we were | |
|
293 | passed. | |
|
294 | ||
|
295 | """ | |
|
296 | ret_array = [] | |
|
297 | optimized = False | |
|
298 | ||
|
299 | for cur_addr in addresses: | |
|
300 | if not ret_array: | |
|
301 | ret_array.append(cur_addr) | |
|
302 | continue | |
|
303 | if cur_addr in ret_array[-1]: | |
|
304 | optimized = True | |
|
305 | elif cur_addr == ret_array[-1].supernet().subnet()[1]: | |
|
306 | ret_array.append(ret_array.pop().supernet()) | |
|
307 | optimized = True | |
|
308 | else: | |
|
309 | ret_array.append(cur_addr) | |
|
310 | ||
|
311 | if optimized: | |
|
312 | return _collapse_address_list_recursive(ret_array) | |
|
313 | ||
|
314 | return ret_array | |
|
315 | ||
|
316 | ||
|
317 | def collapse_address_list(addresses): | |
|
318 | """Collapse a list of IP objects. | |
|
319 | ||
|
320 | Example: | |
|
321 | collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) -> | |
|
322 | [IPv4('1.1.0.0/23')] | |
|
323 | ||
|
324 | Args: | |
|
325 | addresses: A list of IPv4Network or IPv6Network objects. | |
|
326 | ||
|
327 | Returns: | |
|
328 | A list of IPv4Network or IPv6Network objects depending on what we | |
|
329 | were passed. | |
|
330 | ||
|
331 | Raises: | |
|
332 | TypeError: If passed a list of mixed version objects. | |
|
333 | ||
|
334 | """ | |
|
335 | i = 0 | |
|
336 | addrs = [] | |
|
337 | ips = [] | |
|
338 | nets = [] | |
|
339 | ||
|
340 | # split IP addresses and networks | |
|
341 | for ip in addresses: | |
|
342 | if isinstance(ip, _BaseIP): | |
|
343 | if ips and ips[-1]._version != ip._version: | |
|
344 | raise TypeError("%s and %s are not of the same version" % ( | |
|
345 | str(ip), str(ips[-1]))) | |
|
346 | ips.append(ip) | |
|
347 | elif ip._prefixlen == ip._max_prefixlen: | |
|
348 | if ips and ips[-1]._version != ip._version: | |
|
349 | raise TypeError("%s and %s are not of the same version" % ( | |
|
350 | str(ip), str(ips[-1]))) | |
|
351 | ips.append(ip.ip) | |
|
352 | else: | |
|
353 | if nets and nets[-1]._version != ip._version: | |
|
354 | raise TypeError("%s and %s are not of the same version" % ( | |
|
355 | str(ip), str(nets[-1]))) | |
|
356 | nets.append(ip) | |
|
357 | ||
|
358 | # sort and dedup | |
|
359 | ips = sorted(set(ips)) | |
|
360 | nets = sorted(set(nets)) | |
|
361 | ||
|
362 | while i < len(ips): | |
|
363 | (first, last) = _find_address_range(ips[i:]) | |
|
364 | i = ips.index(last) + 1 | |
|
365 | addrs.extend(summarize_address_range(first, last)) | |
|
366 | ||
|
367 | return _collapse_address_list_recursive(sorted( | |
|
368 | addrs + nets, key=_BaseNet._get_networks_key)) | |
|
369 | ||
|
370 | # backwards compatibility | |
|
371 | CollapseAddrList = collapse_address_list | |
|
372 | ||
|
373 | # We need to distinguish between the string and packed-bytes representations | |
|
374 | # of an IP address. For example, b'0::1' is the IPv4 address 48.58.58.49, | |
|
375 | # while '0::1' is an IPv6 address. | |
|
376 | # | |
|
377 | # In Python 3, the native 'bytes' type already provides this functionality, | |
|
378 | # so we use it directly. For earlier implementations where bytes is not a | |
|
379 | # distinct type, we create a subclass of str to serve as a tag. | |
|
380 | # | |
|
381 | # Usage example (Python 2): | |
|
382 | # ip = ipaddr.IPAddress(ipaddr.Bytes('xxxx')) | |
|
383 | # | |
|
384 | # Usage example (Python 3): | |
|
385 | # ip = ipaddr.IPAddress(b'xxxx') | |
|
386 | try: | |
|
387 | if bytes is str: | |
|
388 | raise TypeError("bytes is not a distinct type") | |
|
389 | Bytes = bytes | |
|
390 | except (NameError, TypeError): | |
|
391 | class Bytes(str): | |
|
392 | def __repr__(self): | |
|
393 | return 'Bytes(%s)' % str.__repr__(self) | |
|
394 | ||
|
395 | ||
|
396 | def get_mixed_type_key(obj): | |
|
397 | """Return a key suitable for sorting between networks and addresses. | |
|
398 | ||
|
399 | Address and Network objects are not sortable by default; they're | |
|
400 | fundamentally different so the expression | |
|
401 | ||
|
402 | IPv4Address('1.1.1.1') <= IPv4Network('1.1.1.1/24') | |
|
403 | ||
|
404 | doesn't make any sense. There are some times however, where you may wish | |
|
405 | to have ipaddr sort these for you anyway. If you need to do this, you | |
|
406 | can use this function as the key= argument to sorted(). | |
|
407 | ||
|
408 | Args: | |
|
409 | obj: either a Network or Address object. | |
|
410 | Returns: | |
|
411 | appropriate key. | |
|
412 | ||
|
413 | """ | |
|
414 | if isinstance(obj, _BaseNet): | |
|
415 | return obj._get_networks_key() | |
|
416 | elif isinstance(obj, _BaseIP): | |
|
417 | return obj._get_address_key() | |
|
418 | return NotImplemented | |
|
419 | ||
|
420 | ||
|
421 | class _IPAddrBase(object): | |
|
422 | ||
|
423 | """The mother class.""" | |
|
424 | ||
|
425 | def __index__(self): | |
|
426 | return self._ip | |
|
427 | ||
|
428 | def __int__(self): | |
|
429 | return self._ip | |
|
430 | ||
|
431 | def __hex__(self): | |
|
432 | return hex(self._ip) | |
|
433 | ||
|
434 | @property | |
|
435 | def exploded(self): | |
|
436 | """Return the longhand version of the IP address as a string.""" | |
|
437 | return self._explode_shorthand_ip_string() | |
|
438 | ||
|
439 | @property | |
|
440 | def compressed(self): | |
|
441 | """Return the shorthand version of the IP address as a string.""" | |
|
442 | return str(self) | |
|
443 | ||
|
444 | ||
|
445 | class _BaseIP(_IPAddrBase): | |
|
446 | ||
|
447 | """A generic IP object. | |
|
448 | ||
|
449 | This IP class contains the version independent methods which are | |
|
450 | used by single IP addresses. | |
|
451 | ||
|
452 | """ | |
|
453 | ||
|
454 | def __eq__(self, other): | |
|
455 | try: | |
|
456 | return (self._ip == other._ip | |
|
457 | and self._version == other._version) | |
|
458 | except AttributeError: | |
|
459 | return NotImplemented | |
|
460 | ||
|
461 | def __ne__(self, other): | |
|
462 | eq = self.__eq__(other) | |
|
463 | if eq is NotImplemented: | |
|
464 | return NotImplemented | |
|
465 | return not eq | |
|
466 | ||
|
467 | def __le__(self, other): | |
|
468 | gt = self.__gt__(other) | |
|
469 | if gt is NotImplemented: | |
|
470 | return NotImplemented | |
|
471 | return not gt | |
|
472 | ||
|
473 | def __ge__(self, other): | |
|
474 | lt = self.__lt__(other) | |
|
475 | if lt is NotImplemented: | |
|
476 | return NotImplemented | |
|
477 | return not lt | |
|
478 | ||
|
479 | def __lt__(self, other): | |
|
480 | if self._version != other._version: | |
|
481 | raise TypeError('%s and %s are not of the same version' % ( | |
|
482 | str(self), str(other))) | |
|
483 | if not isinstance(other, _BaseIP): | |
|
484 | raise TypeError('%s and %s are not of the same type' % ( | |
|
485 | str(self), str(other))) | |
|
486 | if self._ip != other._ip: | |
|
487 | return self._ip < other._ip | |
|
488 | return False | |
|
489 | ||
|
490 | def __gt__(self, other): | |
|
491 | if self._version != other._version: | |
|
492 | raise TypeError('%s and %s are not of the same version' % ( | |
|
493 | str(self), str(other))) | |
|
494 | if not isinstance(other, _BaseIP): | |
|
495 | raise TypeError('%s and %s are not of the same type' % ( | |
|
496 | str(self), str(other))) | |
|
497 | if self._ip != other._ip: | |
|
498 | return self._ip > other._ip | |
|
499 | return False | |
|
500 | ||
|
501 | # Shorthand for Integer addition and subtraction. This is not | |
|
502 | # meant to ever support addition/subtraction of addresses. | |
|
503 | def __add__(self, other): | |
|
504 | if not isinstance(other, int): | |
|
505 | return NotImplemented | |
|
506 | return IPAddress(int(self) + other, version=self._version) | |
|
507 | ||
|
508 | def __sub__(self, other): | |
|
509 | if not isinstance(other, int): | |
|
510 | return NotImplemented | |
|
511 | return IPAddress(int(self) - other, version=self._version) | |
|
512 | ||
|
513 | def __repr__(self): | |
|
514 | return '%s(%r)' % (self.__class__.__name__, str(self)) | |
|
515 | ||
|
516 | def __str__(self): | |
|
517 | return '%s' % self._string_from_ip_int(self._ip) | |
|
518 | ||
|
519 | def __hash__(self): | |
|
520 | return hash(hex(long(self._ip))) | |
|
521 | ||
|
522 | def _get_address_key(self): | |
|
523 | return (self._version, self) | |
|
524 | ||
|
525 | @property | |
|
526 | def version(self): | |
|
527 | raise NotImplementedError('BaseIP has no version') | |
|
528 | ||
|
529 | ||
|
530 | class _BaseNet(_IPAddrBase): | |
|
531 | ||
|
532 | """A generic IP object. | |
|
533 | ||
|
534 | This IP class contains the version independent methods which are | |
|
535 | used by networks. | |
|
536 | ||
|
537 | """ | |
|
538 | ||
|
539 | def __init__(self, address): | |
|
540 | self._cache = {} | |
|
541 | ||
|
542 | def __repr__(self): | |
|
543 | return '%s(%r)' % (self.__class__.__name__, str(self)) | |
|
544 | ||
|
545 | def iterhosts(self): | |
|
546 | """Generate Iterator over usable hosts in a network. | |
|
547 | ||
|
548 | This is like __iter__ except it doesn't return the network | |
|
549 | or broadcast addresses. | |
|
550 | ||
|
551 | """ | |
|
552 | cur = int(self.network) + 1 | |
|
553 | bcast = int(self.broadcast) - 1 | |
|
554 | while cur <= bcast: | |
|
555 | cur += 1 | |
|
556 | yield IPAddress(cur - 1, version=self._version) | |
|
557 | ||
|
558 | def __iter__(self): | |
|
559 | cur = int(self.network) | |
|
560 | bcast = int(self.broadcast) | |
|
561 | while cur <= bcast: | |
|
562 | cur += 1 | |
|
563 | yield IPAddress(cur - 1, version=self._version) | |
|
564 | ||
|
565 | def __getitem__(self, n): | |
|
566 | network = int(self.network) | |
|
567 | broadcast = int(self.broadcast) | |
|
568 | if n >= 0: | |
|
569 | if network + n > broadcast: | |
|
570 | raise IndexError | |
|
571 | return IPAddress(network + n, version=self._version) | |
|
572 | else: | |
|
573 | n += 1 | |
|
574 | if broadcast + n < network: | |
|
575 | raise IndexError | |
|
576 | return IPAddress(broadcast + n, version=self._version) | |
|
577 | ||
|
578 | def __lt__(self, other): | |
|
579 | if self._version != other._version: | |
|
580 | raise TypeError('%s and %s are not of the same version' % ( | |
|
581 | str(self), str(other))) | |
|
582 | if not isinstance(other, _BaseNet): | |
|
583 | raise TypeError('%s and %s are not of the same type' % ( | |
|
584 | str(self), str(other))) | |
|
585 | if self.network != other.network: | |
|
586 | return self.network < other.network | |
|
587 | if self.netmask != other.netmask: | |
|
588 | return self.netmask < other.netmask | |
|
589 | return False | |
|
590 | ||
|
591 | def __gt__(self, other): | |
|
592 | if self._version != other._version: | |
|
593 | raise TypeError('%s and %s are not of the same version' % ( | |
|
594 | str(self), str(other))) | |
|
595 | if not isinstance(other, _BaseNet): | |
|
596 | raise TypeError('%s and %s are not of the same type' % ( | |
|
597 | str(self), str(other))) | |
|
598 | if self.network != other.network: | |
|
599 | return self.network > other.network | |
|
600 | if self.netmask != other.netmask: | |
|
601 | return self.netmask > other.netmask | |
|
602 | return False | |
|
603 | ||
|
604 | def __le__(self, other): | |
|
605 | gt = self.__gt__(other) | |
|
606 | if gt is NotImplemented: | |
|
607 | return NotImplemented | |
|
608 | return not gt | |
|
609 | ||
|
610 | def __ge__(self, other): | |
|
611 | lt = self.__lt__(other) | |
|
612 | if lt is NotImplemented: | |
|
613 | return NotImplemented | |
|
614 | return not lt | |
|
615 | ||
|
616 | def __eq__(self, other): | |
|
617 | try: | |
|
618 | return (self._version == other._version | |
|
619 | and self.network == other.network | |
|
620 | and int(self.netmask) == int(other.netmask)) | |
|
621 | except AttributeError: | |
|
622 | if isinstance(other, _BaseIP): | |
|
623 | return (self._version == other._version | |
|
624 | and self._ip == other._ip) | |
|
625 | ||
|
626 | def __ne__(self, other): | |
|
627 | eq = self.__eq__(other) | |
|
628 | if eq is NotImplemented: | |
|
629 | return NotImplemented | |
|
630 | return not eq | |
|
631 | ||
|
632 | def __str__(self): | |
|
633 | return '%s/%s' % (str(self.ip), | |
|
634 | str(self._prefixlen)) | |
|
635 | ||
|
636 | def __hash__(self): | |
|
637 | return hash(int(self.network) ^ int(self.netmask)) | |
|
638 | ||
|
639 | def __contains__(self, other): | |
|
640 | # always false if one is v4 and the other is v6. | |
|
641 | if self._version != other._version: | |
|
642 | return False | |
|
643 | # dealing with another network. | |
|
644 | if isinstance(other, _BaseNet): | |
|
645 | return (self.network <= other.network and | |
|
646 | self.broadcast >= other.broadcast) | |
|
647 | # dealing with another address | |
|
648 | else: | |
|
649 | return (int(self.network) <= int(other._ip) <= | |
|
650 | int(self.broadcast)) | |
|
651 | ||
|
652 | def overlaps(self, other): | |
|
653 | """Tell if self is partly contained in other.""" | |
|
654 | return self.network in other or self.broadcast in other or ( | |
|
655 | other.network in self or other.broadcast in self) | |
|
656 | ||
|
657 | @property | |
|
658 | def network(self): | |
|
659 | x = self._cache.get('network') | |
|
660 | if x is None: | |
|
661 | x = IPAddress(self._ip & int(self.netmask), version=self._version) | |
|
662 | self._cache['network'] = x | |
|
663 | return x | |
|
664 | ||
|
665 | @property | |
|
666 | def broadcast(self): | |
|
667 | x = self._cache.get('broadcast') | |
|
668 | if x is None: | |
|
669 | x = IPAddress(self._ip | int(self.hostmask), version=self._version) | |
|
670 | self._cache['broadcast'] = x | |
|
671 | return x | |
|
672 | ||
|
673 | @property | |
|
674 | def hostmask(self): | |
|
675 | x = self._cache.get('hostmask') | |
|
676 | if x is None: | |
|
677 | x = IPAddress(int(self.netmask) ^ self._ALL_ONES, | |
|
678 | version=self._version) | |
|
679 | self._cache['hostmask'] = x | |
|
680 | return x | |
|
681 | ||
|
682 | @property | |
|
683 | def with_prefixlen(self): | |
|
684 | return '%s/%d' % (str(self.ip), self._prefixlen) | |
|
685 | ||
|
686 | @property | |
|
687 | def with_netmask(self): | |
|
688 | return '%s/%s' % (str(self.ip), str(self.netmask)) | |
|
689 | ||
|
690 | @property | |
|
691 | def with_hostmask(self): | |
|
692 | return '%s/%s' % (str(self.ip), str(self.hostmask)) | |
|
693 | ||
|
694 | @property | |
|
695 | def numhosts(self): | |
|
696 | """Number of hosts in the current subnet.""" | |
|
697 | return int(self.broadcast) - int(self.network) + 1 | |
|
698 | ||
|
699 | @property | |
|
700 | def version(self): | |
|
701 | raise NotImplementedError('BaseNet has no version') | |
|
702 | ||
|
703 | @property | |
|
704 | def prefixlen(self): | |
|
705 | return self._prefixlen | |
|
706 | ||
|
707 | def address_exclude(self, other): | |
|
708 | """Remove an address from a larger block. | |
|
709 | ||
|
710 | For example: | |
|
711 | ||
|
712 | addr1 = IPNetwork('10.1.1.0/24') | |
|
713 | addr2 = IPNetwork('10.1.1.0/26') | |
|
714 | addr1.address_exclude(addr2) = | |
|
715 | [IPNetwork('10.1.1.64/26'), IPNetwork('10.1.1.128/25')] | |
|
716 | ||
|
717 | or IPv6: | |
|
718 | ||
|
719 | addr1 = IPNetwork('::1/32') | |
|
720 | addr2 = IPNetwork('::1/128') | |
|
721 | addr1.address_exclude(addr2) = [IPNetwork('::0/128'), | |
|
722 | IPNetwork('::2/127'), | |
|
723 | IPNetwork('::4/126'), | |
|
724 | IPNetwork('::8/125'), | |
|
725 | ... | |
|
726 | IPNetwork('0:0:8000::/33')] | |
|
727 | ||
|
728 | Args: | |
|
729 | other: An IPvXNetwork object of the same type. | |
|
730 | ||
|
731 | Returns: | |
|
732 | A sorted list of IPvXNetwork objects addresses which is self | |
|
733 | minus other. | |
|
734 | ||
|
735 | Raises: | |
|
736 | TypeError: If self and other are of difffering address | |
|
737 | versions, or if other is not a network object. | |
|
738 | ValueError: If other is not completely contained by self. | |
|
739 | ||
|
740 | """ | |
|
741 | if not self._version == other._version: | |
|
742 | raise TypeError("%s and %s are not of the same version" % ( | |
|
743 | str(self), str(other))) | |
|
744 | ||
|
745 | if not isinstance(other, _BaseNet): | |
|
746 | raise TypeError("%s is not a network object" % str(other)) | |
|
747 | ||
|
748 | if other not in self: | |
|
749 | raise ValueError('%s not contained in %s' % (str(other), | |
|
750 | str(self))) | |
|
751 | if other == self: | |
|
752 | return [] | |
|
753 | ||
|
754 | ret_addrs = [] | |
|
755 | ||
|
756 | # Make sure we're comparing the network of other. | |
|
757 | other = IPNetwork('%s/%s' % (str(other.network), str(other.prefixlen)), | |
|
758 | version=other._version) | |
|
759 | ||
|
760 | s1, s2 = self.subnet() | |
|
761 | while s1 != other and s2 != other: | |
|
762 | if other in s1: | |
|
763 | ret_addrs.append(s2) | |
|
764 | s1, s2 = s1.subnet() | |
|
765 | elif other in s2: | |
|
766 | ret_addrs.append(s1) | |
|
767 | s1, s2 = s2.subnet() | |
|
768 | else: | |
|
769 | # If we got here, there's a bug somewhere. | |
|
770 | assert True == False, ('Error performing exclusion: ' | |
|
771 | 's1: %s s2: %s other: %s' % | |
|
772 | (str(s1), str(s2), str(other))) | |
|
773 | if s1 == other: | |
|
774 | ret_addrs.append(s2) | |
|
775 | elif s2 == other: | |
|
776 | ret_addrs.append(s1) | |
|
777 | else: | |
|
778 | # If we got here, there's a bug somewhere. | |
|
779 | assert True == False, ('Error performing exclusion: ' | |
|
780 | 's1: %s s2: %s other: %s' % | |
|
781 | (str(s1), str(s2), str(other))) | |
|
782 | ||
|
783 | return sorted(ret_addrs, key=_BaseNet._get_networks_key) | |
|
784 | ||
|
785 | def compare_networks(self, other): | |
|
786 | """Compare two IP objects. | |
|
787 | ||
|
788 | This is only concerned about the comparison of the integer | |
|
789 | representation of the network addresses. This means that the | |
|
790 | host bits aren't considered at all in this method. If you want | |
|
791 | to compare host bits, you can easily enough do a | |
|
792 | 'HostA._ip < HostB._ip' | |
|
793 | ||
|
794 | Args: | |
|
795 | other: An IP object. | |
|
796 | ||
|
797 | Returns: | |
|
798 | If the IP versions of self and other are the same, returns: | |
|
799 | ||
|
800 | -1 if self < other: | |
|
801 | eg: IPv4('1.1.1.0/24') < IPv4('1.1.2.0/24') | |
|
802 | IPv6('1080::200C:417A') < IPv6('1080::200B:417B') | |
|
803 | 0 if self == other | |
|
804 | eg: IPv4('1.1.1.1/24') == IPv4('1.1.1.2/24') | |
|
805 | IPv6('1080::200C:417A/96') == IPv6('1080::200C:417B/96') | |
|
806 | 1 if self > other | |
|
807 | eg: IPv4('1.1.1.0/24') > IPv4('1.1.0.0/24') | |
|
808 | IPv6('1080::1:200C:417A/112') > | |
|
809 | IPv6('1080::0:200C:417A/112') | |
|
810 | ||
|
811 | If the IP versions of self and other are different, returns: | |
|
812 | ||
|
813 | -1 if self._version < other._version | |
|
814 | eg: IPv4('10.0.0.1/24') < IPv6('::1/128') | |
|
815 | 1 if self._version > other._version | |
|
816 | eg: IPv6('::1/128') > IPv4('255.255.255.0/24') | |
|
817 | ||
|
818 | """ | |
|
819 | if self._version < other._version: | |
|
820 | return -1 | |
|
821 | if self._version > other._version: | |
|
822 | return 1 | |
|
823 | # self._version == other._version below here: | |
|
824 | if self.network < other.network: | |
|
825 | return -1 | |
|
826 | if self.network > other.network: | |
|
827 | return 1 | |
|
828 | # self.network == other.network below here: | |
|
829 | if self.netmask < other.netmask: | |
|
830 | return -1 | |
|
831 | if self.netmask > other.netmask: | |
|
832 | return 1 | |
|
833 | # self.network == other.network and self.netmask == other.netmask | |
|
834 | return 0 | |
|
835 | ||
|
836 | def _get_networks_key(self): | |
|
837 | """Network-only key function. | |
|
838 | ||
|
839 | Returns an object that identifies this address' network and | |
|
840 | netmask. This function is a suitable "key" argument for sorted() | |
|
841 | and list.sort(). | |
|
842 | ||
|
843 | """ | |
|
844 | return (self._version, self.network, self.netmask) | |
|
845 | ||
|
846 | def _ip_int_from_prefix(self, prefixlen=None): | |
|
847 | """Turn the prefix length netmask into a int for comparison. | |
|
848 | ||
|
849 | Args: | |
|
850 | prefixlen: An integer, the prefix length. | |
|
851 | ||
|
852 | Returns: | |
|
853 | An integer. | |
|
854 | ||
|
855 | """ | |
|
856 | if not prefixlen and prefixlen != 0: | |
|
857 | prefixlen = self._prefixlen | |
|
858 | return self._ALL_ONES ^ (self._ALL_ONES >> prefixlen) | |
|
859 | ||
|
860 | def _prefix_from_ip_int(self, ip_int, mask=32): | |
|
861 | """Return prefix length from the decimal netmask. | |
|
862 | ||
|
863 | Args: | |
|
864 | ip_int: An integer, the IP address. | |
|
865 | mask: The netmask. Defaults to 32. | |
|
866 | ||
|
867 | Returns: | |
|
868 | An integer, the prefix length. | |
|
869 | ||
|
870 | """ | |
|
871 | while mask: | |
|
872 | if ip_int & 1 == 1: | |
|
873 | break | |
|
874 | ip_int >>= 1 | |
|
875 | mask -= 1 | |
|
876 | ||
|
877 | return mask | |
|
878 | ||
|
879 | def _ip_string_from_prefix(self, prefixlen=None): | |
|
880 | """Turn a prefix length into a dotted decimal string. | |
|
881 | ||
|
882 | Args: | |
|
883 | prefixlen: An integer, the netmask prefix length. | |
|
884 | ||
|
885 | Returns: | |
|
886 | A string, the dotted decimal netmask string. | |
|
887 | ||
|
888 | """ | |
|
889 | if not prefixlen: | |
|
890 | prefixlen = self._prefixlen | |
|
891 | return self._string_from_ip_int(self._ip_int_from_prefix(prefixlen)) | |
|
892 | ||
|
893 | def iter_subnets(self, prefixlen_diff=1, new_prefix=None): | |
|
894 | """The subnets which join to make the current subnet. | |
|
895 | ||
|
896 | In the case that self contains only one IP | |
|
897 | (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 | |
|
898 | for IPv6), return a list with just ourself. | |
|
899 | ||
|
900 | Args: | |
|
901 | prefixlen_diff: An integer, the amount the prefix length | |
|
902 | should be increased by. This should not be set if | |
|
903 | new_prefix is also set. | |
|
904 | new_prefix: The desired new prefix length. This must be a | |
|
905 | larger number (smaller prefix) than the existing prefix. | |
|
906 | This should not be set if prefixlen_diff is also set. | |
|
907 | ||
|
908 | Returns: | |
|
909 | An iterator of IPv(4|6) objects. | |
|
910 | ||
|
911 | Raises: | |
|
912 | ValueError: The prefixlen_diff is too small or too large. | |
|
913 | OR | |
|
914 | prefixlen_diff and new_prefix are both set or new_prefix | |
|
915 | is a smaller number than the current prefix (smaller | |
|
916 | number means a larger network) | |
|
917 | ||
|
918 | """ | |
|
919 | if self._prefixlen == self._max_prefixlen: | |
|
920 | yield self | |
|
921 | return | |
|
922 | ||
|
923 | if new_prefix is not None: | |
|
924 | if new_prefix < self._prefixlen: | |
|
925 | raise ValueError('new prefix must be longer') | |
|
926 | if prefixlen_diff != 1: | |
|
927 | raise ValueError('cannot set prefixlen_diff and new_prefix') | |
|
928 | prefixlen_diff = new_prefix - self._prefixlen | |
|
929 | ||
|
930 | if prefixlen_diff < 0: | |
|
931 | raise ValueError('prefix length diff must be > 0') | |
|
932 | new_prefixlen = self._prefixlen + prefixlen_diff | |
|
933 | ||
|
934 | if not self._is_valid_netmask(str(new_prefixlen)): | |
|
935 | raise ValueError( | |
|
936 | 'prefix length diff %d is invalid for netblock %s' % ( | |
|
937 | new_prefixlen, str(self))) | |
|
938 | ||
|
939 | first = IPNetwork('%s/%s' % (str(self.network), | |
|
940 | str(self._prefixlen + prefixlen_diff)), | |
|
941 | version=self._version) | |
|
942 | ||
|
943 | yield first | |
|
944 | current = first | |
|
945 | while True: | |
|
946 | broadcast = current.broadcast | |
|
947 | if broadcast == self.broadcast: | |
|
948 | return | |
|
949 | new_addr = IPAddress(int(broadcast) + 1, version=self._version) | |
|
950 | current = IPNetwork('%s/%s' % (str(new_addr), str(new_prefixlen)), | |
|
951 | version=self._version) | |
|
952 | ||
|
953 | yield current | |
|
954 | ||
|
955 | def masked(self): | |
|
956 | """Return the network object with the host bits masked out.""" | |
|
957 | return IPNetwork('%s/%d' % (self.network, self._prefixlen), | |
|
958 | version=self._version) | |
|
959 | ||
|
960 | def subnet(self, prefixlen_diff=1, new_prefix=None): | |
|
961 | """Return a list of subnets, rather than an iterator.""" | |
|
962 | return list(self.iter_subnets(prefixlen_diff, new_prefix)) | |
|
963 | ||
|
964 | def supernet(self, prefixlen_diff=1, new_prefix=None): | |
|
965 | """The supernet containing the current network. | |
|
966 | ||
|
967 | Args: | |
|
968 | prefixlen_diff: An integer, the amount the prefix length of | |
|
969 | the network should be decreased by. For example, given a | |
|
970 | /24 network and a prefixlen_diff of 3, a supernet with a | |
|
971 | /21 netmask is returned. | |
|
972 | ||
|
973 | Returns: | |
|
974 | An IPv4 network object. | |
|
975 | ||
|
976 | Raises: | |
|
977 | ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have a | |
|
978 | negative prefix length. | |
|
979 | OR | |
|
980 | If prefixlen_diff and new_prefix are both set or new_prefix is a | |
|
981 | larger number than the current prefix (larger number means a | |
|
982 | smaller network) | |
|
983 | ||
|
984 | """ | |
|
985 | if self._prefixlen == 0: | |
|
986 | return self | |
|
987 | ||
|
988 | if new_prefix is not None: | |
|
989 | if new_prefix > self._prefixlen: | |
|
990 | raise ValueError('new prefix must be shorter') | |
|
991 | if prefixlen_diff != 1: | |
|
992 | raise ValueError('cannot set prefixlen_diff and new_prefix') | |
|
993 | prefixlen_diff = self._prefixlen - new_prefix | |
|
994 | ||
|
995 | if self.prefixlen - prefixlen_diff < 0: | |
|
996 | raise ValueError( | |
|
997 | 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % | |
|
998 | (self.prefixlen, prefixlen_diff)) | |
|
999 | return IPNetwork('%s/%s' % (str(self.network), | |
|
1000 | str(self.prefixlen - prefixlen_diff)), | |
|
1001 | version=self._version) | |
|
1002 | ||
|
1003 | # backwards compatibility | |
|
1004 | Subnet = subnet | |
|
1005 | Supernet = supernet | |
|
1006 | AddressExclude = address_exclude | |
|
1007 | CompareNetworks = compare_networks | |
|
1008 | Contains = __contains__ | |
|
1009 | ||
|
1010 | ||
|
1011 | class _BaseV4(object): | |
|
1012 | ||
|
1013 | """Base IPv4 object. | |
|
1014 | ||
|
1015 | The following methods are used by IPv4 objects in both single IP | |
|
1016 | addresses and networks. | |
|
1017 | ||
|
1018 | """ | |
|
1019 | ||
|
1020 | # Equivalent to 255.255.255.255 or 32 bits of 1's. | |
|
1021 | _ALL_ONES = (2 ** IPV4LENGTH) - 1 | |
|
1022 | _DECIMAL_DIGITS = frozenset('0123456789') | |
|
1023 | ||
|
1024 | def __init__(self, address): | |
|
1025 | self._version = 4 | |
|
1026 | self._max_prefixlen = IPV4LENGTH | |
|
1027 | ||
|
1028 | def _explode_shorthand_ip_string(self): | |
|
1029 | return str(self) | |
|
1030 | ||
|
1031 | def _ip_int_from_string(self, ip_str): | |
|
1032 | """Turn the given IP string into an integer for comparison. | |
|
1033 | ||
|
1034 | Args: | |
|
1035 | ip_str: A string, the IP ip_str. | |
|
1036 | ||
|
1037 | Returns: | |
|
1038 | The IP ip_str as an integer. | |
|
1039 | ||
|
1040 | Raises: | |
|
1041 | AddressValueError: if ip_str isn't a valid IPv4 Address. | |
|
1042 | ||
|
1043 | """ | |
|
1044 | octets = ip_str.split('.') | |
|
1045 | if len(octets) != 4: | |
|
1046 | raise AddressValueError(ip_str) | |
|
1047 | ||
|
1048 | packed_ip = 0 | |
|
1049 | for oc in octets: | |
|
1050 | try: | |
|
1051 | packed_ip = (packed_ip << 8) | self._parse_octet(oc) | |
|
1052 | except ValueError: | |
|
1053 | raise AddressValueError(ip_str) | |
|
1054 | return packed_ip | |
|
1055 | ||
|
1056 | def _parse_octet(self, octet_str): | |
|
1057 | """Convert a decimal octet into an integer. | |
|
1058 | ||
|
1059 | Args: | |
|
1060 | octet_str: A string, the number to parse. | |
|
1061 | ||
|
1062 | Returns: | |
|
1063 | The octet as an integer. | |
|
1064 | ||
|
1065 | Raises: | |
|
1066 | ValueError: if the octet isn't strictly a decimal from [0..255]. | |
|
1067 | ||
|
1068 | """ | |
|
1069 | # Whitelist the characters, since int() allows a lot of bizarre stuff. | |
|
1070 | if not self._DECIMAL_DIGITS.issuperset(octet_str): | |
|
1071 | raise ValueError | |
|
1072 | octet_int = int(octet_str, 10) | |
|
1073 | # Disallow leading zeroes, because no clear standard exists on | |
|
1074 | # whether these should be interpreted as decimal or octal. | |
|
1075 | if octet_int > 255 or (octet_str[0] == '0' and len(octet_str) > 1): | |
|
1076 | raise ValueError | |
|
1077 | return octet_int | |
|
1078 | ||
|
1079 | def _string_from_ip_int(self, ip_int): | |
|
1080 | """Turns a 32-bit integer into dotted decimal notation. | |
|
1081 | ||
|
1082 | Args: | |
|
1083 | ip_int: An integer, the IP address. | |
|
1084 | ||
|
1085 | Returns: | |
|
1086 | The IP address as a string in dotted decimal notation. | |
|
1087 | ||
|
1088 | """ | |
|
1089 | octets = [] | |
|
1090 | for _ in xrange(4): | |
|
1091 | octets.insert(0, str(ip_int & 0xFF)) | |
|
1092 | ip_int >>= 8 | |
|
1093 | return '.'.join(octets) | |
|
1094 | ||
|
1095 | @property | |
|
1096 | def max_prefixlen(self): | |
|
1097 | return self._max_prefixlen | |
|
1098 | ||
|
1099 | @property | |
|
1100 | def packed(self): | |
|
1101 | """The binary representation of this address.""" | |
|
1102 | return v4_int_to_packed(self._ip) | |
|
1103 | ||
|
1104 | @property | |
|
1105 | def version(self): | |
|
1106 | return self._version | |
|
1107 | ||
|
1108 | @property | |
|
1109 | def is_reserved(self): | |
|
1110 | """Test if the address is otherwise IETF reserved. | |
|
1111 | ||
|
1112 | Returns: | |
|
1113 | A boolean, True if the address is within the | |
|
1114 | reserved IPv4 Network range. | |
|
1115 | ||
|
1116 | """ | |
|
1117 | return self in IPv4Network('240.0.0.0/4') | |
|
1118 | ||
|
1119 | @property | |
|
1120 | def is_private(self): | |
|
1121 | """Test if this address is allocated for private networks. | |
|
1122 | ||
|
1123 | Returns: | |
|
1124 | A boolean, True if the address is reserved per RFC 1918. | |
|
1125 | ||
|
1126 | """ | |
|
1127 | return (self in IPv4Network('10.0.0.0/8') or | |
|
1128 | self in IPv4Network('172.16.0.0/12') or | |
|
1129 | self in IPv4Network('192.168.0.0/16')) | |
|
1130 | ||
|
1131 | @property | |
|
1132 | def is_multicast(self): | |
|
1133 | """Test if the address is reserved for multicast use. | |
|
1134 | ||
|
1135 | Returns: | |
|
1136 | A boolean, True if the address is multicast. | |
|
1137 | See RFC 3171 for details. | |
|
1138 | ||
|
1139 | """ | |
|
1140 | return self in IPv4Network('224.0.0.0/4') | |
|
1141 | ||
|
1142 | @property | |
|
1143 | def is_unspecified(self): | |
|
1144 | """Test if the address is unspecified. | |
|
1145 | ||
|
1146 | Returns: | |
|
1147 | A boolean, True if this is the unspecified address as defined in | |
|
1148 | RFC 5735 3. | |
|
1149 | ||
|
1150 | """ | |
|
1151 | return self in IPv4Network('0.0.0.0') | |
|
1152 | ||
|
1153 | @property | |
|
1154 | def is_loopback(self): | |
|
1155 | """Test if the address is a loopback address. | |
|
1156 | ||
|
1157 | Returns: | |
|
1158 | A boolean, True if the address is a loopback per RFC 3330. | |
|
1159 | ||
|
1160 | """ | |
|
1161 | return self in IPv4Network('127.0.0.0/8') | |
|
1162 | ||
|
1163 | @property | |
|
1164 | def is_link_local(self): | |
|
1165 | """Test if the address is reserved for link-local. | |
|
1166 | ||
|
1167 | Returns: | |
|
1168 | A boolean, True if the address is link-local per RFC 3927. | |
|
1169 | ||
|
1170 | """ | |
|
1171 | return self in IPv4Network('169.254.0.0/16') | |
|
1172 | ||
|
1173 | ||
|
1174 | class IPv4Address(_BaseV4, _BaseIP): | |
|
1175 | ||
|
1176 | """Represent and manipulate single IPv4 Addresses.""" | |
|
1177 | ||
|
1178 | def __init__(self, address): | |
|
1179 | ||
|
1180 | """ | |
|
1181 | Args: | |
|
1182 | address: A string or integer representing the IP | |
|
1183 | '192.168.1.1' | |
|
1184 | ||
|
1185 | Additionally, an integer can be passed, so | |
|
1186 | IPv4Address('192.168.1.1') == IPv4Address(3232235777). | |
|
1187 | or, more generally | |
|
1188 | IPv4Address(int(IPv4Address('192.168.1.1'))) == | |
|
1189 | IPv4Address('192.168.1.1') | |
|
1190 | ||
|
1191 | Raises: | |
|
1192 | AddressValueError: If ipaddr isn't a valid IPv4 address. | |
|
1193 | ||
|
1194 | """ | |
|
1195 | _BaseV4.__init__(self, address) | |
|
1196 | ||
|
1197 | # Efficient constructor from integer. | |
|
1198 | if isinstance(address, (int, long)): | |
|
1199 | self._ip = address | |
|
1200 | if address < 0 or address > self._ALL_ONES: | |
|
1201 | raise AddressValueError(address) | |
|
1202 | return | |
|
1203 | ||
|
1204 | # Constructing from a packed address | |
|
1205 | if isinstance(address, Bytes): | |
|
1206 | try: | |
|
1207 | self._ip, = struct.unpack('!I', address) | |
|
1208 | except struct.error: | |
|
1209 | raise AddressValueError(address) # Wrong length. | |
|
1210 | return | |
|
1211 | ||
|
1212 | # Assume input argument to be string or any object representation | |
|
1213 | # which converts into a formatted IP string. | |
|
1214 | addr_str = str(address) | |
|
1215 | self._ip = self._ip_int_from_string(addr_str) | |
|
1216 | ||
|
1217 | ||
|
1218 | class IPv4Network(_BaseV4, _BaseNet): | |
|
1219 | ||
|
1220 | """This class represents and manipulates 32-bit IPv4 networks. | |
|
1221 | ||
|
1222 | Attributes: [examples for IPv4Network('1.2.3.4/27')] | |
|
1223 | ._ip: 16909060 | |
|
1224 | .ip: IPv4Address('1.2.3.4') | |
|
1225 | .network: IPv4Address('1.2.3.0') | |
|
1226 | .hostmask: IPv4Address('0.0.0.31') | |
|
1227 | .broadcast: IPv4Address('1.2.3.31') | |
|
1228 | .netmask: IPv4Address('255.255.255.224') | |
|
1229 | .prefixlen: 27 | |
|
1230 | ||
|
1231 | """ | |
|
1232 | ||
|
1233 | # the valid octets for host and netmasks. only useful for IPv4. | |
|
1234 | _valid_mask_octets = set((255, 254, 252, 248, 240, 224, 192, 128, 0)) | |
|
1235 | ||
|
1236 | def __init__(self, address, strict=False): | |
|
1237 | """Instantiate a new IPv4 network object. | |
|
1238 | ||
|
1239 | Args: | |
|
1240 | address: A string or integer representing the IP [& network]. | |
|
1241 | '192.168.1.1/24' | |
|
1242 | '192.168.1.1/255.255.255.0' | |
|
1243 | '192.168.1.1/0.0.0.255' | |
|
1244 | are all functionally the same in IPv4. Similarly, | |
|
1245 | '192.168.1.1' | |
|
1246 | '192.168.1.1/255.255.255.255' | |
|
1247 | '192.168.1.1/32' | |
|
1248 | are also functionaly equivalent. That is to say, failing to | |
|
1249 | provide a subnetmask will create an object with a mask of /32. | |
|
1250 | ||
|
1251 | If the mask (portion after the / in the argument) is given in | |
|
1252 | dotted quad form, it is treated as a netmask if it starts with a | |
|
1253 | non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it | |
|
1254 | starts with a zero field (e.g. 0.255.255.255 == /8), with the | |
|
1255 | single exception of an all-zero mask which is treated as a | |
|
1256 | netmask == /0. If no mask is given, a default of /32 is used. | |
|
1257 | ||
|
1258 | Additionally, an integer can be passed, so | |
|
1259 | IPv4Network('192.168.1.1') == IPv4Network(3232235777). | |
|
1260 | or, more generally | |
|
1261 | IPv4Network(int(IPv4Network('192.168.1.1'))) == | |
|
1262 | IPv4Network('192.168.1.1') | |
|
1263 | ||
|
1264 | strict: A boolean. If true, ensure that we have been passed | |
|
1265 | A true network address, eg, 192.168.1.0/24 and not an | |
|
1266 | IP address on a network, eg, 192.168.1.1/24. | |
|
1267 | ||
|
1268 | Raises: | |
|
1269 | AddressValueError: If ipaddr isn't a valid IPv4 address. | |
|
1270 | NetmaskValueError: If the netmask isn't valid for | |
|
1271 | an IPv4 address. | |
|
1272 | ValueError: If strict was True and a network address was not | |
|
1273 | supplied. | |
|
1274 | ||
|
1275 | """ | |
|
1276 | _BaseNet.__init__(self, address) | |
|
1277 | _BaseV4.__init__(self, address) | |
|
1278 | ||
|
1279 | # Constructing from an integer or packed bytes. | |
|
1280 | if isinstance(address, (int, long, Bytes)): | |
|
1281 | self.ip = IPv4Address(address) | |
|
1282 | self._ip = self.ip._ip | |
|
1283 | self._prefixlen = self._max_prefixlen | |
|
1284 | self.netmask = IPv4Address(self._ALL_ONES) | |
|
1285 | return | |
|
1286 | ||
|
1287 | # Assume input argument to be string or any object representation | |
|
1288 | # which converts into a formatted IP prefix string. | |
|
1289 | addr = str(address).split('/') | |
|
1290 | ||
|
1291 | if len(addr) > 2: | |
|
1292 | raise AddressValueError(address) | |
|
1293 | ||
|
1294 | self._ip = self._ip_int_from_string(addr[0]) | |
|
1295 | self.ip = IPv4Address(self._ip) | |
|
1296 | ||
|
1297 | if len(addr) == 2: | |
|
1298 | mask = addr[1].split('.') | |
|
1299 | if len(mask) == 4: | |
|
1300 | # We have dotted decimal netmask. | |
|
1301 | if self._is_valid_netmask(addr[1]): | |
|
1302 | self.netmask = IPv4Address(self._ip_int_from_string( | |
|
1303 | addr[1])) | |
|
1304 | elif self._is_hostmask(addr[1]): | |
|
1305 | self.netmask = IPv4Address( | |
|
1306 | self._ip_int_from_string(addr[1]) ^ self._ALL_ONES) | |
|
1307 | else: | |
|
1308 | raise NetmaskValueError('%s is not a valid netmask' | |
|
1309 | % addr[1]) | |
|
1310 | ||
|
1311 | self._prefixlen = self._prefix_from_ip_int(int(self.netmask)) | |
|
1312 | else: | |
|
1313 | # We have a netmask in prefix length form. | |
|
1314 | if not self._is_valid_netmask(addr[1]): | |
|
1315 | raise NetmaskValueError(addr[1]) | |
|
1316 | self._prefixlen = int(addr[1]) | |
|
1317 | self.netmask = IPv4Address(self._ip_int_from_prefix( | |
|
1318 | self._prefixlen)) | |
|
1319 | else: | |
|
1320 | self._prefixlen = self._max_prefixlen | |
|
1321 | self.netmask = IPv4Address(self._ip_int_from_prefix( | |
|
1322 | self._prefixlen)) | |
|
1323 | if strict: | |
|
1324 | if self.ip != self.network: | |
|
1325 | raise ValueError('%s has host bits set' % | |
|
1326 | self.ip) | |
|
1327 | if self._prefixlen == (self._max_prefixlen - 1): | |
|
1328 | self.iterhosts = self.__iter__ | |
|
1329 | ||
|
1330 | def _is_hostmask(self, ip_str): | |
|
1331 | """Test if the IP string is a hostmask (rather than a netmask). | |
|
1332 | ||
|
1333 | Args: | |
|
1334 | ip_str: A string, the potential hostmask. | |
|
1335 | ||
|
1336 | Returns: | |
|
1337 | A boolean, True if the IP string is a hostmask. | |
|
1338 | ||
|
1339 | """ | |
|
1340 | bits = ip_str.split('.') | |
|
1341 | try: | |
|
1342 | parts = [int(x) for x in bits if int(x) in self._valid_mask_octets] | |
|
1343 | except ValueError: | |
|
1344 | return False | |
|
1345 | if len(parts) != len(bits): | |
|
1346 | return False | |
|
1347 | if parts[0] < parts[-1]: | |
|
1348 | return True | |
|
1349 | return False | |
|
1350 | ||
|
1351 | def _is_valid_netmask(self, netmask): | |
|
1352 | """Verify that the netmask is valid. | |
|
1353 | ||
|
1354 | Args: | |
|
1355 | netmask: A string, either a prefix or dotted decimal | |
|
1356 | netmask. | |
|
1357 | ||
|
1358 | Returns: | |
|
1359 | A boolean, True if the prefix represents a valid IPv4 | |
|
1360 | netmask. | |
|
1361 | ||
|
1362 | """ | |
|
1363 | mask = netmask.split('.') | |
|
1364 | if len(mask) == 4: | |
|
1365 | if [x for x in mask if int(x) not in self._valid_mask_octets]: | |
|
1366 | return False | |
|
1367 | if [y for idx, y in enumerate(mask) if idx > 0 and | |
|
1368 | y > mask[idx - 1]]: | |
|
1369 | return False | |
|
1370 | return True | |
|
1371 | try: | |
|
1372 | netmask = int(netmask) | |
|
1373 | except ValueError: | |
|
1374 | return False | |
|
1375 | return 0 <= netmask <= self._max_prefixlen | |
|
1376 | ||
|
1377 | # backwards compatibility | |
|
1378 | IsRFC1918 = lambda self: self.is_private | |
|
1379 | IsMulticast = lambda self: self.is_multicast | |
|
1380 | IsLoopback = lambda self: self.is_loopback | |
|
1381 | IsLinkLocal = lambda self: self.is_link_local | |
|
1382 | ||
|
1383 | ||
|
1384 | class _BaseV6(object): | |
|
1385 | ||
|
1386 | """Base IPv6 object. | |
|
1387 | ||
|
1388 | The following methods are used by IPv6 objects in both single IP | |
|
1389 | addresses and networks. | |
|
1390 | ||
|
1391 | """ | |
|
1392 | ||
|
1393 | _ALL_ONES = (2 ** IPV6LENGTH) - 1 | |
|
1394 | _HEXTET_COUNT = 8 | |
|
1395 | _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') | |
|
1396 | ||
|
1397 | def __init__(self, address): | |
|
1398 | self._version = 6 | |
|
1399 | self._max_prefixlen = IPV6LENGTH | |
|
1400 | ||
|
1401 | def _ip_int_from_string(self, ip_str): | |
|
1402 | """Turn an IPv6 ip_str into an integer. | |
|
1403 | ||
|
1404 | Args: | |
|
1405 | ip_str: A string, the IPv6 ip_str. | |
|
1406 | ||
|
1407 | Returns: | |
|
1408 | A long, the IPv6 ip_str. | |
|
1409 | ||
|
1410 | Raises: | |
|
1411 | AddressValueError: if ip_str isn't a valid IPv6 Address. | |
|
1412 | ||
|
1413 | """ | |
|
1414 | parts = ip_str.split(':') | |
|
1415 | ||
|
1416 | # An IPv6 address needs at least 2 colons (3 parts). | |
|
1417 | if len(parts) < 3: | |
|
1418 | raise AddressValueError(ip_str) | |
|
1419 | ||
|
1420 | # If the address has an IPv4-style suffix, convert it to hexadecimal. | |
|
1421 | if '.' in parts[-1]: | |
|
1422 | ipv4_int = IPv4Address(parts.pop())._ip | |
|
1423 | parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) | |
|
1424 | parts.append('%x' % (ipv4_int & 0xFFFF)) | |
|
1425 | ||
|
1426 | # An IPv6 address can't have more than 8 colons (9 parts). | |
|
1427 | if len(parts) > self._HEXTET_COUNT + 1: | |
|
1428 | raise AddressValueError(ip_str) | |
|
1429 | ||
|
1430 | # Disregarding the endpoints, find '::' with nothing in between. | |
|
1431 | # This indicates that a run of zeroes has been skipped. | |
|
1432 | try: | |
|
1433 | skip_index, = ( | |
|
1434 | [i for i in xrange(1, len(parts) - 1) if not parts[i]] or | |
|
1435 | [None]) | |
|
1436 | except ValueError: | |
|
1437 | # Can't have more than one '::' | |
|
1438 | raise AddressValueError(ip_str) | |
|
1439 | ||
|
1440 | # parts_hi is the number of parts to copy from above/before the '::' | |
|
1441 | # parts_lo is the number of parts to copy from below/after the '::' | |
|
1442 | if skip_index is not None: | |
|
1443 | # If we found a '::', then check if it also covers the endpoints. | |
|
1444 | parts_hi = skip_index | |
|
1445 | parts_lo = len(parts) - skip_index - 1 | |
|
1446 | if not parts[0]: | |
|
1447 | parts_hi -= 1 | |
|
1448 | if parts_hi: | |
|
1449 | raise AddressValueError(ip_str) # ^: requires ^:: | |
|
1450 | if not parts[-1]: | |
|
1451 | parts_lo -= 1 | |
|
1452 | if parts_lo: | |
|
1453 | raise AddressValueError(ip_str) # :$ requires ::$ | |
|
1454 | parts_skipped = self._HEXTET_COUNT - (parts_hi + parts_lo) | |
|
1455 | if parts_skipped < 1: | |
|
1456 | raise AddressValueError(ip_str) | |
|
1457 | else: | |
|
1458 | # Otherwise, allocate the entire address to parts_hi. The endpoints | |
|
1459 | # could still be empty, but _parse_hextet() will check for that. | |
|
1460 | if len(parts) != self._HEXTET_COUNT: | |
|
1461 | raise AddressValueError(ip_str) | |
|
1462 | parts_hi = len(parts) | |
|
1463 | parts_lo = 0 | |
|
1464 | parts_skipped = 0 | |
|
1465 | ||
|
1466 | try: | |
|
1467 | # Now, parse the hextets into a 128-bit integer. | |
|
1468 | ip_int = 0L | |
|
1469 | for i in xrange(parts_hi): | |
|
1470 | ip_int <<= 16 | |
|
1471 | ip_int |= self._parse_hextet(parts[i]) | |
|
1472 | ip_int <<= 16 * parts_skipped | |
|
1473 | for i in xrange(-parts_lo, 0): | |
|
1474 | ip_int <<= 16 | |
|
1475 | ip_int |= self._parse_hextet(parts[i]) | |
|
1476 | return ip_int | |
|
1477 | except ValueError: | |
|
1478 | raise AddressValueError(ip_str) | |
|
1479 | ||
|
1480 | def _parse_hextet(self, hextet_str): | |
|
1481 | """Convert an IPv6 hextet string into an integer. | |
|
1482 | ||
|
1483 | Args: | |
|
1484 | hextet_str: A string, the number to parse. | |
|
1485 | ||
|
1486 | Returns: | |
|
1487 | The hextet as an integer. | |
|
1488 | ||
|
1489 | Raises: | |
|
1490 | ValueError: if the input isn't strictly a hex number from [0..FFFF]. | |
|
1491 | ||
|
1492 | """ | |
|
1493 | # Whitelist the characters, since int() allows a lot of bizarre stuff. | |
|
1494 | if not self._HEX_DIGITS.issuperset(hextet_str): | |
|
1495 | raise ValueError | |
|
1496 | if len(hextet_str) > 4: | |
|
1497 | raise ValueError | |
|
1498 | hextet_int = int(hextet_str, 16) | |
|
1499 | if hextet_int > 0xFFFF: | |
|
1500 | raise ValueError | |
|
1501 | return hextet_int | |
|
1502 | ||
|
1503 | def _compress_hextets(self, hextets): | |
|
1504 | """Compresses a list of hextets. | |
|
1505 | ||
|
1506 | Compresses a list of strings, replacing the longest continuous | |
|
1507 | sequence of "0" in the list with "" and adding empty strings at | |
|
1508 | the beginning or at the end of the string such that subsequently | |
|
1509 | calling ":".join(hextets) will produce the compressed version of | |
|
1510 | the IPv6 address. | |
|
1511 | ||
|
1512 | Args: | |
|
1513 | hextets: A list of strings, the hextets to compress. | |
|
1514 | ||
|
1515 | Returns: | |
|
1516 | A list of strings. | |
|
1517 | ||
|
1518 | """ | |
|
1519 | best_doublecolon_start = -1 | |
|
1520 | best_doublecolon_len = 0 | |
|
1521 | doublecolon_start = -1 | |
|
1522 | doublecolon_len = 0 | |
|
1523 | for index in range(len(hextets)): | |
|
1524 | if hextets[index] == '0': | |
|
1525 | doublecolon_len += 1 | |
|
1526 | if doublecolon_start == -1: | |
|
1527 | # Start of a sequence of zeros. | |
|
1528 | doublecolon_start = index | |
|
1529 | if doublecolon_len > best_doublecolon_len: | |
|
1530 | # This is the longest sequence of zeros so far. | |
|
1531 | best_doublecolon_len = doublecolon_len | |
|
1532 | best_doublecolon_start = doublecolon_start | |
|
1533 | else: | |
|
1534 | doublecolon_len = 0 | |
|
1535 | doublecolon_start = -1 | |
|
1536 | ||
|
1537 | if best_doublecolon_len > 1: | |
|
1538 | best_doublecolon_end = (best_doublecolon_start + | |
|
1539 | best_doublecolon_len) | |
|
1540 | # For zeros at the end of the address. | |
|
1541 | if best_doublecolon_end == len(hextets): | |
|
1542 | hextets += [''] | |
|
1543 | hextets[best_doublecolon_start:best_doublecolon_end] = [''] | |
|
1544 | # For zeros at the beginning of the address. | |
|
1545 | if best_doublecolon_start == 0: | |
|
1546 | hextets = [''] + hextets | |
|
1547 | ||
|
1548 | return hextets | |
|
1549 | ||
|
1550 | def _string_from_ip_int(self, ip_int=None): | |
|
1551 | """Turns a 128-bit integer into hexadecimal notation. | |
|
1552 | ||
|
1553 | Args: | |
|
1554 | ip_int: An integer, the IP address. | |
|
1555 | ||
|
1556 | Returns: | |
|
1557 | A string, the hexadecimal representation of the address. | |
|
1558 | ||
|
1559 | Raises: | |
|
1560 | ValueError: The address is bigger than 128 bits of all ones. | |
|
1561 | ||
|
1562 | """ | |
|
1563 | if not ip_int and ip_int != 0: | |
|
1564 | ip_int = int(self._ip) | |
|
1565 | ||
|
1566 | if ip_int > self._ALL_ONES: | |
|
1567 | raise ValueError('IPv6 address is too large') | |
|
1568 | ||
|
1569 | hex_str = '%032x' % ip_int | |
|
1570 | hextets = [] | |
|
1571 | for x in range(0, 32, 4): | |
|
1572 | hextets.append('%x' % int(hex_str[x:x + 4], 16)) | |
|
1573 | ||
|
1574 | hextets = self._compress_hextets(hextets) | |
|
1575 | return ':'.join(hextets) | |
|
1576 | ||
|
1577 | def _explode_shorthand_ip_string(self): | |
|
1578 | """Expand a shortened IPv6 address. | |
|
1579 | ||
|
1580 | Args: | |
|
1581 | ip_str: A string, the IPv6 address. | |
|
1582 | ||
|
1583 | Returns: | |
|
1584 | A string, the expanded IPv6 address. | |
|
1585 | ||
|
1586 | """ | |
|
1587 | if isinstance(self, _BaseNet): | |
|
1588 | ip_str = str(self.ip) | |
|
1589 | else: | |
|
1590 | ip_str = str(self) | |
|
1591 | ||
|
1592 | ip_int = self._ip_int_from_string(ip_str) | |
|
1593 | parts = [] | |
|
1594 | for i in xrange(self._HEXTET_COUNT): | |
|
1595 | parts.append('%04x' % (ip_int & 0xFFFF)) | |
|
1596 | ip_int >>= 16 | |
|
1597 | parts.reverse() | |
|
1598 | if isinstance(self, _BaseNet): | |
|
1599 | return '%s/%d' % (':'.join(parts), self.prefixlen) | |
|
1600 | return ':'.join(parts) | |
|
1601 | ||
|
1602 | @property | |
|
1603 | def max_prefixlen(self): | |
|
1604 | return self._max_prefixlen | |
|
1605 | ||
|
1606 | @property | |
|
1607 | def packed(self): | |
|
1608 | """The binary representation of this address.""" | |
|
1609 | return v6_int_to_packed(self._ip) | |
|
1610 | ||
|
1611 | @property | |
|
1612 | def version(self): | |
|
1613 | return self._version | |
|
1614 | ||
|
1615 | @property | |
|
1616 | def is_multicast(self): | |
|
1617 | """Test if the address is reserved for multicast use. | |
|
1618 | ||
|
1619 | Returns: | |
|
1620 | A boolean, True if the address is a multicast address. | |
|
1621 | See RFC 2373 2.7 for details. | |
|
1622 | ||
|
1623 | """ | |
|
1624 | return self in IPv6Network('ff00::/8') | |
|
1625 | ||
|
1626 | @property | |
|
1627 | def is_reserved(self): | |
|
1628 | """Test if the address is otherwise IETF reserved. | |
|
1629 | ||
|
1630 | Returns: | |
|
1631 | A boolean, True if the address is within one of the | |
|
1632 | reserved IPv6 Network ranges. | |
|
1633 | ||
|
1634 | """ | |
|
1635 | return (self in IPv6Network('::/8') or | |
|
1636 | self in IPv6Network('100::/8') or | |
|
1637 | self in IPv6Network('200::/7') or | |
|
1638 | self in IPv6Network('400::/6') or | |
|
1639 | self in IPv6Network('800::/5') or | |
|
1640 | self in IPv6Network('1000::/4') or | |
|
1641 | self in IPv6Network('4000::/3') or | |
|
1642 | self in IPv6Network('6000::/3') or | |
|
1643 | self in IPv6Network('8000::/3') or | |
|
1644 | self in IPv6Network('A000::/3') or | |
|
1645 | self in IPv6Network('C000::/3') or | |
|
1646 | self in IPv6Network('E000::/4') or | |
|
1647 | self in IPv6Network('F000::/5') or | |
|
1648 | self in IPv6Network('F800::/6') or | |
|
1649 | self in IPv6Network('FE00::/9')) | |
|
1650 | ||
|
1651 | @property | |
|
1652 | def is_unspecified(self): | |
|
1653 | """Test if the address is unspecified. | |
|
1654 | ||
|
1655 | Returns: | |
|
1656 | A boolean, True if this is the unspecified address as defined in | |
|
1657 | RFC 2373 2.5.2. | |
|
1658 | ||
|
1659 | """ | |
|
1660 | return self._ip == 0 and getattr(self, '_prefixlen', 128) == 128 | |
|
1661 | ||
|
1662 | @property | |
|
1663 | def is_loopback(self): | |
|
1664 | """Test if the address is a loopback address. | |
|
1665 | ||
|
1666 | Returns: | |
|
1667 | A boolean, True if the address is a loopback address as defined in | |
|
1668 | RFC 2373 2.5.3. | |
|
1669 | ||
|
1670 | """ | |
|
1671 | return self._ip == 1 and getattr(self, '_prefixlen', 128) == 128 | |
|
1672 | ||
|
1673 | @property | |
|
1674 | def is_link_local(self): | |
|
1675 | """Test if the address is reserved for link-local. | |
|
1676 | ||
|
1677 | Returns: | |
|
1678 | A boolean, True if the address is reserved per RFC 4291. | |
|
1679 | ||
|
1680 | """ | |
|
1681 | return self in IPv6Network('fe80::/10') | |
|
1682 | ||
|
1683 | @property | |
|
1684 | def is_site_local(self): | |
|
1685 | """Test if the address is reserved for site-local. | |
|
1686 | ||
|
1687 | Note that the site-local address space has been deprecated by RFC 3879. | |
|
1688 | Use is_private to test if this address is in the space of unique local | |
|
1689 | addresses as defined by RFC 4193. | |
|
1690 | ||
|
1691 | Returns: | |
|
1692 | A boolean, True if the address is reserved per RFC 3513 2.5.6. | |
|
1693 | ||
|
1694 | """ | |
|
1695 | return self in IPv6Network('fec0::/10') | |
|
1696 | ||
|
1697 | @property | |
|
1698 | def is_private(self): | |
|
1699 | """Test if this address is allocated for private networks. | |
|
1700 | ||
|
1701 | Returns: | |
|
1702 | A boolean, True if the address is reserved per RFC 4193. | |
|
1703 | ||
|
1704 | """ | |
|
1705 | return self in IPv6Network('fc00::/7') | |
|
1706 | ||
|
1707 | @property | |
|
1708 | def ipv4_mapped(self): | |
|
1709 | """Return the IPv4 mapped address. | |
|
1710 | ||
|
1711 | Returns: | |
|
1712 | If the IPv6 address is a v4 mapped address, return the | |
|
1713 | IPv4 mapped address. Return None otherwise. | |
|
1714 | ||
|
1715 | """ | |
|
1716 | if (self._ip >> 32) != 0xFFFF: | |
|
1717 | return None | |
|
1718 | return IPv4Address(self._ip & 0xFFFFFFFF) | |
|
1719 | ||
|
1720 | @property | |
|
1721 | def teredo(self): | |
|
1722 | """Tuple of embedded teredo IPs. | |
|
1723 | ||
|
1724 | Returns: | |
|
1725 | Tuple of the (server, client) IPs or None if the address | |
|
1726 | doesn't appear to be a teredo address (doesn't start with | |
|
1727 | 2001::/32) | |
|
1728 | ||
|
1729 | """ | |
|
1730 | if (self._ip >> 96) != 0x20010000: | |
|
1731 | return None | |
|
1732 | return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), | |
|
1733 | IPv4Address(~self._ip & 0xFFFFFFFF)) | |
|
1734 | ||
|
1735 | @property | |
|
1736 | def sixtofour(self): | |
|
1737 | """Return the IPv4 6to4 embedded address. | |
|
1738 | ||
|
1739 | Returns: | |
|
1740 | The IPv4 6to4-embedded address if present or None if the | |
|
1741 | address doesn't appear to contain a 6to4 embedded address. | |
|
1742 | ||
|
1743 | """ | |
|
1744 | if (self._ip >> 112) != 0x2002: | |
|
1745 | return None | |
|
1746 | return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) | |
|
1747 | ||
|
1748 | ||
|
1749 | class IPv6Address(_BaseV6, _BaseIP): | |
|
1750 | ||
|
1751 | """Represent and manipulate single IPv6 Addresses. | |
|
1752 | """ | |
|
1753 | ||
|
1754 | def __init__(self, address): | |
|
1755 | """Instantiate a new IPv6 address object. | |
|
1756 | ||
|
1757 | Args: | |
|
1758 | address: A string or integer representing the IP | |
|
1759 | ||
|
1760 | Additionally, an integer can be passed, so | |
|
1761 | IPv6Address('2001:4860::') == | |
|
1762 | IPv6Address(42541956101370907050197289607612071936L). | |
|
1763 | or, more generally | |
|
1764 | IPv6Address(IPv6Address('2001:4860::')._ip) == | |
|
1765 | IPv6Address('2001:4860::') | |
|
1766 | ||
|
1767 | Raises: | |
|
1768 | AddressValueError: If address isn't a valid IPv6 address. | |
|
1769 | ||
|
1770 | """ | |
|
1771 | _BaseV6.__init__(self, address) | |
|
1772 | ||
|
1773 | # Efficient constructor from integer. | |
|
1774 | if isinstance(address, (int, long)): | |
|
1775 | self._ip = address | |
|
1776 | if address < 0 or address > self._ALL_ONES: | |
|
1777 | raise AddressValueError(address) | |
|
1778 | return | |
|
1779 | ||
|
1780 | # Constructing from a packed address | |
|
1781 | if isinstance(address, Bytes): | |
|
1782 | try: | |
|
1783 | hi, lo = struct.unpack('!QQ', address) | |
|
1784 | except struct.error: | |
|
1785 | raise AddressValueError(address) # Wrong length. | |
|
1786 | self._ip = (hi << 64) | lo | |
|
1787 | return | |
|
1788 | ||
|
1789 | # Assume input argument to be string or any object representation | |
|
1790 | # which converts into a formatted IP string. | |
|
1791 | addr_str = str(address) | |
|
1792 | if not addr_str: | |
|
1793 | raise AddressValueError('') | |
|
1794 | ||
|
1795 | self._ip = self._ip_int_from_string(addr_str) | |
|
1796 | ||
|
1797 | ||
|
1798 | class IPv6Network(_BaseV6, _BaseNet): | |
|
1799 | ||
|
1800 | """This class represents and manipulates 128-bit IPv6 networks. | |
|
1801 | ||
|
1802 | Attributes: [examples for IPv6('2001:658:22A:CAFE:200::1/64')] | |
|
1803 | .ip: IPv6Address('2001:658:22a:cafe:200::1') | |
|
1804 | .network: IPv6Address('2001:658:22a:cafe::') | |
|
1805 | .hostmask: IPv6Address('::ffff:ffff:ffff:ffff') | |
|
1806 | .broadcast: IPv6Address('2001:658:22a:cafe:ffff:ffff:ffff:ffff') | |
|
1807 | .netmask: IPv6Address('ffff:ffff:ffff:ffff::') | |
|
1808 | .prefixlen: 64 | |
|
1809 | ||
|
1810 | """ | |
|
1811 | ||
|
1812 | def __init__(self, address, strict=False): | |
|
1813 | """Instantiate a new IPv6 Network object. | |
|
1814 | ||
|
1815 | Args: | |
|
1816 | address: A string or integer representing the IPv6 network or the IP | |
|
1817 | and prefix/netmask. | |
|
1818 | '2001:4860::/128' | |
|
1819 | '2001:4860:0000:0000:0000:0000:0000:0000/128' | |
|
1820 | '2001:4860::' | |
|
1821 | are all functionally the same in IPv6. That is to say, | |
|
1822 | failing to provide a subnetmask will create an object with | |
|
1823 | a mask of /128. | |
|
1824 | ||
|
1825 | Additionally, an integer can be passed, so | |
|
1826 | IPv6Network('2001:4860::') == | |
|
1827 | IPv6Network(42541956101370907050197289607612071936L). | |
|
1828 | or, more generally | |
|
1829 | IPv6Network(IPv6Network('2001:4860::')._ip) == | |
|
1830 | IPv6Network('2001:4860::') | |
|
1831 | ||
|
1832 | strict: A boolean. If true, ensure that we have been passed | |
|
1833 | A true network address, eg, 192.168.1.0/24 and not an | |
|
1834 | IP address on a network, eg, 192.168.1.1/24. | |
|
1835 | ||
|
1836 | Raises: | |
|
1837 | AddressValueError: If address isn't a valid IPv6 address. | |
|
1838 | NetmaskValueError: If the netmask isn't valid for | |
|
1839 | an IPv6 address. | |
|
1840 | ValueError: If strict was True and a network address was not | |
|
1841 | supplied. | |
|
1842 | ||
|
1843 | """ | |
|
1844 | _BaseNet.__init__(self, address) | |
|
1845 | _BaseV6.__init__(self, address) | |
|
1846 | ||
|
1847 | # Constructing from an integer or packed bytes. | |
|
1848 | if isinstance(address, (int, long, Bytes)): | |
|
1849 | self.ip = IPv6Address(address) | |
|
1850 | self._ip = self.ip._ip | |
|
1851 | self._prefixlen = self._max_prefixlen | |
|
1852 | self.netmask = IPv6Address(self._ALL_ONES) | |
|
1853 | return | |
|
1854 | ||
|
1855 | # Assume input argument to be string or any object representation | |
|
1856 | # which converts into a formatted IP prefix string. | |
|
1857 | addr = str(address).split('/') | |
|
1858 | ||
|
1859 | if len(addr) > 2: | |
|
1860 | raise AddressValueError(address) | |
|
1861 | ||
|
1862 | self._ip = self._ip_int_from_string(addr[0]) | |
|
1863 | self.ip = IPv6Address(self._ip) | |
|
1864 | ||
|
1865 | if len(addr) == 2: | |
|
1866 | if self._is_valid_netmask(addr[1]): | |
|
1867 | self._prefixlen = int(addr[1]) | |
|
1868 | else: | |
|
1869 | raise NetmaskValueError(addr[1]) | |
|
1870 | else: | |
|
1871 | self._prefixlen = self._max_prefixlen | |
|
1872 | ||
|
1873 | self.netmask = IPv6Address(self._ip_int_from_prefix(self._prefixlen)) | |
|
1874 | ||
|
1875 | if strict: | |
|
1876 | if self.ip != self.network: | |
|
1877 | raise ValueError('%s has host bits set' % | |
|
1878 | self.ip) | |
|
1879 | if self._prefixlen == (self._max_prefixlen - 1): | |
|
1880 | self.iterhosts = self.__iter__ | |
|
1881 | ||
|
1882 | def _is_valid_netmask(self, prefixlen): | |
|
1883 | """Verify that the netmask/prefixlen is valid. | |
|
1884 | ||
|
1885 | Args: | |
|
1886 | prefixlen: A string, the netmask in prefix length format. | |
|
1887 | ||
|
1888 | Returns: | |
|
1889 | A boolean, True if the prefix represents a valid IPv6 | |
|
1890 | netmask. | |
|
1891 | ||
|
1892 | """ | |
|
1893 | try: | |
|
1894 | prefixlen = int(prefixlen) | |
|
1895 | except ValueError: | |
|
1896 | return False | |
|
1897 | return 0 <= prefixlen <= self._max_prefixlen | |
|
1898 | ||
|
1899 | @property | |
|
1900 | def with_netmask(self): | |
|
1901 | return self.with_prefixlen |
@@ -38,7 +38,7 b' except ImportError:' | |||
|
38 | 38 | |
|
39 | 39 | __version__ = ('.'.join((str(each) for each in VERSION[:3])) + |
|
40 | 40 | '.'.join(VERSION[3:])) |
|
41 |
__dbversion__ = |
|
|
41 | __dbversion__ = 10 # defines current db version for migrations | |
|
42 | 42 | __platform__ = platform.system() |
|
43 | 43 | __license__ = 'GPLv3' |
|
44 | 44 | __py_version__ = sys.version_info |
@@ -222,6 +222,10 b' def make_map(config):' | |||
|
222 | 222 | action="add_email", conditions=dict(method=["PUT"])) |
|
223 | 223 | m.connect("user_emails_delete", "/users_emails/{id}", |
|
224 | 224 | action="delete_email", conditions=dict(method=["DELETE"])) |
|
225 | m.connect("user_ips", "/users_ips/{id}", | |
|
226 | action="add_ip", conditions=dict(method=["PUT"])) | |
|
227 | m.connect("user_ips_delete", "/users_ips/{id}", | |
|
228 | action="delete_ip", conditions=dict(method=["DELETE"])) | |
|
225 | 229 | |
|
226 | 230 | #ADMIN USERS GROUPS REST ROUTES |
|
227 | 231 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
@@ -33,11 +33,12 b' from pylons.controllers.util import abor' | |||
|
33 | 33 | from pylons.i18n.translation import _ |
|
34 | 34 | |
|
35 | 35 | from rhodecode.lib import helpers as h |
|
36 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator | |
|
36 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\ | |
|
37 | AuthUser | |
|
37 | 38 | from rhodecode.lib.base import BaseController, render |
|
38 | 39 | from rhodecode.model.forms import DefaultPermissionsForm |
|
39 | 40 | from rhodecode.model.permission import PermissionModel |
|
40 | from rhodecode.model.db import User | |
|
41 | from rhodecode.model.db import User, UserIpMap | |
|
41 | 42 | from rhodecode.model.meta import Session |
|
42 | 43 | |
|
43 | 44 | log = logging.getLogger(__name__) |
@@ -105,10 +106,15 b' class PermissionsController(BaseControll' | |||
|
105 | 106 | # h.form(url('permission', id=ID), |
|
106 | 107 | # method='put') |
|
107 | 108 | # url('permission', id=ID) |
|
108 | ||
|
109 | if id == 'default': | |
|
110 | c.user = default_user = User.get_by_username('default') | |
|
111 | c.perm_user = AuthUser(user_id=default_user.user_id) | |
|
112 | c.user_ip_map = UserIpMap.query()\ | |
|
113 | .filter(UserIpMap.user == default_user).all() | |
|
109 | 114 | permission_model = PermissionModel() |
|
110 | 115 | |
|
111 |
_form = DefaultPermissionsForm( |
|
|
116 | _form = DefaultPermissionsForm( | |
|
117 | [x[0] for x in self.repo_perms_choices], | |
|
112 | 118 |
|
|
113 | 119 |
|
|
114 | 120 |
|
@@ -157,10 +163,11 b' class PermissionsController(BaseControll' | |||
|
157 | 163 | |
|
158 | 164 | #this form can only edit default user permissions |
|
159 | 165 | if id == 'default': |
|
160 | default_user = User.get_by_username('default') | |
|
161 |
defaults = {' |
|
|
162 | 'anonymous': default_user.active} | |
|
163 | ||
|
166 | c.user = default_user = User.get_by_username('default') | |
|
167 | defaults = {'anonymous': default_user.active} | |
|
168 | c.perm_user = AuthUser(user_id=default_user.user_id) | |
|
169 | c.user_ip_map = UserIpMap.query()\ | |
|
170 | .filter(UserIpMap.user == default_user).all() | |
|
164 | 171 | for p in default_user.user_perms: |
|
165 | 172 | if p.permission.permission_name.startswith('repository.'): |
|
166 | 173 | defaults['default_repo_perm'] = p.permission.permission_name |
@@ -181,7 +188,7 b' class PermissionsController(BaseControll' | |||
|
181 | 188 | render('admin/permissions/permissions.html'), |
|
182 | 189 | defaults=defaults, |
|
183 | 190 | encoding="UTF-8", |
|
184 |
force_defaults= |
|
|
191 | force_defaults=False | |
|
185 | 192 | ) |
|
186 | 193 | else: |
|
187 | 194 | return redirect(url('admin_home')) |
@@ -41,7 +41,7 b' from rhodecode.lib.auth import LoginRequ' | |||
|
41 | 41 | AuthUser |
|
42 | 42 | from rhodecode.lib.base import BaseController, render |
|
43 | 43 | |
|
44 | from rhodecode.model.db import User, UserEmailMap | |
|
44 | from rhodecode.model.db import User, UserEmailMap, UserIpMap | |
|
45 | 45 | from rhodecode.model.forms import UserForm |
|
46 | 46 | from rhodecode.model.user import UserModel |
|
47 | 47 | from rhodecode.model.meta import Session |
@@ -159,7 +159,7 b' class UsersController(BaseController):' | |||
|
159 | 159 | user_model = UserModel() |
|
160 | 160 | c.user = user_model.get(id) |
|
161 | 161 | c.ldap_dn = c.user.ldap_dn |
|
162 | c.perm_user = AuthUser(user_id=id) | |
|
162 | c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr) | |
|
163 | 163 | _form = UserForm(edit=True, old_data={'user_id': id, |
|
164 | 164 | 'email': c.user.email})() |
|
165 | 165 | form_result = {} |
@@ -178,6 +178,8 b' class UsersController(BaseController):' | |||
|
178 | 178 | except formencode.Invalid, errors: |
|
179 | 179 | c.user_email_map = UserEmailMap.query()\ |
|
180 | 180 | .filter(UserEmailMap.user == c.user).all() |
|
181 | c.user_ip_map = UserIpMap.query()\ | |
|
182 | .filter(UserIpMap.user == c.user).all() | |
|
181 | 183 | defaults = errors.value |
|
182 | 184 | e = errors.error_dict or {} |
|
183 | 185 | defaults.update({ |
@@ -231,12 +233,14 b' class UsersController(BaseController):' | |||
|
231 | 233 | h.flash(_("You can't edit this user"), category='warning') |
|
232 | 234 | return redirect(url('users')) |
|
233 | 235 | |
|
234 | c.perm_user = AuthUser(user_id=id) | |
|
236 | c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr) | |
|
235 | 237 | c.user.permissions = {} |
|
236 | 238 | c.granted_permissions = UserModel().fill_perms(c.user)\ |
|
237 | 239 | .permissions['global'] |
|
238 | 240 | c.user_email_map = UserEmailMap.query()\ |
|
239 | 241 | .filter(UserEmailMap.user == c.user).all() |
|
242 | c.user_ip_map = UserIpMap.query()\ | |
|
243 | .filter(UserIpMap.user == c.user).all() | |
|
240 | 244 | user_model = UserModel() |
|
241 | 245 | c.ldap_dn = c.user.ldap_dn |
|
242 | 246 | defaults = c.user.get_dict() |
@@ -299,7 +303,6 b' class UsersController(BaseController):' | |||
|
299 | 303 | """POST /user_emails:Add an existing item""" |
|
300 | 304 | # url('user_emails', id=ID, method='put') |
|
301 | 305 | |
|
302 | #TODO: validation and form !!! | |
|
303 | 306 | email = request.POST.get('new_email') |
|
304 | 307 | user_model = UserModel() |
|
305 | 308 | |
@@ -324,3 +327,36 b' class UsersController(BaseController):' | |||
|
324 | 327 | Session().commit() |
|
325 | 328 | h.flash(_("Removed email from user"), category='success') |
|
326 | 329 | return redirect(url('edit_user', id=id)) |
|
330 | ||
|
331 | def add_ip(self, id): | |
|
332 | """POST /user_ips:Add an existing item""" | |
|
333 | # url('user_ips', id=ID, method='put') | |
|
334 | ||
|
335 | ip = request.POST.get('new_ip') | |
|
336 | user_model = UserModel() | |
|
337 | ||
|
338 | try: | |
|
339 | user_model.add_extra_ip(id, ip) | |
|
340 | Session().commit() | |
|
341 | h.flash(_("Added ip %s to user") % ip, category='success') | |
|
342 | except formencode.Invalid, error: | |
|
343 | msg = error.error_dict['ip'] | |
|
344 | h.flash(msg, category='error') | |
|
345 | except Exception: | |
|
346 | log.error(traceback.format_exc()) | |
|
347 | h.flash(_('An error occurred during ip saving'), | |
|
348 | category='error') | |
|
349 | if 'default_user' in request.POST: | |
|
350 | return redirect(url('edit_permission', id='default')) | |
|
351 | return redirect(url('edit_user', id=id)) | |
|
352 | ||
|
353 | def delete_ip(self, id): | |
|
354 | """DELETE /user_ips_delete/id: Delete an existing item""" | |
|
355 | # url('user_ips_delete', id=ID, method='delete') | |
|
356 | user_model = UserModel() | |
|
357 | user_model.delete_extra_ip(id, request.POST.get('del_ip')) | |
|
358 | Session().commit() | |
|
359 | h.flash(_("Removed ip from user"), category='success') | |
|
360 | if 'default_user' in request.POST: | |
|
361 | return redirect(url('edit_permission', id='default')) | |
|
362 | return redirect(url('edit_user', id=id)) |
@@ -43,7 +43,7 b' from webob.exc import HTTPNotFound, HTTP' | |||
|
43 | 43 | HTTPBadRequest, HTTPError |
|
44 | 44 | |
|
45 | 45 | from rhodecode.model.db import User |
|
46 | from rhodecode.lib.auth import AuthUser | |
|
46 | from rhodecode.lib.auth import AuthUser, check_ip_access | |
|
47 | 47 | from rhodecode.lib.base import _get_ip_addr, _get_access_path |
|
48 | 48 | from rhodecode.lib.utils2 import safe_unicode |
|
49 | 49 | |
@@ -99,6 +99,7 b' class JSONRPCController(WSGIController):' | |||
|
99 | 99 | controller and if it exists, dispatch to it. |
|
100 | 100 | """ |
|
101 | 101 | start = time.time() |
|
102 | ip_addr = self._get_ip_addr(environ) | |
|
102 | 103 | self._req_id = None |
|
103 | 104 | if 'CONTENT_LENGTH' not in environ: |
|
104 | 105 | log.debug("No Content-Length") |
@@ -144,7 +145,17 b' class JSONRPCController(WSGIController):' | |||
|
144 | 145 | if u is None: |
|
145 | 146 | return jsonrpc_error(retid=self._req_id, |
|
146 | 147 | message='Invalid API KEY') |
|
147 | auth_u = AuthUser(u.user_id, self._req_api_key) | |
|
148 | #check if we are allowed to use this IP | |
|
149 | allowed_ips = AuthUser.get_allowed_ips(u.user_id) | |
|
150 | if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips) is False: | |
|
151 | log.info('Access for IP:%s forbidden, ' | |
|
152 | 'not in %s' % (ip_addr, allowed_ips)) | |
|
153 | return jsonrpc_error(retid=self._req_id, | |
|
154 | message='request from IP:%s not allowed' % (ip_addr)) | |
|
155 | else: | |
|
156 | log.info('Access for IP:%s allowed' % (ip_addr)) | |
|
157 | ||
|
158 | auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr) | |
|
148 | 159 | except Exception, e: |
|
149 | 160 | return jsonrpc_error(retid=self._req_id, |
|
150 | 161 | message='Invalid API KEY') |
@@ -140,6 +140,9 b' class ApiController(JSONRPCController):' | |||
|
140 | 140 | errors that happens |
|
141 | 141 | |
|
142 | 142 | """ |
|
143 | def _get_ip_addr(self, environ): | |
|
144 | from rhodecode.lib.base import _get_ip_addr | |
|
145 | return _get_ip_addr(environ) | |
|
143 | 146 | |
|
144 | 147 | @HasPermissionAllDecorator('hg.admin') |
|
145 | 148 | def pull(self, apiuser, repoid): |
@@ -45,7 +45,7 b' from rhodecode.lib.auth_ldap import Auth' | |||
|
45 | 45 | |
|
46 | 46 | from rhodecode.model import meta |
|
47 | 47 | from rhodecode.model.user import UserModel |
|
48 | from rhodecode.model.db import Permission, RhodeCodeSetting, User | |
|
48 | from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap | |
|
49 | 49 | |
|
50 | 50 | log = logging.getLogger(__name__) |
|
51 | 51 | |
@@ -313,11 +313,12 b' class AuthUser(object):' | |||
|
313 | 313 | in |
|
314 | 314 | """ |
|
315 | 315 | |
|
316 | def __init__(self, user_id=None, api_key=None, username=None): | |
|
316 | def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None): | |
|
317 | 317 | |
|
318 | 318 | self.user_id = user_id |
|
319 | 319 | self.api_key = None |
|
320 | 320 | self.username = username |
|
321 | self.ip_addr = ip_addr | |
|
321 | 322 | |
|
322 | 323 | self.name = '' |
|
323 | 324 | self.lastname = '' |
@@ -326,6 +327,7 b' class AuthUser(object):' | |||
|
326 | 327 | self.admin = False |
|
327 | 328 | self.inherit_default_permissions = False |
|
328 | 329 | self.permissions = {} |
|
330 | self.allowed_ips = set() | |
|
329 | 331 | self._api_key = api_key |
|
330 | 332 | self.propagate_data() |
|
331 | 333 | self._instance = None |
@@ -375,6 +377,8 b' class AuthUser(object):' | |||
|
375 | 377 | |
|
376 | 378 | log.debug('Auth User is now %s' % self) |
|
377 | 379 | user_model.fill_perms(self) |
|
380 | log.debug('Filling Allowed IPs') | |
|
381 | self.allowed_ips = AuthUser.get_allowed_ips(self.user_id) | |
|
378 | 382 | |
|
379 | 383 | @property |
|
380 | 384 | def is_admin(self): |
@@ -406,6 +410,14 b' class AuthUser(object):' | |||
|
406 | 410 | api_key = cookie_store.get('api_key') |
|
407 | 411 | return AuthUser(user_id, api_key, username) |
|
408 | 412 | |
|
413 | @classmethod | |
|
414 | def get_allowed_ips(cls, user_id): | |
|
415 | _set = set() | |
|
416 | user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id).all() | |
|
417 | for ip in user_ips: | |
|
418 | _set.add(ip.ip_addr) | |
|
419 | return _set or set(['0.0.0.0/0']) | |
|
420 | ||
|
409 | 421 | |
|
410 | 422 | def set_available_permissions(config): |
|
411 | 423 | """ |
@@ -821,3 +833,19 b' class HasPermissionAnyMiddleware(object)' | |||
|
821 | 833 | ) |
|
822 | 834 | ) |
|
823 | 835 | return False |
|
836 | ||
|
837 | ||
|
838 | def check_ip_access(source_ip, allowed_ips=None): | |
|
839 | """ | |
|
840 | Checks if source_ip is a subnet of any of allowed_ips. | |
|
841 | ||
|
842 | :param source_ip: | |
|
843 | :param allowed_ips: list of allowed ips together with mask | |
|
844 | """ | |
|
845 | from rhodecode.lib import ipaddr | |
|
846 | log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips)) | |
|
847 | if isinstance(allowed_ips, (tuple, list, set)): | |
|
848 | for ip in allowed_ips: | |
|
849 | if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip): | |
|
850 | return True | |
|
851 | return False |
@@ -20,7 +20,7 b' from rhodecode import __version__, BACKE' | |||
|
20 | 20 | from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\ |
|
21 | 21 | safe_str, safe_int |
|
22 | 22 | from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\ |
|
23 | HasPermissionAnyMiddleware, CookieStoreWrapper | |
|
23 | HasPermissionAnyMiddleware, CookieStoreWrapper, check_ip_access | |
|
24 | 24 | from rhodecode.lib.utils import get_repo_slug, invalidate_cache |
|
25 | 25 | from rhodecode.model import meta |
|
26 | 26 | |
@@ -101,7 +101,7 b' class BaseVCSController(object):' | |||
|
101 | 101 | #authenticate this mercurial request using authfunc |
|
102 | 102 | self.authenticate = BasicAuth('', authfunc, |
|
103 | 103 | config.get('auth_ret_code')) |
|
104 | self.ipaddr = '0.0.0.0' | |
|
104 | self.ip_addr = '0.0.0.0' | |
|
105 | 105 | |
|
106 | 106 | def _handle_request(self, environ, start_response): |
|
107 | 107 | raise NotImplementedError() |
@@ -136,7 +136,7 b' class BaseVCSController(object):' | |||
|
136 | 136 | """ |
|
137 | 137 | invalidate_cache('get_repo_cached_%s' % repo_name) |
|
138 | 138 | |
|
139 | def _check_permission(self, action, user, repo_name): | |
|
139 | def _check_permission(self, action, user, repo_name, ip_addr=None): | |
|
140 | 140 | """ |
|
141 | 141 | Checks permissions using action (push/pull) user and repository |
|
142 | 142 | name |
@@ -145,6 +145,14 b' class BaseVCSController(object):' | |||
|
145 | 145 | :param user: user instance |
|
146 | 146 | :param repo_name: repository name |
|
147 | 147 | """ |
|
148 | #check IP | |
|
149 | allowed_ips = AuthUser.get_allowed_ips(user.user_id) | |
|
150 | if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips) is False: | |
|
151 | log.info('Access for IP:%s forbidden, ' | |
|
152 | 'not in %s' % (ip_addr, allowed_ips)) | |
|
153 | return False | |
|
154 | else: | |
|
155 | log.info('Access for IP:%s allowed' % (ip_addr)) | |
|
148 | 156 | if action == 'push': |
|
149 | 157 | if not HasPermissionAnyMiddleware('repository.write', |
|
150 | 158 | 'repository.admin')(user, |
@@ -235,6 +243,9 b' class BaseVCSController(object):' | |||
|
235 | 243 | class BaseController(WSGIController): |
|
236 | 244 | |
|
237 | 245 | def __before__(self): |
|
246 | """ | |
|
247 | __before__ is called before controller methods and after __call__ | |
|
248 | """ | |
|
238 | 249 | c.rhodecode_version = __version__ |
|
239 | 250 | c.rhodecode_instanceid = config.get('instance_id') |
|
240 | 251 | c.rhodecode_name = config.get('rhodecode_title') |
@@ -258,7 +269,6 b' class BaseController(WSGIController):' | |||
|
258 | 269 | |
|
259 | 270 | self.sa = meta.Session |
|
260 | 271 | self.scm_model = ScmModel(self.sa) |
|
261 | self.ip_addr = '' | |
|
262 | 272 | |
|
263 | 273 | def __call__(self, environ, start_response): |
|
264 | 274 | """Invoke the Controller""" |
@@ -273,7 +283,7 b' class BaseController(WSGIController):' | |||
|
273 | 283 | cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) |
|
274 | 284 | user_id = cookie_store.get('user_id', None) |
|
275 | 285 | username = get_container_username(environ, config) |
|
276 | auth_user = AuthUser(user_id, api_key, username) | |
|
286 | auth_user = AuthUser(user_id, api_key, username, self.ip_addr) | |
|
277 | 287 | request.user = auth_user |
|
278 | 288 | self.rhodecode_user = c.rhodecode_user = auth_user |
|
279 | 289 | if not self.rhodecode_user.is_authenticated and \ |
@@ -286,6 +286,9 b' class DbManage(object):' | |||
|
286 | 286 | 'Please validate and check default permissions ' |
|
287 | 287 | 'in admin panel') |
|
288 | 288 | |
|
289 | def step_10(self): | |
|
290 | pass | |
|
291 | ||
|
289 | 292 | upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) |
|
290 | 293 | |
|
291 | 294 | # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE |
@@ -1164,3 +1164,10 b' def not_mapped_error(repo_name):' | |||
|
1164 | 1164 | ' it was created or renamed from the filesystem' |
|
1165 | 1165 | ' please run the application again' |
|
1166 | 1166 | ' in order to rescan repositories') % repo_name, category='error') |
|
1167 | ||
|
1168 | ||
|
1169 | def ip_range(ip_addr): | |
|
1170 | from rhodecode.model.db import UserIpMap | |
|
1171 | s, e = UserIpMap._get_ip_range(ip_addr) | |
|
1172 | return '%s - %s' % (s, e) | |
|
1173 |
@@ -109,7 +109,7 b' class SimpleGit(BaseVCSController):' | |||
|
109 | 109 | if not self._check_ssl(environ, start_response): |
|
110 | 110 | return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) |
|
111 | 111 | |
|
112 | ipaddr = self._get_ip_addr(environ) | |
|
112 | ip_addr = self._get_ip_addr(environ) | |
|
113 | 113 | username = None |
|
114 | 114 | self._git_first_op = False |
|
115 | 115 | # skip passing error to error controller |
@@ -140,7 +140,7 b' class SimpleGit(BaseVCSController):' | |||
|
140 | 140 | anonymous_user = self.__get_user('default') |
|
141 | 141 | username = anonymous_user.username |
|
142 | 142 | anonymous_perm = self._check_permission(action, anonymous_user, |
|
143 | repo_name) | |
|
143 | repo_name, ip_addr) | |
|
144 | 144 | |
|
145 | 145 | if anonymous_perm is not True or anonymous_user.active is False: |
|
146 | 146 | if anonymous_perm is not True: |
@@ -182,7 +182,7 b' class SimpleGit(BaseVCSController):' | |||
|
182 | 182 | return HTTPInternalServerError()(environ, start_response) |
|
183 | 183 | |
|
184 | 184 | #check permissions for this repository |
|
185 | perm = self._check_permission(action, user, repo_name) | |
|
185 | perm = self._check_permission(action, user, repo_name, ip_addr) | |
|
186 | 186 | if perm is not True: |
|
187 | 187 | return HTTPForbidden()(environ, start_response) |
|
188 | 188 | |
@@ -191,7 +191,7 b' class SimpleGit(BaseVCSController):' | |||
|
191 | 191 | from rhodecode import CONFIG |
|
192 | 192 | server_url = get_server_url(environ) |
|
193 | 193 | extras = { |
|
194 | 'ip': ipaddr, | |
|
194 | 'ip': ip_addr, | |
|
195 | 195 | 'username': username, |
|
196 | 196 | 'action': action, |
|
197 | 197 | 'repository': repo_name, |
@@ -73,7 +73,7 b' class SimpleHg(BaseVCSController):' | |||
|
73 | 73 | if not self._check_ssl(environ, start_response): |
|
74 | 74 | return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) |
|
75 | 75 | |
|
76 | ipaddr = self._get_ip_addr(environ) | |
|
76 | ip_addr = self._get_ip_addr(environ) | |
|
77 | 77 | username = None |
|
78 | 78 | # skip passing error to error controller |
|
79 | 79 | environ['pylons.status_code_redirect'] = True |
@@ -103,7 +103,7 b' class SimpleHg(BaseVCSController):' | |||
|
103 | 103 | anonymous_user = self.__get_user('default') |
|
104 | 104 | username = anonymous_user.username |
|
105 | 105 | anonymous_perm = self._check_permission(action, anonymous_user, |
|
106 | repo_name) | |
|
106 | repo_name, ip_addr) | |
|
107 | 107 | |
|
108 | 108 | if anonymous_perm is not True or anonymous_user.active is False: |
|
109 | 109 | if anonymous_perm is not True: |
@@ -145,7 +145,7 b' class SimpleHg(BaseVCSController):' | |||
|
145 | 145 | return HTTPInternalServerError()(environ, start_response) |
|
146 | 146 | |
|
147 | 147 | #check permissions for this repository |
|
148 | perm = self._check_permission(action, user, repo_name) | |
|
148 | perm = self._check_permission(action, user, repo_name, ip_addr) | |
|
149 | 149 | if perm is not True: |
|
150 | 150 | return HTTPForbidden()(environ, start_response) |
|
151 | 151 | |
@@ -154,7 +154,7 b' class SimpleHg(BaseVCSController):' | |||
|
154 | 154 | from rhodecode import CONFIG |
|
155 | 155 | server_url = get_server_url(environ) |
|
156 | 156 | extras = { |
|
157 | 'ip': ipaddr, | |
|
157 | 'ip': ip_addr, | |
|
158 | 158 | 'username': username, |
|
159 | 159 | 'action': action, |
|
160 | 160 | 'repository': repo_name, |
@@ -518,6 +518,33 b' class UserEmailMap(Base, BaseModel):' | |||
|
518 | 518 | self._email = val.lower() if val else None |
|
519 | 519 | |
|
520 | 520 | |
|
521 | class UserIpMap(Base, BaseModel): | |
|
522 | __tablename__ = 'user_ip_map' | |
|
523 | __table_args__ = ( | |
|
524 | UniqueConstraint('user_id', 'ip_addr'), | |
|
525 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
|
526 | 'mysql_charset': 'utf8'} | |
|
527 | ) | |
|
528 | __mapper_args__ = {} | |
|
529 | ||
|
530 | ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
|
531 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |
|
532 | ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
|
533 | user = relationship('User', lazy='joined') | |
|
534 | ||
|
535 | @classmethod | |
|
536 | def _get_ip_range(cls, ip_addr): | |
|
537 | from rhodecode.lib import ipaddr | |
|
538 | net = ipaddr.IPv4Network(ip_addr) | |
|
539 | return [str(net.network), str(net.broadcast)] | |
|
540 | ||
|
541 | def __json__(self): | |
|
542 | return dict( | |
|
543 | ip_addr=self.ip_addr, | |
|
544 | ip_range=self._get_ip_range(self.ip_addr) | |
|
545 | ) | |
|
546 | ||
|
547 | ||
|
521 | 548 | class UserLog(Base, BaseModel): |
|
522 | 549 | __tablename__ = 'user_logs' |
|
523 | 550 | __table_args__ = ( |
@@ -637,6 +664,7 b' class Repository(Base, BaseModel):' | |||
|
637 | 664 | landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) |
|
638 | 665 | enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) |
|
639 | 666 | _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) |
|
667 | #changeset_cache = Column("changeset_cache", LargeBinary(), nullable=False) #JSON data | |
|
640 | 668 | |
|
641 | 669 | fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) |
|
642 | 670 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) |
@@ -345,9 +345,14 b' def LdapSettingsForm(tls_reqcert_choices' | |||
|
345 | 345 | |
|
346 | 346 | def UserExtraEmailForm(): |
|
347 | 347 | class _UserExtraEmailForm(formencode.Schema): |
|
348 | email = All(v.UniqSystemEmail(), v.Email) | |
|
348 | email = All(v.UniqSystemEmail(), v.Email(not_empty=True)) | |
|
349 | return _UserExtraEmailForm | |
|
350 | ||
|
349 | 351 | |
|
350 | return _UserExtraEmailForm | |
|
352 | def UserExtraIpForm(): | |
|
353 | class _UserExtraIpForm(formencode.Schema): | |
|
354 | ip = v.ValidIp()(not_empty=True) | |
|
355 | return _UserExtraIpForm | |
|
351 | 356 | |
|
352 | 357 | |
|
353 | 358 | def PullRequestForm(repo_id): |
@@ -40,7 +40,7 b' from rhodecode.model import BaseModel' | |||
|
40 | 40 | from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ |
|
41 | 41 | UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ |
|
42 | 42 | Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \ |
|
43 | UserEmailMap | |
|
43 | UserEmailMap, UserIpMap | |
|
44 | 44 | from rhodecode.lib.exceptions import DefaultUserException, \ |
|
45 | 45 | UserOwnsReposException |
|
46 | 46 | |
@@ -705,3 +705,33 b' class UserModel(BaseModel):' | |||
|
705 | 705 | obj = UserEmailMap.query().get(email_id) |
|
706 | 706 | if obj: |
|
707 | 707 | self.sa.delete(obj) |
|
708 | ||
|
709 | def add_extra_ip(self, user, ip): | |
|
710 | """ | |
|
711 | Adds ip address to UserIpMap | |
|
712 | ||
|
713 | :param user: | |
|
714 | :param ip: | |
|
715 | """ | |
|
716 | from rhodecode.model import forms | |
|
717 | form = forms.UserExtraIpForm()() | |
|
718 | data = form.to_python(dict(ip=ip)) | |
|
719 | user = self._get_user(user) | |
|
720 | ||
|
721 | obj = UserIpMap() | |
|
722 | obj.user = user | |
|
723 | obj.ip_addr = data['ip'] | |
|
724 | self.sa.add(obj) | |
|
725 | return obj | |
|
726 | ||
|
727 | def delete_extra_ip(self, user, ip_id): | |
|
728 | """ | |
|
729 | Removes ip address from UserIpMap | |
|
730 | ||
|
731 | :param user: | |
|
732 | :param ip_id: | |
|
733 | """ | |
|
734 | user = self._get_user(user) | |
|
735 | obj = UserIpMap.query().get(ip_id) | |
|
736 | if obj: | |
|
737 | self.sa.delete(obj) |
@@ -11,7 +11,7 b' from webhelpers.pylonslib.secure_form im' | |||
|
11 | 11 | |
|
12 | 12 | from formencode.validators import ( |
|
13 | 13 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, |
|
14 | NotEmpty | |
|
14 | NotEmpty, IPAddress, CIDR | |
|
15 | 15 | ) |
|
16 | 16 | from rhodecode.lib.compat import OrderedSet |
|
17 | 17 | from rhodecode.lib.utils import repo_name_slug |
@@ -23,7 +23,7 b' from rhodecode.lib.auth import HasReposG' | |||
|
23 | 23 | |
|
24 | 24 | # silence warnings and pylint |
|
25 | 25 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \ |
|
26 | NotEmpty | |
|
26 | NotEmpty, IPAddress, CIDR | |
|
27 | 27 | |
|
28 | 28 | log = logging.getLogger(__name__) |
|
29 | 29 | |
@@ -706,3 +706,40 b' def NotReviewedRevisions(repo_id):' | |||
|
706 | 706 | ) |
|
707 | 707 | |
|
708 | 708 | return _validator |
|
709 | ||
|
710 | ||
|
711 | def ValidIp(): | |
|
712 | class _validator(CIDR): | |
|
713 | messages = dict( | |
|
714 | badFormat=_('Please enter a valid IP address (a.b.c.d)'), | |
|
715 | illegalOctets=_('The octets must be within the range of 0-255' | |
|
716 | ' (not %(octet)r)'), | |
|
717 | illegalBits=_('The network size (bits) must be within the range' | |
|
718 | ' of 0-32 (not %(bits)r)')) | |
|
719 | ||
|
720 | def validate_python(self, value, state): | |
|
721 | try: | |
|
722 | # Split into octets and bits | |
|
723 | if '/' in value: # a.b.c.d/e | |
|
724 | addr, bits = value.split('/') | |
|
725 | else: # a.b.c.d | |
|
726 | addr, bits = value, 32 | |
|
727 | # Use IPAddress validator to validate the IP part | |
|
728 | IPAddress.validate_python(self, addr, state) | |
|
729 | # Bits (netmask) correct? | |
|
730 | if not 0 <= int(bits) <= 32: | |
|
731 | raise formencode.Invalid( | |
|
732 | self.message('illegalBits', state, bits=bits), | |
|
733 | value, state) | |
|
734 | # Splitting faild: wrong syntax | |
|
735 | except ValueError: | |
|
736 | raise formencode.Invalid(self.message('badFormat', state), | |
|
737 | value, state) | |
|
738 | ||
|
739 | def to_python(self, value, state): | |
|
740 | v = super(_validator, self).to_python(value, state) | |
|
741 | #if IP doesn't end with a mask, add /32 | |
|
742 | if '/' not in value: | |
|
743 | v += '/32' | |
|
744 | return v | |
|
745 | return _validator |
@@ -4040,6 +4040,22 b' div#legend_container table td,div#legend' | |||
|
4040 | 4040 | float: left |
|
4041 | 4041 | } |
|
4042 | 4042 | |
|
4043 | .ips_wrap{ | |
|
4044 | padding: 0px 20px; | |
|
4045 | } | |
|
4046 | ||
|
4047 | .ips_wrap .ip_entry{ | |
|
4048 | height: 30px; | |
|
4049 | padding:0px 0px 0px 10px; | |
|
4050 | } | |
|
4051 | .ips_wrap .ip_entry .ip{ | |
|
4052 | float: left | |
|
4053 | } | |
|
4054 | .ips_wrap .ip_entry .ip_action{ | |
|
4055 | float: left | |
|
4056 | } | |
|
4057 | ||
|
4058 | ||
|
4043 | 4059 | /*README STYLE*/ |
|
4044 | 4060 | |
|
4045 | 4061 | div.readme { |
@@ -16,7 +16,7 b'' | |||
|
16 | 16 | </%def> |
|
17 | 17 | |
|
18 | 18 | <%def name="main()"> |
|
19 | <div class="box"> | |
|
19 | <div class="box box-left"> | |
|
20 | 20 | <!-- box / title --> |
|
21 | 21 | <div class="title"> |
|
22 | 22 | ${self.breadcrumbs()} |
@@ -89,7 +89,124 b'' | |||
|
89 | 89 | </div> |
|
90 | 90 | </div> |
|
91 | 91 | <div class="buttons"> |
|
92 |
|
|
|
92 | ${h.submit('save',_('Save'),class_="ui-btn large")} | |
|
93 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |
|
94 | </div> | |
|
95 | </div> | |
|
96 | </div> | |
|
97 | ${h.end_form()} | |
|
98 | </div> | |
|
99 | ||
|
100 | <div style="min-height:780px" class="box box-right"> | |
|
101 | <!-- box / title --> | |
|
102 | <div class="title"> | |
|
103 | <h5>${_('Default User Permissions')}</h5> | |
|
104 | </div> | |
|
105 | ||
|
106 | ## permissions overview | |
|
107 | <div id="perms" class="table"> | |
|
108 | %for section in sorted(c.perm_user.permissions.keys()): | |
|
109 | <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div> | |
|
110 | %if not c.perm_user.permissions[section]: | |
|
111 | <span class="empty_data">${_('Nothing here yet')}</span> | |
|
112 | %else: | |
|
113 | <div id='tbl_list_wrap_${section}' class="yui-skin-sam"> | |
|
114 | <table id="tbl_list_${section}"> | |
|
115 | <thead> | |
|
116 | <tr> | |
|
117 | <th class="left">${_('Name')}</th> | |
|
118 | <th class="left">${_('Permission')}</th> | |
|
119 | <th class="left">${_('Edit Permission')}</th> | |
|
120 | </thead> | |
|
121 | <tbody> | |
|
122 | %for k in c.perm_user.permissions[section]: | |
|
123 | <% | |
|
124 | if section != 'global': | |
|
125 | section_perm = c.perm_user.permissions[section].get(k) | |
|
126 | _perm = section_perm.split('.')[-1] | |
|
127 | else: | |
|
128 | _perm = section_perm = None | |
|
129 | %> | |
|
130 | <tr> | |
|
131 | <td> | |
|
132 | %if section == 'repositories': | |
|
133 | <a href="${h.url('summary_home',repo_name=k)}">${k}</a> | |
|
134 | %elif section == 'repositories_groups': | |
|
135 | <a href="${h.url('repos_group_home',group_name=k)}">${k}</a> | |
|
136 | %else: | |
|
137 | ${h.get_permission_name(k)} | |
|
138 | %endif | |
|
139 | </td> | |
|
140 | <td> | |
|
141 | %if section == 'global': | |
|
142 | ${h.bool2icon(k.split('.')[-1] != 'none')} | |
|
143 | %else: | |
|
144 | <span class="perm_tag ${_perm}">${section_perm}</span> | |
|
145 | %endif | |
|
146 | </td> | |
|
147 | <td> | |
|
148 | %if section == 'repositories': | |
|
149 | <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a> | |
|
150 | %elif section == 'repositories_groups': | |
|
151 | <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a> | |
|
152 | %else: | |
|
153 | -- | |
|
154 | %endif | |
|
155 | </td> | |
|
156 | </tr> | |
|
157 | %endfor | |
|
158 | </tbody> | |
|
159 | </table> | |
|
160 | </div> | |
|
161 | %endif | |
|
162 | %endfor | |
|
163 | </div> | |
|
164 | </div> | |
|
165 | <div class="box box-left" style="clear:left"> | |
|
166 | <!-- box / title --> | |
|
167 | <div class="title"> | |
|
168 | <h5>${_('Allowed IP addresses')}</h5> | |
|
169 | </div> | |
|
170 | ||
|
171 | <div class="ips_wrap"> | |
|
172 | <table class="noborder"> | |
|
173 | %if c.user_ip_map: | |
|
174 | %for ip in c.user_ip_map: | |
|
175 | <tr> | |
|
176 | <td><div class="ip">${ip.ip_addr}</div></td> | |
|
177 | <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td> | |
|
178 | <td> | |
|
179 | ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')} | |
|
180 | ${h.hidden('del_ip',ip.ip_id)} | |
|
181 | ${h.hidden('default_user', 'True')} | |
|
182 | ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, | |
|
183 | class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} | |
|
184 | ${h.end_form()} | |
|
185 | </td> | |
|
186 | </tr> | |
|
187 | %endfor | |
|
188 | %else: | |
|
189 | <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr> | |
|
190 | %endif | |
|
191 | </table> | |
|
192 | </div> | |
|
193 | ||
|
194 | ${h.form(url('user_ips', id=c.user.user_id),method='put')} | |
|
195 | <div class="form"> | |
|
196 | <!-- fields --> | |
|
197 | <div class="fields"> | |
|
198 | <div class="field"> | |
|
199 | <div class="label"> | |
|
200 | <label for="new_ip">${_('New ip address')}:</label> | |
|
201 | </div> | |
|
202 | <div class="input"> | |
|
203 | ${h.hidden('default_user', 'True')} | |
|
204 | ${h.text('new_ip', class_='medium')} | |
|
205 | </div> | |
|
206 | </div> | |
|
207 | <div class="buttons"> | |
|
208 | ${h.submit('save',_('Add'),class_="ui-btn large")} | |
|
209 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |
|
93 | 210 |
|
|
94 | 211 | </div> |
|
95 | 212 | </div> |
@@ -43,7 +43,11 b'' | |||
|
43 | 43 | <label>${_('API key')}</label> ${c.user.api_key} |
|
44 | 44 | </div> |
|
45 | 45 | </div> |
|
46 | ||
|
46 | <div class="field"> | |
|
47 | <div class="label"> | |
|
48 | <label>${_('Your IP')}</label> ${c.perm_user.ip_addr or "?"} | |
|
49 | </div> | |
|
50 | </div> | |
|
47 | 51 | <div class="fields"> |
|
48 | 52 | <div class="field"> |
|
49 | 53 | <div class="label"> |
@@ -271,7 +275,7 b'' | |||
|
271 | 275 | <div class="fields"> |
|
272 | 276 | <div class="field"> |
|
273 | 277 | <div class="label"> |
|
274 | <label for="email">${_('New email address')}:</label> | |
|
278 | <label for="new_email">${_('New email address')}:</label> | |
|
275 | 279 | </div> |
|
276 | 280 | <div class="input"> |
|
277 | 281 | ${h.text('new_email', class_='medium')} |
@@ -285,4 +289,52 b'' | |||
|
285 | 289 | </div> |
|
286 | 290 | ${h.end_form()} |
|
287 | 291 | </div> |
|
292 | <div class="box box-left" style="clear:left"> | |
|
293 | <!-- box / title --> | |
|
294 | <div class="title"> | |
|
295 | <h5>${_('Allowed IP addresses')}</h5> | |
|
296 | </div> | |
|
297 | ||
|
298 | <div class="ips_wrap"> | |
|
299 | <table class="noborder"> | |
|
300 | %if c.user_ip_map: | |
|
301 | %for ip in c.user_ip_map: | |
|
302 | <tr> | |
|
303 | <td><div class="ip">${ip.ip_addr}</div></td> | |
|
304 | <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td> | |
|
305 | <td> | |
|
306 | ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')} | |
|
307 | ${h.hidden('del_ip',ip.ip_id)} | |
|
308 | ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, | |
|
309 | class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} | |
|
310 | ${h.end_form()} | |
|
311 | </td> | |
|
312 | </tr> | |
|
313 | %endfor | |
|
314 | %else: | |
|
315 | <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr> | |
|
316 | %endif | |
|
317 | </table> | |
|
318 | </div> | |
|
319 | ||
|
320 | ${h.form(url('user_ips', id=c.user.user_id),method='put')} | |
|
321 | <div class="form"> | |
|
322 | <!-- fields --> | |
|
323 | <div class="fields"> | |
|
324 | <div class="field"> | |
|
325 | <div class="label"> | |
|
326 | <label for="new_ip">${_('New ip address')}:</label> | |
|
327 | </div> | |
|
328 | <div class="input"> | |
|
329 | ${h.text('new_ip', class_='medium')} | |
|
330 | </div> | |
|
331 | </div> | |
|
332 | <div class="buttons"> | |
|
333 | ${h.submit('save',_('Add'),class_="ui-btn large")} | |
|
334 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |
|
335 | </div> | |
|
336 | </div> | |
|
337 | </div> | |
|
338 | ${h.end_form()} | |
|
339 | </div> | |
|
288 | 340 | </%def> |
General Comments 0
You need to be logged in to leave comments.
Login now