Ticket #210: MapFishAuthentication_v2.patch

File MapFishAuthentication_v2.patch, 13.2 kB (added by sypasche, 4 months ago)

version 2

  • server/python/mapfish/controllers/auth_proxy.py

    old new  
     1from pylons import request, response 
     2from pylons.controllers import WSGIController 
     3 
     4from authkit.authorize.pylons_adaptors import authorized 
     5from authkit.authorize import NotAuthorizedError 
     6import re 
     7import cgi 
     8import urlparse 
     9import urllib2 
     10from urllib import urlencode 
     11 
     12def add_routes(map, url, controller): 
     13    """Add the pylons routes for a proxy 
     14    """ 
     15    map.connect(url + "*more", controller=controller, action='get', 
     16                conditions=dict(method=['GET'])) 
     17 
     18class Layer(object): 
     19    def __init__(self, alias, url, layers, **kwargs): 
     20        self.alias = alias 
     21        self.url = url 
     22        self.layers = layers 
     23        self.options = kwargs 
     24 
     25    def get_permissions(self): 
     26        return { 
     27            "url": self.url, 
     28            "layers": dict(((name, authorized(perm)) for (name, perm) in 
     29                            self.layers.iteritems())) 
     30        } 
     31 
     32    def check_permissions(self): 
     33        # Check layers 
     34        layers = self.get_requested_layers() 
     35        for layer in layers: 
     36            if self.layers.has_key(layer): 
     37                perm = self.layers[layer] 
     38            elif self.layers.has_key("DEFAULT"): 
     39                perm = self.layers["DEFAULT"] 
     40            else: 
     41                continue 
     42            if not authorized(perm): 
     43                return False, "Not allowed to access layer: %s" % cgi.escape(layer) 
     44 
     45        # TODO Check bbox 
     46 
     47        return True, "Access allowed" 
     48 
     49    def get_requested_layers(self): 
     50        raise NotImplementedError() 
     51 
     52    def get_requested_bbox(self): 
     53        raise NotImplementedError() 
     54 
     55class WMSLayer(Layer): 
     56    def get_param(self, name): 
     57        """Returns the request parameter given in argument, ignoring case. 
     58           If not found, None is returned. 
     59        """ 
     60        for k in request.params.keys(): 
     61            if k.lower() == name: 
     62                return request.params[k] 
     63        return None 
     64 
     65    def get_requested_layers(self): 
     66        layers = self.get_param("layers") 
     67        if not layers: 
     68            return [] 
     69        return layers.split(",") 
     70 
     71class TileCacheLayer(Layer): 
     72    def get_requested_layers(self): 
     73        raise NotImplementedError("TODO TileCacheLayer::get_requested_layers") 
     74 
     75 
     76class AuthProxyController(WSGIController): 
     77    def __init__(self): 
     78        self.layers = [] 
     79        self.alias_to_layer = {} 
     80 
     81    def set_layers(self, layers): 
     82        self.alias_to_layers = dict(((layer.alias, layer) for layer in layers)) 
     83        self.layers = layers 
     84 
     85    def _get_permissions(self): 
     86        return { 
     87            "layer": [l.get_permissions() for l in self.layers] 
     88        } 
     89 
     90    def get(self, more): 
     91        """Proxy action 
     92        """ 
     93        # extract the first segment of the url 
     94        m = re.match("/?([^/]+)(/?.*)$", more) 
     95        if not m: 
     96            response.status_code = 500 
     97            return "Invalid URL, missing alias in the path" 
     98        alias, more = m.groups() 
     99         
     100        if not self.alias_to_layers.has_key(alias): 
     101            response.status_code = 500 
     102            return "Wrong alias %s" % cgi.escape(alias) 
     103        layer = self.alias_to_layers[alias] 
     104 
     105        allowed, msg = layer.check_permissions() 
     106        if not allowed: 
     107            # We don't use 403 to avoid triggering AuthKit login dialog 
     108            response.status_code = 406 
     109            return msg 
     110 
     111        url = layer.url + more 
     112        return self._proxy(url) 
     113 
     114    def _proxy(self, url): 
     115        """Do the actual action of proxying the call. 
     116        """ 
     117        query = urlencode(request.params) 
     118        full_url = url 
     119        if query: 
     120            if not full_url.endswith("?"): 
     121                full_url += "?" 
     122            full_url += query 
     123 
     124        # build the request with its headers 
     125        print "full_url", full_url 
     126        req = urllib2.Request(url=full_url) 
     127        for header in request.headers: 
     128            if header.lower() == "host": 
     129                req.add_header(header, urlparse.urlparse(url)[1]) 
     130            else: 
     131                req.add_header(header, request.headers[header]) 
     132        res = urllib2.urlopen(req) 
     133 
     134        # add response headers 
     135        i = res.info() 
     136        response.status_code = res.code 
     137        got_content_length = False 
     138        for header in i: 
     139            response.headers[header] =i[header] 
     140            if header.lower() == "content-length": 
     141                got_content_length = True 
     142 
     143        # return the result 
     144        result = res.read() 
     145        res.close() 
     146 
     147        if not got_content_length: 
     148            response.headers['content-length'] = str(len(result)) 
     149        return result 
     150 
  • server/python/mapfish/controllers/security.py

    old new  
     1import logging 
     2 
     3from pylons.controllers import WSGIController 
     4from pylons.decorators import jsonify 
     5from pylons import config 
     6from authkit.authorize.pylons_adaptors import authorize, authorized 
     7from authkit.permissions import ValidAuthKitUser 
     8from authkit.authorize import NotAuthorizedError 
     9import simplejson 
     10 
     11import sys 
     12import os 
     13import os.path 
     14import inspect 
     15 
     16 
     17log = logging.getLogger(__name__) 
     18 
     19class SecurityController(WSGIController): 
     20    def _get_master_permissions(self): 
     21        """Read the permissions in the config/permissions.json file and return 
     22           an object 
     23        """ 
     24        perm_json = os.path.join(config.here, config['pylons.package'], 
     25                                 "config", "permissions.json") 
     26 
     27        permissions = {} 
     28        modulename = "%s.config.permissions" % config['pylons.package'] 
     29        try: 
     30            mod = __import__(modulename) 
     31            for comp in modulename.split(".")[1:]: 
     32                mod = getattr(mod, comp) 
     33            permissions = mod.permissions 
     34        except ImportError: 
     35            log.debug("Couldn't find permission configuration module %s" % modulename) 
     36 
     37        # compute permissions 
     38        # TODO: calling authorized should be propagated inside nested dicts. 
     39        permissions = dict(((key, authorized(perm)) for (key, perm) in 
     40                            permissions.iteritems())) 
     41 
     42        return permissions 
     43 
     44    @jsonify 
     45    def permissions(self): 
     46        controllers_module_name = config['pylons.package'] + '.controllers' 
     47        __import__(controllers_module_name) 
     48        module = sys.modules[controllers_module_name] 
     49 
     50        permissions = self._get_master_permissions() 
     51        module_names = [] 
     52 
     53        # Iterate through all the controllers to call the method "get_permissions" 
     54        # at the module level if available. 
     55        # The value returned is merged into the permission object. 
     56 
     57        if hasattr(module, '__path__'): 
     58            for file in os.listdir(module.__path__[0]): 
     59                if not file.endswith(".py"): 
     60                    continue 
     61                path = os.path.join(module.__path__[0], file) 
     62                module_name = inspect.getmodulename(file) 
     63                if not module_name or module_name == '__init__': 
     64                    continue 
     65 
     66                method_name = "get_permissions" 
     67                full_module_name = controllers_module_name + '.' + module_name 
     68                try: 
     69                    __import__(full_module_name) 
     70                except Exception, e: 
     71                    log.warn("Failure while fetching controller in module %s (%s: %s)", 
     72                             full_module_name, e.__class__, e) 
     73                    continue 
     74                mod = sys.modules[full_module_name] 
     75                if not hasattr(mod, method_name): 
     76                    continue 
     77                get_permissions_method = getattr(mod, method_name) 
     78 
     79                perms = get_permissions_method() 
     80                if not isinstance(perms, dict): 
     81                    raise Exception("Invalid permission type, should be dict, was %s" % type(perms)) 
     82 
     83                # TODO: should use a recursive udpate 
     84                permissions.update(perms) 
     85 
     86        return {"permissions": permissions} 
     87 
     88    # XXX for debug 
     89    @authorize(ValidAuthKitUser()) 
     90    def log_me_in(self): 
     91        return "This page requires login <br/><a href='/logout'>logout</a>" 
     92 
     93    # XXX for debug 
     94    # XXX could be used by client to retrieve user/roles. 
     95    @jsonify 
     96    def user_roles(self): 
     97        user = request.environ.get("REMOTE_USER", "anonymous") 
     98        users = request.environ['authkit.users'] 
     99        roles = [] 
     100        for role in users.list_roles(): 
     101            if users.user_has_role(user, role): 
     102                roles.append(role) 
     103        return {"user": user, "roles": roles} 
     104 
     105class Deny: 
     106    """A special AuthKit permission that always deny access 
     107    """ 
     108    def check(self, app, environ, start_response): 
     109        raise NotAuthorizedError("Not authorized at all") 
  • server/python/mapfish/lib/user_auth.py

    old new  
     1from authkit.users.sqlalchemy_driver import UsersFromDatabase 
     2from time import time 
     3 
     4class CachedUsersFromDatabase(UsersFromDatabase): 
     5    """ 
     6    Caching proxy for AuthKit's class. 
     7 
     8    It's not 100% thread safe, but who cares if the request is done twice for 
     9    nothing? 
     10    """ 
     11 
     12    #number of seconds between two purge of the caches 
     13    REFRESH_PERIOD = 60 
     14 
     15    def __init__(self, model, encrypt=None): 
     16        UsersFromDatabase.__init__(self, model, encrypt) 
     17        self.nextRefresh = 0 
     18        self.checkCache() 
     19         
     20    def user_delete(self, username): 
     21        self.users.pop(username) 
     22        self.fullUsers.pop(username) 
     23        return UsersFromDatabase.user_delete(self, username) 
     24 
     25    def user_exists(self, username): 
     26        self.checkCache() 
     27        if self.users.has_key(username): 
     28            return self.users.get(username) 
     29        else: 
     30            result = UsersFromDatabase.user_exists(self, username) 
     31            self.users[username] = result 
     32            return result 
     33 
     34    def role_delete(self, rolename): 
     35        self.roles.pop(rolename) 
     36        return UsersFromDatabase.role_delete(self, rolename) 
     37 
     38    def role_exists(self, rolename): 
     39        self.checkCache() 
     40        if self.roles.has_key(rolename): 
     41            return self.roles.get(rolename) 
     42        else: 
     43            result = UsersFromDatabase.role_exists(self, rolename) 
     44            self.roles[rolename] = result 
     45            return result 
     46 
     47    def group_delete(self, groupname): 
     48        self.groups.pop(groupname) 
     49        return UsersFromDatabase.group_delete(self, groupname) 
     50 
     51    def group_exists(self, groupname): 
     52        self.checkCache() 
     53        if self.groups.has_key(groupname): 
     54            return self.groups.get(groupname) 
     55        else: 
     56            result = UsersFromDatabase.group_exists(self, groupname) 
     57            self.groups[groupname] = result 
     58            return result 
     59 
     60    def user(self, username): 
     61        self.checkCache() 
     62        if self.fullUsers.has_key(username): 
     63            return self.fullUsers.get(username) 
     64        else: 
     65            result = UsersFromDatabase.user(self, username) 
     66            self.fullUsers[username] = result 
     67            return result 
     68 
     69    def user_roles(self, username): 
     70        return self.user(username)['roles'] 
     71 
     72    def user_group(self, username): 
     73        return self.user(username)['group'] 
     74 
     75    def user_password(self, username): 
     76        return self.user(username)['password'] 
     77 
     78    def user_has_role(self, username, role): 
     79        return self.user_roles(username).count(role)>0 
     80 
     81    def user_has_group(self, username, group): 
     82        return self.user_group(username)==group 
     83 
     84    def user_has_password(self, username, password): 
     85        return self.user(username)['password']==self.encrypt(password) 
     86 
     87    def user_set_username(self, username, new_username): 
     88        self.fullUsers.pop(username) 
     89        UsersFromDatabase.user_set_username(self, username, new_username) 
     90 
     91    def user_set_group(self, username, group, auto_add_group=False): 
     92        self.fullUsers.pop(username) 
     93        self.groups.pop(group) 
     94        UsersFromDatabase.user_set_group(self, username, group, auto_add_group) 
     95 
     96    def user_add_role(self, username, role, auto_add_role=False): 
     97        self.fullUsers.pop(username) 
     98        self.roles.pop(role) 
     99        UsersFromDatabase.user_add_role(self, username, role, auto_add_role) 
     100 
     101    def user_remove_group(self, username): 
     102        self.fullUsers.pop(username) 
     103        UsersFromDatabase.user_remove_group(self, username, group) 
     104 
     105    def user_remove_role(self, username, role): 
     106        self.fullUsers.pop(username) 
     107        UsersFromDatabase.user_remove_role(self, username, role) 
     108 
     109    def checkCache(self): 
     110        if self.nextRefresh <= time(): 
     111            self.nextRefresh = time() + self.REFRESH_PERIOD 
     112            self.users = {} 
     113            self.roles = {} 
     114            self.groups = {} 
     115            self.fullUsers = {}