Ticket #210: MapFishAuthentication.patch

File MapFishAuthentication.patch, 11.2 kB (added by pvalsecc, 5 months ago)

The patch for Map Fish?

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

    old new  
     1# 
     2# Copyright (C) 2007-2008  Camptocamp 
     3# 
     4# This file is part of MapFish 
     5# 
     6# MapFish is free software: you can redistribute it and/or modify 
     7# it under the terms of the GNU Lesser General Public License as published by 
     8# the Free Software Foundation, either version 3 of the License, or 
     9# (at your option) any later version. 
     10# 
     11# MapFish is distributed in the hope that it will be useful, 
     12# but WITHOUT ANY WARRANTY; without even the implied warranty of 
     13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     14# GNU Lesser General Public License for more details. 
     15# 
     16# You should have received a copy of the GNU Lesser General Public License 
     17# along with MapFish.  If not, see <http://www.gnu.org/licenses/>. 
     18# 
     19 
     20import logging 
     21 
     22from pylons import request, response 
     23from pylons.controllers import WSGIController 
     24from pylons.controllers.util import abort 
     25from routes.util import url_for 
     26from authkit.authorize.pylons_adaptors import authorized 
     27from authkit.authorize import NotAuthorizedError 
     28import urllib2 
     29from urllib import urlencode 
     30import urlparse 
     31 
     32def add_routes(map, url, controller): 
     33    """ 
     34    Add the pylons routes for a proxy 
     35    """ 
     36    map.connect(url+"*more", controller = controller, action = 'get', conditions = dict(method = ['GET'])) 
     37 
     38log = logging.getLogger(__name__) 
     39 
     40 
     41class AuthProxyController(WSGIController): 
     42    """Implements a proxy (HTTP GET only) with possibilities to authorize only 
     43    certain parameter values in function of AuthKit permissions. 
     44 
     45    For each URL parameter to check, child classes can define class variables 
     46    named "PERMISSION_{paramName.upper()}" containing a dictionary with the 
     47    parameter values (lowercase) as keys and a AuthKit permission as value. 
     48    Keys can be regular expressions as well. A default permission can be defined 
     49    with the key "DEFAULT" (uppercase). If a parameter value is not defined or 
     50    is None, authorisation is granted. If no class variable is found for a 
     51    parameters, authorisation is granted. 
     52 
     53    This proxy supports additional URL information. The permission dictionary 
     54    to control that is "PERMISSION__". 
     55 
     56    If the "standard" way to check the permission is not enough for you, you can 
     57    always override the _check method and add your tests in it. 
     58 
     59    To use it, add a controller like that to your project: 
     60        from mapfish.controllers.auth_proxy import AuthProxyController, Deny 
     61        from authkit.permissions import HasAuthKitRole, ValidAuthKitUser 
     62        import re 
     63 
     64        class WmsAuthProxyController(AuthProxyController): 
     65            # Base URL to redirect to 
     66            URL = "http://www.example.com" 
     67 
     68            # Permissions on what can be added to the URL 
     69            PERMISSIONS__ = { 
     70                '/tilecache': None, 
     71                'DEFAULT': Deny() 
     72            } 
     73 
     74            # Permission on what layer can be read by whom 
     75            PERMISSIONS_LAYERS = { 
     76                'background': None, 
     77                'stuff': HasAuthKitRole('view'), 
     78                re.compile('^secret\d+$'): HasAuthKitRole('god'), 
     79                'DEFAULT': Deny() 
     80            } 
     81 
     82    Then, add this to your routings.py: 
     83        from mapfish.controllers import auth_proxy 
     84        auth_proxy.addRoutes(map, 'wmsProxy', 'wms_auth_proxy') 
     85 
     86    """ 
     87 
     88    def _has_checks(self, field): 
     89        return hasattr(self, "PERMISSIONS_"+field.upper()) 
     90 
     91    def _get_role(self, field, value): 
     92        dico = getattr(self, "PERMISSIONS_"+field.upper()) 
     93 
     94         # try direct access 
     95        role = dico.get(value.lower(), 0) 
     96        if role != 0: 
     97            return role 
     98 
     99        # try with regular expression 
     100        for key in dico: 
     101            if hasattr(key, 'match'): 
     102                if key.match(value): 
     103                    return dico[key] 
     104 
     105        # try the DEFAULT value 
     106        return dico.get("DEFAULT", None) 
     107 
     108    def _get_url(self): 
     109        return self.URL 
     110 
     111    def get(self, more): 
     112        """Proxy action 
     113        """ 
     114        check_result = self._check(more) 
     115        if check_result == "": 
     116            return self._proxy(more) 
     117        else: 
     118            response.status=406             
     119            return check_result 
     120 
     121    def _check(self, more): 
     122        """Do the actual checking 
     123        """ 
     124        result = "" 
     125        if self._has_checks("_"): 
     126            perm = self._get_role("_", more) 
     127            if perm != None and not authorized(perm): 
     128                msg = "Not allowed to have the URL with value '%s' as user '%s'<br/>\n" % ( 
     129                         more, 
     130                         request.environ.get("REMOTE_USER", "anonymous") 
     131                      ) 
     132                result += msg 
     133 
     134        #check the parameters for special rights 
     135        for param in request.params: 
     136            if self._has_checks(param): 
     137                paramValues = request.params[param].split(",") 
     138                for paramValue in paramValues: 
     139                    paramValue = paramValue.strip() 
     140                    perm = self._get_role(param, paramValue) 
     141                    if perm!=None and not authorized(perm): 
     142                        msg = "Not allowed to have the parameter '%s' with value '%s' as user '%s'<br/>\n" % ( 
     143                                 param, 
     144                                 paramValue, 
     145                                 request.environ.get("REMOTE_USER", "anonymous") 
     146                              ) 
     147                        result += msg 
     148        return result                     
     149 
     150    def _proxy(self, more): 
     151        """Do the actual action of proxying the call. 
     152        """ 
     153        query = urlencode(request.params) 
     154        fullUrl = self._get_url() + more; 
     155        if query: 
     156            if not fullUrl.endswith("?"): 
     157                fullUrl += "?" 
     158            fullUrl += query 
     159 
     160        #build the request with its headers 
     161        req = urllib2.Request(url=fullUrl) 
     162        for header in request.headers: 
     163            if header.lower()=="host": 
     164                req.add_header(header, urlparse.urlparse(self._get_url())[1]) 
     165            else: 
     166                req.add_header(header, request.headers[header]) 
     167        res = urllib2.urlopen(req) 
     168 
     169        #add response headers 
     170        i = res.info() 
     171        response.status = res.code 
     172        gotContentLength = False 
     173        for header in i: 
     174            response.headers[header] =i[header] 
     175            if header.lower() == "content-length": 
     176                gotContentLength = True 
     177 
     178        #return the result 
     179        result = res.read(); 
     180        res.close(); 
     181 
     182        if not gotContentLength: 
     183            response.headers['content-length'] = str(len(result)) 
     184        return result; 
     185 
     186 
     187class Deny: 
     188    """A special AuthKit permission that always deny access 
     189    """ 
     190    def check(self, app, environ, start_response): 
     191        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 = {}