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