source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/pylons/security_util.py @ 3918

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/pylons/security_util.py@3918
Revision 3918, 8.1 KB checked in by pjkersha, 12 years ago (diff)

Initial Integration of Single Sign On Service with OpenID and Pylons AuthKit?:

  • WAYF now contains an OpenID textbox for sign in
  • No role integration carried out yet - OpenID has no better privileges than an anonymous user(!)
  • Integrated into Authkit - requires lots of config settings in pylons ini file
  • HTTP 401 error get redirected automatically to WAYF
  • Need to create an AuthKit? egg from SVN 151 checkout - will put on NDG dist
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(dict):
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):
35        '''Initialize security dict key in session object'''
36        if SecuritySession.key not in session:
37            session[SecuritySession.key] = {}.fromkeys(SecuritySession.subKeys)
38           
39        session[SecuritySession.key]['roles'] = []
40           
41    def set(self, **subKeys):
42        """Update the security key of session object with the
43        input sub keys
44       
45        type **subKeys: dict
46        param **subKeys: set any of the security keywords as contained in
47        SecuritySession.subKeys"""
48       
49        # Set the security session keys from request.params if no keywords
50        # were input
51        if subKeys == {}:
52            subKeys = SSOServiceQuery.decodeRequestParams()
53           
54        # Ensure security key is present
55        if SecuritySession.key not in session:
56            session[SecuritySession.key] = {}
57         
58        # Ensure valid keys are being set   
59        for k in subKeys:
60            if k not in SecuritySession.subKeys:
61                raise KeyError('Invalid key Security session dict: "%s"' % k)
62       
63        # Update security values
64        session[SecuritySession.key].update(subKeys)           
65        session.save()
66        log.debug("Set security session: %s" % session[SecuritySession.key])
67
68    def __delitem__(self, key):
69        "Keys cannot be removed"       
70        raise KeyError('Keys cannot be deleted from security session')
71
72    def __getitem__(self, key):
73        '''data dictionary overload'''
74        if key not in session[SecuritySession.key]:
75            raise KeyError("Invalid key '%s'" % key)
76       
77        return session[SecuritySession.key][key]
78       
79    def __setitem__(self, key, item):
80        '''data dictionary overload'''
81        if key not in SecuritySession.subKeys:
82            raise KeyError("Invalid security key %s" % key)
83       
84        session[SecuritySession.key][key] = item
85           
86    def get(self, kw):
87        '''data dictionary overload'''
88        return session[SecuritySession.key][kw]
89
90    def clear(self):
91        '''data dictionary overload'''
92        raise KeyError("Data cannot be cleared from security session")
93   
94    def keys(self):
95        '''data dictionary overload'''
96        return session[SecuritySession.key].keys()
97
98    def items(self):
99        '''data dictionary overload'''
100        return session[SecuritySession.key].items()
101
102    def values(self):
103        return session[SecuritySession.key].values()
104
105    def has_key(self, key):
106        return session[SecuritySession.key].has_key(key)
107
108    # 'in' operator
109    def __contains__(self, key):
110        return key in session[SecuritySession.key]
111
112   
113    @classmethod
114    def save(self):
115        session.save()
116       
117    @classmethod
118    def delete(self):
119        """Delete security key from session object"""
120        if SecuritySession.key in session:
121            del session[SecuritySession.key]
122            session.save()
123        log.debug("Deleted security key to session object: %s" % session)
124
125           
126def setSecuritySession(**kw):
127    '''Convenience wrapper to SecuritySession and it's set method'''
128    SecuritySession().set(**kw)
129   
130           
131class LoginServiceQueryError(Exception):
132    """Error handling for SSOServiceQuery - a class which handles the
133    parsing of security args in a HTTP GET request for the LoginService"""
134   
135class SSOServiceQuery(object):
136    """Create query string containing security credentials.  This is used by
137    the Identity Provider pass the credentials over a HTTP GET back to the
138    Service Provider
139   
140    @cvar keys: query args to be copied into security session dict
141    @type keys: tuple
142    @cvar roleSep: delimit roles names in URL arg with this symbol
143    @type roleSep: string
144    @cvar argSep: standard arg separator for URLs
145    @type argSep: string"""
146   
147    keys = SecuritySession.subKeys
148    rolesSep = ","
149    argSep = "&"
150       
151    def __str__(self):
152        """Provide convenient short-cut for call to make query string
153
154        @rtype: string
155        @return: URL query string with security args"""
156        return self.makeQueryStr()
157   
158    @classmethod
159    def makeQueryStr(cls):
160        """Create the query string containing the required security
161        credentials to return to the service provider
162       
163        @rtype: string
164        @return: URL query string with security args"""
165       
166        # Make a copy of the security session dict reseting the
167        # roles to a single string ready for passing over URL
168        secDict = session[SecuritySession.key].copy()
169        secDict['roles'] = cls.rolesSep.join(secDict['roles'])
170       
171        # Return the full query as a string
172        return cls.argSep.join(["%s=%s" % (k, secDict[k]) for k in cls.keys])
173
174    @classmethod
175    def stripFromURI(cls, *params):
176        """Make a new query string using Pylons request.params but stripping
177        args relating to security
178       
179        @param params: parameters to remove instead of those contained in keys
180        class variable
181        @type additionalParams: tuple
182        @rtype: string
183        @return: URL query string with security args removed"""
184        keys = params or cls.keys
185        return str(cls.argSep.join(['%s=%s' % (i, request.params[i]) \
186                                for i in request.params if i not in keys]))
187
188    @classmethod
189    def decodeRequestParams(cls):
190        """Get security parameters from request.params received from Login
191        Service (IdP).  Decode parameters where necessary: roles are sent as a
192        comma delimited list - convert into a list type
193       
194        @rtype: dict
195        @return: dictionary of security parameters
196        """
197       
198        try:
199            # request.params is actually a MultiDict type but for the purposes
200            # of this code it can be treated as a regular dict type
201            keys = dict([(k, request.params[k]) for k in cls.keys])
202        except KeyError, e:
203            LoginServiceQueryError, \
204                '%s argument is missing from URL returned by Login Service' %\
205                str(e)
206               
207        # Modify roles from a comma delimited string into a list
208        if 'roles' in keys:
209            keys['roles'] = keys['roles'].split(cls.rolesSep)
210
211        return keys
212
213# Backwards compatibility
214LoginServiceQuery = SSOServiceQuery
215
216# TODO: this could be used in the future to replace parts of BaseController.
217# __call__ but leave for the moment as there may be a more modular solution
218def constructURL(pathInfo,
219                 scheme=None,
220                 netloc=None,
221                 altPathInfo='/discovery',
222                 query=None):
223    """Utility for BaseController.  Remove getCredentials calls"""
224 
225    if scheme is None and netloc is None:
226        pathPfx = g.server
227    else:
228        pathPfx = urlunsplit((scheme, netloc, '', '', ''))
229       
230    if 'getCredentials' in pathInfo:
231        logger.debug("Reverting request URL from getCredentials to discovery...")
232        requestURL = pathPfx + altPathInfo       
233    else:
234        requestURL = pathPfx + pathInfo
235        if query is None:
236            query='&'.join(["%s=%s"%item for item in request.params.items()])
237
238        if query:
239            requestURL += '?' + query
240           
241    return requestURL
Note: See TracBrowser for help on using the repository browser.