Changeset 8792


Ignore:
Timestamp:
30/08/13 17:14:44 (6 years ago)
Author:
mnagni
Message:

Incomplete - # 22802: [CEDA Site Python Port] Login - no message is displayed when the user enters the wrong password
 http://team.ceda.ac.uk/trac/ceda/ticket/22802

Updates to implement the "Reset Password" feature

Location:
mauRepo/dj_security_middleware/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • mauRepo/dj_security_middleware/trunk/README

    r8752 r8792  
    33 
    44The dj_security_middleware.middleware.DJ_Security_Middleware checks two cases: 
    5 1) if the HTTP request contains a cookie called 'auth_tkt' 
     51) if the HTTP request contains a cookie named after the AUTH_TKT parameter 
    662) the underlying application defines a function returning a not None value  
    77(see DJ_SECURITY_AUTH_CHECK). 
     
    99the DJ_SECURITY_LOGIN_SERVICE  
    1010  
    11 If the authentication succeeds the DJ_SECURITY_LOGIN_SERVICE sets the 'auth_tkt' cookie, 
     11If the authentication succeeds the DJ_SECURITY_LOGIN_SERVICE sets the AUTH_TKT cookie, 
    1212which is caught by the middleware which: 
    1313a) reads the informations in the cookie and copies them into the request  
    1414'authenticated_user' parameter 
    15 b) deletes the 'auth_tkt' cookie for security reasons. 
     15b) deletes the AUTH_TKT cookie for security reasons. 
    1616The request 'authenticated_user' parameter contains all the user information  
    1717returned by the authentication service and from this moment the underlying  
     
    1919 
    2020If the request, GET or POST, contains a parameter 'logout' with value different from '' 
    21 then the 'auth_tkt' will be removed from the next response  
     21then the AUTH_TKT will be removed from the next response  
    2222 
    2323The configuration is quite straightforward. In your Django app settings.py add: 
     
    2828is listening (say http://my.domain.ac.uk/login) 
    2929 
    30 3) DJ_SECURITY_SHAREDSECRET to specify the secret key used by the authentication  
    31 service to encrypt the 'auth_tkt' cookie (say 'sharedsecret') 
     303) SECURITY_SHAREDSECRET (optional, default='sharedsecret') to specify  
     31the secret key used by the authentication service to encrypt the AUTH_TKT cookie 
    3232 
    33 4) DJ_SECURITY_FILTER (optional) is a list of URL paths which are secured 
    34 by the middleware, that is if a request URL matches any in DJ_SECURITY_FILTER 
    35 the middleware will verify if the user is authenticated or not. 
    36 If the parameter is absent all the required paths will be secured. 
    37 More specifically the middleware will trust 
    38  - all the patterns equal to a given path 
    39  - all the patterns equal or below a given path 
    40 Example: 
    41 ------------------------------------------------------------------------- 
    42 # If not already authenticated redirects to the DJ_SECURITY_LOGIN_SERVICE 
    43 DJ_SECURITY_FILTER = []   
    44  
    45 If not already authenticated redirects to the DJ_SECURITY_LOGIN_SERVICE 
    46 all the paths starting with "/my_ceda" 
    47 DJ_SECURITY_FILTER = ['/my_ceda']   
    48  
    49 If not already authenticated redirects to the DJ_SECURITY_LOGIN_SERVICE 
    50 all the paths starting with "/my_ceda/my_page" but not path like "/my_ceda" 
    51 DJ_SECURITY_FILTER = ['/my_ceda/my_page'] 
    52  
    53 Equivalent to ['/my_ceda'] 
    54 DJ_SECURITY_FILTER = ['/my_ceda/my_page', '/my_ceda'] 
    55  
    56 If not already authenticated redirects to the DJ_SECURITY_LOGIN_SERVICE 
    57 all the paths starting with regular expression "/my_ceda/[1-2]", that is 
    58 'my_ceda/1_test' will be secured, 'my_ceda/3_test' will be not 
    59 DJ_SECURITY_FILTER = ['/my_ceda/[1-2]'] 
    60 ------------------------------------------------------------------------- 
     334) DJ_SECURITY_FILTER (optional) is a list of regular expressions used to filter  
     34which URLs the middleware SHOULD NOT protect 
    6135 
    62365) DJ_SECURITY_AUTH_CHECK (optional) is a function which returns a boolean  
     
    7549-------------------------------- 
    7650 
    77 5) DJ_MIDDLEWARE_IP (optional) to specify the client machine where the middleware is installed  
     516) DJ_MIDDLEWARE_IP (optional) to specify the client machine where the middleware is installed  
    7852(say '123.456.7.8'). The reason for this is that the client machine could be behind  
    7953a proxy and in this case the authentication service uses the remote machine IP,  
    8054the proxy in this case, to encrypt the cookie.  
     55 
     567) REDIRECT_FIELD_NAME (optional, default = 'r') specifies the name of the GET parameter  
     57containing return address to be used by the authentication layer if login is successful  
     58 
     598) TOKEN_FIELD_NAME (optional, default = 't') specifies the name of the GET parameter  
     60containing token after a reset password action has been executed 
  • mauRepo/dj_security_middleware/trunk/dj_security_middleware/__init__.py

    r8761 r8792  
    1 __version__ = '0.0.10' 
     1__version__ = '0.0.11' 
     2 
     3from django.conf import settings 
     4from paste.auth.auth_tkt import AuthTicket 
     5import logging 
     6import socket 
     7import urlparse 
     8 
     9# Get an instance of a logger 
     10LOGGER = logging.getLogger(__name__) 
     11 
     12def auth_tkt_name(): 
     13    return getattr(settings, 'AUTH_TKT_NAME', 'auth_tkt') 
     14 
     15def cookie_domain(): 
     16    return getattr(settings, 'COOKIE_DOMAIN', None) 
     17 
     18def reset_password(): 
     19    return getattr(settings, 'CC_RESET_PASSWORD', []) 
     20 
     21def redirect_field_name(): 
     22    return getattr(settings, 'REDIRECT_FIELD_NAME', 'r') 
     23 
     24def token_field_name(): 
     25    return getattr(settings, 'TOKEN_FIELD_NAME', 't') 
     26 
     27def security_filter(): 
     28    return getattr(settings, 'DJ_SECURITY_FILTER', []) 
     29 
     30def shared_secret(): 
     31    return getattr(settings, 'SECURITY_SHAREDSECRET', 'sharedsecret') 
     32 
     33def _calculate_remote_ip(url_path):    
     34    remote_url = urlparse.urlparse(url_path) 
     35    LOGGER.debug("calculating remote_ip for %s" % (str(remote_url))) 
     36    port = 80 
     37    host = None 
     38    if remote_url.netloc: 
     39        host = remote_url.netloc 
     40    elif remote_url.path: 
     41        host = remote_url.path 
     42         
     43    if not host: 
     44        return None 
     45     
     46    if ':' in host: 
     47        host, port = host.split(':') 
     48    addrinfo = socket.getaddrinfo(host, int(port)) 
     49    LOGGER.debug("%s has remote_ip %s" % (url_path, addrinfo[0][-1][0])) 
     50    for remote_url in addrinfo: 
     51        if not remote_url[-1][0].startswith('127'): 
     52            return remote_url[-1][0] 
     53    return None  
     54 
     55def _get_host_ip(): 
     56    if getattr(settings, 'DJ_MIDDLEWARE_IP', None): 
     57        return settings.DJ_MIDDLEWARE_IP 
     58         
     59    return _calculate_remote_ip(socket.getfqdn()) 
     60 
     61def generate_auth_cookie(user, response, remote_ip = _get_host_ip()): 
     62    token = AuthTicket( 
     63            getattr(settings, 'SHARED_SECRET', 'sharedsecret'),  
     64            user.accountid,  
     65            remote_ip,  
     66            user_data = '{"userkey": "%s", "accountid": "%s"}'  
     67            % (user.userkey, getattr("user", "accountid", "NotAssigned")))                
     68    LOGGER.info("Created authTicket for %s from %s" % (user.accountid, remote_ip)) 
     69    idomain = getattr(settings, 'COOKIE_DOMAIN', None) 
     70    response.set_cookie(auth_tkt_name(),  
     71                        token.cookie_value(),  
     72                        domain = idomain) 
     73    LOGGER.debug("Set authTicket in response for %s from %s to domain %s"  
     74                 % (user.accountid, remote_ip, idomain)) 
     75    return response 
  • mauRepo/dj_security_middleware/trunk/dj_security_middleware/middleware.py

    r8761 r8792  
    3434from paste.auth.auth_tkt import BadTicket 
    3535from django.conf import settings 
     36 
     37from django.utils.http import urlencode 
     38from django.http import HttpResponseRedirect 
     39 
    3640from dj_security_middleware.exception import DJMiddlewareException,\ 
    3741    MissingCookieException 
    38 from django.utils.http import urlencode 
    39 from django.http import HttpResponseRedirect 
     42from dj_security_middleware import _get_host_ip, security_filter, auth_tkt_name,\ 
     43    shared_secret, token_field_name, redirect_field_name 
     44 
    4045import socket 
    4146import logging 
    42 import urlparse 
    4347import re 
    44 import base64 
    45  
    46 # Get an instance of a logger 
    47 LOGGER = logging.getLogger() 
     48import urllib 
    4849 
    4950LOGIN_SERVICE_ERROR = 'No LOGIN_SETTING parameter is defined in the \ 
     
    5152authenticating service' 
    5253 
    53 DJ_SECURITY_SHAREDSECRET_ERROR = 'No DJ_SECURITY_SHAREDSECRET parameter \ 
     54DJ_SECURITY_SHAREDSECRET_ERROR = 'No SECURITY_SHAREDSECRET parameter \ 
    5455is defined in the application settings.py file. \ 
    5556Please define it accordingly to the used LOGIN_SERVICE' 
     
    6263Please define it accordingly to the machine/proxy seen by the LOGIN_SERVICE' 
    6364 
    64 AUTH_TKT = 'auth_tkt' 
     65LOGOUT = 'logout' 
    6566 
    66 LOGOUT = 'logout' 
     67LOGGER = logging.getLogger(__name__) 
     68 
     69def preapare_user_for_session(request, timestamp, userid, tokens, user_data): 
     70    request.authenticated_user = {'timestamp': timestamp, \ 
     71                                     'userid': userid, \ 
     72                                     'tokens': tokens, \ 
     73                                     'user_data': user_data} 
     74    LOGGER.debug("stored in request - userid:%s, user_data:%s" % (userid, user_data)) 
     75    request.session['accountid'] = userid 
    6776 
    6877class DJ_Security_Middleware(object): 
     
    7382        to the underlying Django application and verifies if the presence  
    7483        or not of a valid paste cookie in the request. 
    75     """     
     84    """ 
     85 
    7686    def process_request(self, request): 
     87        #Has to process a reset password request?  
    7788        if len(request.REQUEST.get(LOGOUT, '')) > 0: 
    7889            response = HttpResponseRedirect(_build_url(request))             
    79             response.delete_cookie(AUTH_TKT,  
    80                                    domain=getattr(settings,  
    81                                                   'COOKIE_DOMAIN',  
    82                                                   'rl.ac.uk')) 
     90            response.delete_cookie(auth_tkt_name()) 
    8391            return response 
    8492 
    8593        request.session['accountid'] = None 
    86         url_fiters = getattr(settings, 'DJ_SECURITY_FILTER', None) 
     94        url_fiters = getattr(settings, 'DJ_SECURITY_FILTER', []) 
     95         
     96        #adds a default filter for reset password request 
     97        reset_regexpr = '%s=[a-f0-9-]*$' % (token_field_name()) 
     98        if reset_regexpr not in security_filter():  
     99            url_fiters.append(reset_regexpr) 
     100         
    87101        if url_fiters \ 
    88             and security_url_filter(request.path, url_fiters): 
     102            and filter(_build_url(request), url_fiters): 
    89103            return 
    90104         
    91105        if not getattr(settings, 'DJ_SECURITY_LOGIN_SERVICE', None): 
    92106            raise DJMiddlewareException(LOGIN_SERVICE_ERROR)   
    93         if not getattr(settings, 'DJ_SECURITY_SHAREDSECRET', None): 
    94             raise DJMiddlewareException(DJ_SECURITY_SHAREDSECRET_ERROR) 
    95107         
    96108        custom_auth = getattr(settings, 'DJ_SECURITY_AUTH_CHECK', None) 
     
    108120          
    109121        try: 
    110             url = '%s?%s' % (settings.DJ_SECURITY_LOGIN_SERVICE, _build_login_url(request)) 
     122            qs = {redirect_field_name():  
     123                  urllib.quote_plus((_build_url(request)))}              
     124            url = '%s?%s' % (settings.DJ_SECURITY_LOGIN_SERVICE,  
     125                             urlencode(qs)) 
    111126            timestamp, userid, tokens, user_data = _is_authenticated(request) 
    112             request.authenticated_user = {'timestamp': timestamp, \ 
    113                                              'userid': userid, \ 
    114                                              'tokens': tokens, \ 
    115                                              'user_data': user_data} 
    116             LOGGER.debug("stored in request - userid:%s, user_data:%s" % (userid, user_data)) 
    117             request.session['accountid'] = userid 
     127            preapare_user_for_session(request,  
     128                                      timestamp,  
     129                                      userid,  
     130                                      tokens,  
     131                                      user_data) 
    118132        except MissingCookieException as ex: 
    119             LOGGER.info("Missing 'auth_tkt' cookie. Redirecting to %s" % (url)) 
     133            LOGGER.info("Missing cookie '%s'. Redirecting to %s" % (auth_tkt_name(), url)) 
    120134            return HttpResponseRedirect(url) 
    121135        except DJMiddlewareException as ex:                     
    122136            LOGGER.info("Error in authentication. Redirecting to %s" % (url)) 
    123137            return HttpResponseRedirect(url)            
     138 
     139 
    124140             
    125141    def process_response(self, request, response): 
     
    128144def _build_url(request): 
    129145    hostname = socket.getfqdn() 
    130     new_get = request.GET.urlencode() 
    131     if request.REQUEST.has_key(LOGOUT): 
    132         start_index = new_get.find(LOGOUT) 
    133         end_index = new_get.find('&', start_index) 
    134         if end_index == -1: 
    135             end_index = len(new_get) 
    136         new_get = new_get.replace(new_get[start_index: end_index],'') 
     146    new_get = request.GET.copy() 
     147    if new_get.has_key(LOGOUT): 
     148        new_get.pop(LOGOUT) 
    137149    if request.META['SERVER_PORT'] != 80: 
    138150        hostname = "%s:%s" % (hostname, request.META['SERVER_PORT']) 
    139     copy_get = request.GET.copy() 
    140     return 'http://%s%s?%s' % (hostname, request.path, new_get) 
    141  
    142 def _build_login_url(request): 
    143     qs = {} 
    144     qs['r'] = base64.b64encode(_build_url(request)) 
    145     return urlencode(qs)            
     151    return 'http://%s%s?%s' % (hostname,  
     152                               request.path,  
     153                               urllib.urlencode(new_get)) 
    146154 
    147155def _is_authenticated(request): 
     
    153161        ** raise ** a DJ_SecurityException if the ticket is not valid 
    154162    """  
    155     if AUTH_TKT in request.COOKIES: 
    156         LOGGER.debug("Found auth_tkt: %s in cookies" % (request.COOKIES.get('auth_tkt'))) 
     163    if auth_tkt_name() in request.COOKIES: 
     164        LOGGER.debug("Found cookie '%s': %s in cookies" \ 
     165                     % (auth_tkt_name(), request.COOKIES.get(auth_tkt_name()))) 
    157166        try: 
    158167             
    159168            return auth_tkt.parse_ticket( 
    160                     settings.DJ_SECURITY_SHAREDSECRET,  
    161                     request.COOKIES.get(AUTH_TKT, ''),  
     169                    shared_secret(),  
     170                    request.COOKIES.get(auth_tkt_name(), ''),  
    162171                    _get_host_ip()) 
    163172        except BadTicket as ex: 
    164173            raise DJMiddlewareException(ex)  
    165     raise MissingCookieException(AUTHENTICATION_COOKIE_MISSING) 
    166     
    167 def _calculate_remote_ip(url_path):    
    168     remote_url = urlparse.urlparse(url_path) 
    169     LOGGER.debug("calculating remote_ip for %s" % (str(remote_url))) 
    170     port = 80 
    171     host = None 
    172     if remote_url.netloc: 
    173         host = remote_url.netloc 
    174     elif remote_url.path: 
    175         host = remote_url.path 
    176          
    177     if not host: 
    178         return None 
    179      
    180     if ':' in host: 
    181         host, port = host.split(':') 
    182     addrinfo = socket.getaddrinfo(host, int(port)) 
    183     LOGGER.debug("%s has remote_ip %s" % (url_path, addrinfo[0][-1][0])) 
    184     for remote_url in addrinfo: 
    185         if not remote_url[-1][0].startswith('127'): 
    186             return remote_url[-1][0] 
    187     return None 
    188      
    189 def _get_host_ip(): 
    190     if getattr(settings, 'DJ_MIDDLEWARE_IP', None): 
    191         return settings.DJ_MIDDLEWARE_IP 
    192          
    193     return _calculate_remote_ip(socket.getfqdn()) 
     174    raise MissingCookieException(AUTHENTICATION_COOKIE_MISSING)  
    194175 
    195 def security_url_filter(string, filters): 
    196     """ 
    197         Checks a given url request against a list of url filters. 
    198         ** string ** string a url 
    199         ** filters ** a list of strings 
    200         ** RETURN ** True if a match is found, False otherwise 
    201     """  
    202     try:         
    203         result = urlparse.urlparse(string) 
    204         return _security_filter(result.path, filters) 
    205     except AttributeError: 
    206         return False 
    207  
    208 def _security_filter(string, filters): 
     176def filter(string, filters): 
    209177    """ 
    210178        Checks a given strings against a list of strings. 
    211179        ** string ** string a url 
    212180        ** filters ** a list of strings 
    213     """     
    214     if not filters or not string or len(string.strip()) == 0: 
    215         return False 
    216     if string in filters: 
    217         return True 
     181    """ 
    218182    for ifilter in filters: 
    219         if re.match(ifilter, string): 
     183        if re.search(ifilter, string): 
    220184            return True 
Note: See TracChangeset for help on using the changeset viewer.