1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3""" 4Grant roles and logins based on IP address. 5""" 6import six 7from paste.util import ip4 8 9class GrantIPMiddleware(object): 10 11 """ 12 On each request, ``ip_map`` is checked against ``REMOTE_ADDR`` 13 and logins and roles are assigned based on that. 14 15 ``ip_map`` is a map of {ip_mask: (username, roles)}. Either 16 ``username`` or ``roles`` may be None. Roles may also be prefixed 17 with ``-``, like ``'-system'`` meaning that role should be 18 revoked. ``'__remove__'`` for a username will remove the username. 19 20 If ``clobber_username`` is true (default) then any user 21 specification will override the current value of ``REMOTE_USER``. 22 ``'__remove__'`` will always clobber the username. 23 24 ``ip_mask`` is something that `paste.util.ip4:IP4Range 25 <class-paste.util.ip4.IP4Range.html>`_ can parse. Simple IP 26 addresses, IP/mask, ip<->ip ranges, and hostnames are allowed. 27 """ 28 29 def __init__(self, app, ip_map, clobber_username=True): 30 self.app = app 31 self.ip_map = [] 32 for key, value in ip_map.items(): 33 self.ip_map.append((ip4.IP4Range(key), 34 self._convert_user_role(value[0], value[1]))) 35 self.clobber_username = clobber_username 36 37 def _convert_user_role(self, username, roles): 38 if roles and isinstance(roles, six.string_types): 39 roles = roles.split(',') 40 return (username, roles) 41 42 def __call__(self, environ, start_response): 43 addr = ip4.ip2int(environ['REMOTE_ADDR'], False) 44 remove_user = False 45 add_roles = [] 46 for range, (username, roles) in self.ip_map: 47 if addr in range: 48 if roles: 49 add_roles.extend(roles) 50 if username == '__remove__': 51 remove_user = True 52 elif username: 53 if (not environ.get('REMOTE_USER') 54 or self.clobber_username): 55 environ['REMOTE_USER'] = username 56 if (remove_user and 'REMOTE_USER' in environ): 57 del environ['REMOTE_USER'] 58 if roles: 59 self._set_roles(environ, add_roles) 60 return self.app(environ, start_response) 61 62 def _set_roles(self, environ, roles): 63 cur_roles = environ.get('REMOTE_USER_TOKENS', '').split(',') 64 # Get rid of empty roles: 65 cur_roles = list(filter(None, cur_roles)) 66 remove_roles = [] 67 for role in roles: 68 if role.startswith('-'): 69 remove_roles.append(role[1:]) 70 else: 71 if role not in cur_roles: 72 cur_roles.append(role) 73 for role in remove_roles: 74 if role in cur_roles: 75 cur_roles.remove(role) 76 environ['REMOTE_USER_TOKENS'] = ','.join(cur_roles) 77 78 79def make_grantip(app, global_conf, clobber_username=False, **kw): 80 """ 81 Grant roles or usernames based on IP addresses. 82 83 Config looks like this:: 84 85 [filter:grant] 86 use = egg:Paste#grantip 87 clobber_username = true 88 # Give localhost system role (no username): 89 127.0.0.1 = -:system 90 # Give everyone in 192.168.0.* editor role: 91 192.168.0.0/24 = -:editor 92 # Give one IP the username joe: 93 192.168.0.7 = joe 94 # And one IP is should not be logged in: 95 192.168.0.10 = __remove__:-editor 96 97 """ 98 from paste.deploy.converters import asbool 99 clobber_username = asbool(clobber_username) 100 ip_map = {} 101 for key, value in kw.items(): 102 if ':' in value: 103 username, role = value.split(':', 1) 104 else: 105 username = value 106 role = '' 107 if username == '-': 108 username = '' 109 if role == '-': 110 role = '' 111 ip_map[key] = value 112 return GrantIPMiddleware(app, ip_map, clobber_username) 113 114 115