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 | """ |
---|
6 | Utilities for transfor of credentials over login service |
---|
7 | |
---|
8 | @author: Philip Kershaw |
---|
9 | """ |
---|
10 | __revision__ = '$Id:$' |
---|
11 | |
---|
12 | import logging |
---|
13 | log = logging.getLogger(__name__) |
---|
14 | |
---|
15 | from pylons import session, request |
---|
16 | |
---|
17 | |
---|
18 | class SecuritySession(object): |
---|
19 | """Utility for Pylons security session keys enables correct |
---|
20 | keys to be set |
---|
21 | |
---|
22 | @type key: string |
---|
23 | @cvar key: name of security key in session object |
---|
24 | |
---|
25 | @type subKeys: tuple |
---|
26 | @cvar subKeys: list of valid security keys to set""" |
---|
27 | |
---|
28 | key = 'ndgSec' |
---|
29 | subKeys = ('h', 'sid', 'u', 'roles') |
---|
30 | |
---|
31 | def __init__(self, **subKeys): |
---|
32 | """Update the security key of session object with the |
---|
33 | input sub keys |
---|
34 | |
---|
35 | type **subKeys: dict |
---|
36 | param **subKeys: set any of the security keywords as contained in |
---|
37 | SecuritySession.subKeys""" |
---|
38 | |
---|
39 | # Set the security session keys from request.params if no keywords |
---|
40 | # were input |
---|
41 | if subKeys == {}: |
---|
42 | subKeys = LoginServiceQuery.decodeRequestParams() |
---|
43 | |
---|
44 | # Ensure security key is present |
---|
45 | if SecuritySession.key not in session: |
---|
46 | session[SecuritySession.key] = {} |
---|
47 | |
---|
48 | # Ensure valid keys are being set |
---|
49 | for k in subKeys: |
---|
50 | if k not in SecuritySession.subKeys: |
---|
51 | raise KeyError, 'Invalid key Security session dict: "%s"' % k |
---|
52 | |
---|
53 | # Update security values |
---|
54 | session[SecuritySession.key].update(subKeys) |
---|
55 | session.save() |
---|
56 | log.debug("Set security session: %s" % session[SecuritySession.key]) |
---|
57 | |
---|
58 | @classmethod |
---|
59 | def delete(self): |
---|
60 | """Delete security key from session object""" |
---|
61 | if SecuritySession.key in session: |
---|
62 | del session[SecuritySession.key] |
---|
63 | session.save() |
---|
64 | log.debug("Deleted security key to session object: %s" % session) |
---|
65 | |
---|
66 | setSecuritySession = SecuritySession |
---|
67 | |
---|
68 | |
---|
69 | class LoginServiceQuery(object): |
---|
70 | """Create query string containing security credentials. This is used by |
---|
71 | the Identity Provider pass the credentials over a HTTP GET back to the |
---|
72 | Service Provider""" |
---|
73 | |
---|
74 | keys = SecuritySession.subKeys |
---|
75 | rolesSep = "," |
---|
76 | argSep = "&" |
---|
77 | |
---|
78 | def __str__(self): |
---|
79 | """Provide convenient short-cut for call to make query string |
---|
80 | |
---|
81 | @rtype: string |
---|
82 | @return: URL query string with security args""" |
---|
83 | return self.makeQueryStr() |
---|
84 | |
---|
85 | @classmethod |
---|
86 | def makeQueryStr(cls): |
---|
87 | """Create the query string containing the required security |
---|
88 | credentials to return to the service provider |
---|
89 | |
---|
90 | @rtype: string |
---|
91 | @return: URL query string with security args""" |
---|
92 | |
---|
93 | # Make a copy of the security session dict reseting the |
---|
94 | # roles to a single string ready for passing over URL |
---|
95 | secDict = session[SecuritySession.key].copy() |
---|
96 | secDict['roles'] = cls.rolesSep.join(secDict['roles']) |
---|
97 | |
---|
98 | # Return the full query as a string |
---|
99 | return cls.argSep.join(["%s=%s" % (k, secDict[k]) for k in cls.keys]) |
---|
100 | |
---|
101 | @classmethod |
---|
102 | def stripFromURI(cls): |
---|
103 | """Make a new query string using Pylons request.params but stripping |
---|
104 | args relating to security |
---|
105 | |
---|
106 | @rtype: string |
---|
107 | @return: URL query string with security args removed""" |
---|
108 | return cls.argSep.join(['%s=%s' % (i, request.params[i]) \ |
---|
109 | for i in request.params if i not in cls.keys]) |
---|
110 | |
---|
111 | @classmethod |
---|
112 | def decodeRequestParams(cls): |
---|
113 | """Get security parameters from request.params received from Login |
---|
114 | Service (IdP). Decode parameters where necessary: roles are sent as a |
---|
115 | comma delimited list - convert into a list type |
---|
116 | |
---|
117 | @rtype: dict |
---|
118 | @return: dictionary of security parameters |
---|
119 | """ |
---|
120 | |
---|
121 | try: |
---|
122 | # request.params is actually a MultiDict type but for the purposes |
---|
123 | # of this code it can be treated as a regular dict type |
---|
124 | keys = dict([(k, request.params[k]) for k in cls.keys]) |
---|
125 | except KeyError, e: |
---|
126 | OwsError, \ |
---|
127 | '%s argument is missing from URL returned by Login Service' %\ |
---|
128 | str(e) |
---|
129 | |
---|
130 | # Modify roles from a comma delimited string into a list |
---|
131 | if 'roles' in keys: |
---|
132 | keys['roles'] = keys['roles'].split(cls.rolesSep) |
---|
133 | |
---|
134 | return keys |
---|
135 | |
---|
136 | import sys |
---|
137 | |
---|
138 | class SecurityConfigError(Exception): |
---|
139 | """Handle errors from parsing security config items""" |
---|
140 | |
---|
141 | class SecurityConfig(object): |
---|
142 | """Get Security related parameters from the Pylons NDG config file""" |
---|
143 | |
---|
144 | def __init__(self, cfg=None): |
---|
145 | '''Get PKI settings for Attribute Authority and Session Manager from |
---|
146 | the configuration file |
---|
147 | |
---|
148 | @type param: pylons config file object |
---|
149 | @param cfg: reference to NDG configuration file. If omitted defaults |
---|
150 | to request.environ['ndgConfig']''' |
---|
151 | |
---|
152 | if cfg is None: |
---|
153 | cfg = request.environ['ndgConfig'] |
---|
154 | |
---|
155 | tracefileExpr = cfg.get('NDG_SECURITY', 'tracefile') |
---|
156 | if tracefileExpr: |
---|
157 | self.tracefile = eval(tracefileExpr) |
---|
158 | |
---|
159 | self.smURI = cfg.get('NDG_SECURITY', 'sessionMgrURI') |
---|
160 | self.aaURI = cfg.get('NDG_SECURITY', 'attAuthorityURI') |
---|
161 | |
---|
162 | # ... for SSL connections to security web services |
---|
163 | try: |
---|
164 | self.sslCACertFilePathList = \ |
---|
165 | cfg.get('NDG_SECURITY', 'sslCACertFilePathList').split() |
---|
166 | |
---|
167 | except AttributeError: |
---|
168 | raise SecurityConfigError, \ |
---|
169 | 'No "sslCACertFilePathList" security setting' |
---|
170 | |
---|
171 | self.sslPeerCertCN = cfg.get('NDG_SECURITY', 'sslPeerCertCN') |
---|
172 | |
---|
173 | # ...and for WS-Security digital signature |
---|
174 | self.wssCertFilePath = cfg.get('NDG_SECURITY', 'wssCertFilePath') |
---|
175 | self.wssPriKeyFilePath = cfg.get('NDG_SECURITY', 'wssKeyFilePath') |
---|
176 | self.wssPriKeyPwd = cfg.get('NDG_SECURITY', 'wssKeyPwd') |
---|
177 | |
---|
178 | try: |
---|
179 | self.wssCACertFilePathList = \ |
---|
180 | cfg.get('NDG_SECURITY', 'wssCACertFilePathList').split() |
---|
181 | |
---|
182 | except AttributeError: |
---|
183 | raise SecurityConfigError, \ |
---|
184 | 'No "wssCACertFilePathList" security setting' |
---|
185 | |
---|
186 | # Gatekeeper params |
---|
187 | |
---|
188 | # Attribute Certificate Issuer |
---|
189 | self.acIssuer = cfg.get('NDG_SECURITY', 'acIssuer') |
---|
190 | |
---|
191 | # verification of X.509 cert back to CA |
---|
192 | self.acCACertFilePathList = cfg.get('NDG_SECURITY', |
---|
193 | 'acCACertFilePathList') |
---|
194 | |
---|
195 | |
---|
196 | def __repr__(self): |
---|
197 | return '\n'.join(["%s=%s" % (k,v) for k,v in self.__dict__.items() \ |
---|
198 | if k[:2] != "__"]) |
---|