source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/lib/security_util.py @ 3056

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/lib/security_util.py@3056
Revision 3056, 8.6 KB checked in by pjkersha, 13 years ago (diff)

Important fix for ticket #883 - ensures cookie at users login site is kept in sync with their Session Manager.

ows_server/ndgDiscovery.config: default SM URI goes through Apache now

ows_server/ows_server/controllers/login.py: added call to SessionMgr?.getSessionStatus in LoginController?.index. This checks the users session and if not found on the Session Manager, offers re-login. This is a likely scenario where the user logs off at a remote site removing their session from the Session Manager but leave stale security session cookie details on their home site.

ows_server/ows_server/lib/security_util.py: fix to LoginServiceQuery? - raise new LoginServiceQueryError? type exception

Line 
1# Copyright (C) 2007 STFC & NERC (Science and Technology Facilities Council).
2# This software may be distributed under the terms of the
3# Q Public License, version 1.0 or later.
4# http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
5"""
6Utilities for transfor of credentials over login service
7
8@author: Philip Kershaw
9"""
10__revision__ = '$Id:$'
11
12import logging
13log = logging.getLogger(__name__)
14
15import urllib
16from pylons import session, request, g
17
18
19class SecuritySession(object):
20    """Utility for Pylons security session keys enables correct
21    keys to be set
22   
23    @type key: string
24    @cvar key: name of security key in session object
25   
26    @type subKeys: tuple
27    @cvar subKeys: list of valid security keys to set h = session manager
28    address, sid = session ID, u = username, org = organisation where user is
29    logged in, roles = the roles the user is entitled to at org"""
30   
31    key = 'ndgSec'
32    subKeys = ('h', 'sid', 'u', 'org', 'roles')
33
34    def __init__(self, **subKeys):
35        """Update the security key of session object with the
36        input sub keys
37       
38        type **subKeys: dict
39        param **subKeys: set any of the security keywords as contained in
40        SecuritySession.subKeys"""
41       
42        # Set the security session keys from request.params if no keywords
43        # were input
44        if subKeys == {}:
45            subKeys = LoginServiceQuery.decodeRequestParams()
46           
47        # Ensure security key is present
48        if SecuritySession.key not in session:
49            session[SecuritySession.key] = {}
50         
51        # Ensure valid keys are being set   
52        for k in subKeys:
53            if k not in SecuritySession.subKeys:
54                raise KeyError, 'Invalid key Security session dict: "%s"' % k
55       
56        # Update security values
57        session[SecuritySession.key].update(subKeys)           
58        session.save()
59        log.debug("Set security session: %s" % session[SecuritySession.key])
60
61    @classmethod
62    def delete(self):
63        """Delete security key from session object"""
64        if SecuritySession.key in session:
65            del session[SecuritySession.key]
66            session.save()
67        log.debug("Deleted security key to session object: %s" % session)
68           
69setSecuritySession = SecuritySession
70           
71           
72class LoginServiceQueryError(Exception):
73    """Error handling for LoginServiceQuery - a class which handles the
74    parsing of security args in a HTTP GET request for the LoginService"""
75   
76class LoginServiceQuery(object):
77    """Create query string containing security credentials.  This is used by
78    the Identity Provider pass the credentials over a HTTP GET back to the
79    Service Provider
80   
81    @cvar keys: query args to be copied into security session dict
82    @type keys: tuple
83    @cvar roleSep: delimit roles names in URL arg with this symbol
84    @type roleSep: string
85    @cvar argSep: standard arg separator for URLs
86    @type argSep: string"""
87   
88    keys = SecuritySession.subKeys
89    rolesSep = ","
90    argSep = "&"
91       
92    def __str__(self):
93        """Provide convenient short-cut for call to make query string
94
95        @rtype: string
96        @return: URL query string with security args"""
97        return self.makeQueryStr()
98   
99    @classmethod
100    def makeQueryStr(cls):
101        """Create the query string containing the required security
102        credentials to return to the service provider
103       
104        @rtype: string
105        @return: URL query string with security args"""
106       
107        # Make a copy of the security session dict reseting the
108        # roles to a single string ready for passing over URL
109        secDict = session[SecuritySession.key].copy()
110        secDict['roles'] = cls.rolesSep.join(secDict['roles'])
111       
112        # Return the full query as a string
113        return cls.argSep.join(["%s=%s" % (k, secDict[k]) for k in cls.keys])
114
115    @classmethod
116    def stripFromURI(cls, *params):
117        """Make a new query string using Pylons request.params but stripping
118        args relating to security
119       
120        @param params: parameters to remove instead of those contained in keys
121        class variable
122        @type additionalParams: tuple
123        @rtype: string
124        @return: URL query string with security args removed"""
125        keys = params or cls.keys
126        return cls.argSep.join(['%s=%s' % (i, request.params[i]) \
127                                for i in request.params if i not in keys])
128
129    @classmethod
130    def decodeRequestParams(cls):
131        """Get security parameters from request.params received from Login
132        Service (IdP).  Decode parameters where necessary: roles are sent as a
133        comma delimited list - convert into a list type
134       
135        @rtype: dict
136        @return: dictionary of security parameters
137        """
138       
139        try:
140            # request.params is actually a MultiDict type but for the purposes
141            # of this code it can be treated as a regular dict type
142            keys = dict([(k, request.params[k]) for k in cls.keys])
143        except KeyError, e:
144            LoginServiceQueryError, \
145                '%s argument is missing from URL returned by Login Service' %\
146                str(e)
147               
148        # Modify roles from a comma delimited string into a list
149        if 'roles' in keys:
150            keys['roles'] = keys['roles'].split(cls.rolesSep)
151
152        return keys
153
154# TODO: this could be used in the future to replace parts of BaseController.
155# __call__ but leave for the moment as there may be a more modular solution
156def constructURL(pathInfo,
157                 scheme=None,
158                 netloc=None,
159                 altPathInfo='/discovery',
160                 query=None):
161    """Utility for BaseController.  Remove getCredentials calls"""
162 
163    if scheme is None and netloc is None:
164        pathPfx = g.server
165    else:
166        pathPfx = urlunsplit((scheme, netloc, '', '', ''))
167       
168    if 'getCredentials' in pathInfo:
169        logger.debug("Reverting request URL from getCredentials to discovery...")
170        requestURL = pathPfx + altPathInfo       
171    else:
172        requestURL = pathPfx + pathInfo
173        if query is None:
174            query='&'.join(["%s=%s"%item for item in request.params.items()])
175
176        if query:
177            requestURL += '?' + query
178           
179    return requestURL
180
181
182import sys
183
184class SecurityConfigError(Exception):
185    """Handle errors from parsing security config items"""
186       
187class SecurityConfig(object):
188    """Get Security related parameters from the Pylons NDG config file"""
189
190    def __init__(self, cfg=None):
191        '''Get PKI settings for Attribute Authority and Session Manager from
192        the configuration file
193       
194        @type param: pylons config file object
195        @param cfg: reference to NDG configuration file.  If omitted defaults
196        to request.environ['ndgConfig']'''
197       
198        if cfg is None:
199            cfg = request.environ['ndgConfig']
200
201        tracefileExpr = cfg.get('NDG_SECURITY', 'tracefile')
202        if tracefileExpr:
203            self.tracefile = eval(tracefileExpr)
204
205        self.smURI = cfg.get('NDG_SECURITY', 'sessionMgrURI')       
206        self.aaURI = cfg.get('NDG_SECURITY', 'attAuthorityURI')
207       
208        # ... for SSL connections to security web services
209        try:
210            self.sslCACertFilePathList = \
211            cfg.get('NDG_SECURITY', 'sslCACertFilePathList').split()
212               
213        except AttributeError:
214            raise SecurityConfigError, \
215                        'No "sslCACertFilePathList" security setting'
216
217        self.sslPeerCertCN = cfg.get('NDG_SECURITY', 'sslPeerCertCN')
218
219        # ...and for WS-Security digital signature
220        self.wssCertFilePath = cfg.get('NDG_SECURITY', 'wssCertFilePath')
221        self.wssPriKeyFilePath = cfg.get('NDG_SECURITY', 'wssKeyFilePath')
222        self.wssPriKeyPwd = cfg.get('NDG_SECURITY', 'wssKeyPwd')
223
224        try:
225            self.wssCACertFilePathList = \
226                cfg.get('NDG_SECURITY', 'wssCACertFilePathList').split()
227               
228        except AttributeError:
229            raise SecurityConfigError, \
230                                'No "wssCACertFilePathList" security setting'
231
232        # Gatekeeper params
233       
234        # Attribute Certificate Issuer
235        self.acIssuer = cfg.get('NDG_SECURITY', 'acIssuer')
236       
237        # verification of X.509 cert back to CA
238        self.acCACertFilePathList = cfg.get('NDG_SECURITY', 
239                                            'acCACertFilePathList')
240
241             
242    def __repr__(self):
243        return '\n'.join(["%s=%s" % (k,v) for k,v in self.__dict__.items() \
244                if k[:2] != "__"])
Note: See TracBrowser for help on using the repository browser.