115 lines
3.9 KiB
Python
115 lines
3.9 KiB
Python
# (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)
|
|
|
|
|