# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Grant roles and logins based on IP address. """ import six from paste.util import ip4 class GrantIPMiddleware(object): """ On each request, ``ip_map`` is checked against ``REMOTE_ADDR`` and logins and roles are assigned based on that. ``ip_map`` is a map of {ip_mask: (username, roles)}. Either ``username`` or ``roles`` may be None. Roles may also be prefixed with ``-``, like ``'-system'`` meaning that role should be revoked. ``'__remove__'`` for a username will remove the username. If ``clobber_username`` is true (default) then any user specification will override the current value of ``REMOTE_USER``. ``'__remove__'`` will always clobber the username. ``ip_mask`` is something that `paste.util.ip4:IP4Range <class-paste.util.ip4.IP4Range.html>`_ can parse. Simple IP addresses, IP/mask, ip<->ip ranges, and hostnames are allowed. """ def __init__(self, app, ip_map, clobber_username=True): self.app = app self.ip_map = [] for key, value in ip_map.items(): self.ip_map.append((ip4.IP4Range(key), self._convert_user_role(value[0], value[1]))) self.clobber_username = clobber_username def _convert_user_role(self, username, roles): if roles and isinstance(roles, six.string_types): roles = roles.split(',') return (username, roles) def __call__(self, environ, start_response): addr = ip4.ip2int(environ['REMOTE_ADDR'], False) remove_user = False add_roles = [] for range, (username, roles) in self.ip_map: if addr in range: if roles: add_roles.extend(roles) if username == '__remove__': remove_user = True elif username: if (not environ.get('REMOTE_USER') or self.clobber_username): environ['REMOTE_USER'] = username if (remove_user and 'REMOTE_USER' in environ): del environ['REMOTE_USER'] if roles: self._set_roles(environ, add_roles) return self.app(environ, start_response) def _set_roles(self, environ, roles): cur_roles = environ.get('REMOTE_USER_TOKENS', '').split(',') # Get rid of empty roles: cur_roles = list(filter(None, cur_roles)) remove_roles = [] for role in roles: if role.startswith('-'): remove_roles.append(role[1:]) else: if role not in cur_roles: cur_roles.append(role) for role in remove_roles: if role in cur_roles: cur_roles.remove(role) environ['REMOTE_USER_TOKENS'] = ','.join(cur_roles) def make_grantip(app, global_conf, clobber_username=False, **kw): """ Grant roles or usernames based on IP addresses. Config looks like this:: [filter:grant] use = egg:Paste#grantip clobber_username = true # Give localhost system role (no username): 127.0.0.1 = -:system # Give everyone in 192.168.0.* editor role: 192.168.0.0/24 = -:editor # Give one IP the username joe: 192.168.0.7 = joe # And one IP is should not be logged in: 192.168.0.10 = __remove__:-editor """ from paste.deploy.converters import asbool clobber_username = asbool(clobber_username) ip_map = {} for key, value in kw.items(): if ':' in value: username, role = value.split(':', 1) else: username = value role = '' if username == '-': username = '' if role == '-': role = '' ip_map[key] = value return GrantIPMiddleware(app, ip_map, clobber_username)