| # (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) |
| |
| |