1 | """NDG Security OpenID Authentication Interface to a Session Manager. |
---|
2 | |
---|
3 | This enables an OpenID Provider's signin to link to a Session Manager running |
---|
4 | in the same WSGI stack or else running as a separate service via the Session |
---|
5 | Manager SOAP interface |
---|
6 | |
---|
7 | NERC DataGrid Project |
---|
8 | """ |
---|
9 | __author__ = "P J Kershaw" |
---|
10 | __date__ = "01/08/08" |
---|
11 | __copyright__ = "(C) 2009 Science and Technology Facilities Council" |
---|
12 | __license__ = "BSD - see LICENSE file in top-level directory" |
---|
13 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |
---|
14 | __revision__ = "$Id$" |
---|
15 | import logging |
---|
16 | log = logging.getLogger(__name__) |
---|
17 | from string import Template |
---|
18 | from sqlalchemy import create_engine |
---|
19 | |
---|
20 | from ndg.security.server.wsgi.openid.provider import AbstractAuthNInterface, \ |
---|
21 | AuthNInterfaceConfigError, AuthNInterfaceInvalidCredentials, \ |
---|
22 | AuthNInterfaceUsername2IdentifierMismatch |
---|
23 | |
---|
24 | from ndg.security.server.wsgi.utils.sessionmanagerclient import \ |
---|
25 | WSGISessionManagerClient, AuthNServiceInvalidCredentials |
---|
26 | |
---|
27 | |
---|
28 | class SessionManagerOpenIDAuthNInterface(AbstractAuthNInterface): |
---|
29 | '''Authentication interface class for OpenIDProviderMiddleware to enable |
---|
30 | authentication to a Session Manager instance running in the same WSGI |
---|
31 | stack or via a SOAP call to a remote service |
---|
32 | |
---|
33 | @type dbParamNames: tuple |
---|
34 | @cvar dbParamNames: permitted config keywords. Nb. SQL queries takes |
---|
35 | String Template style '$' substitutions for username, password and OpenID |
---|
36 | identifier''' |
---|
37 | |
---|
38 | dbParamNames = ( |
---|
39 | 'connectionString', |
---|
40 | 'logonSQLQuery', |
---|
41 | 'userIdentifiersSQLQuery') |
---|
42 | |
---|
43 | def __init__(self, **prop): |
---|
44 | """Make any initial settings |
---|
45 | |
---|
46 | Settings are held in a dictionary which can be set from **prop, |
---|
47 | a call to setProperties() or by passing settings in an XML file |
---|
48 | given by propFilePath |
---|
49 | |
---|
50 | @type **prop: dict |
---|
51 | @param **prop: set properties via keywords |
---|
52 | @raise AuthNInterfaceConfigError: error with configuration |
---|
53 | """ |
---|
54 | try: |
---|
55 | for name in SessionManagerOpenIDAuthNInterface.dbParamNames: |
---|
56 | setattr(self, name, prop.pop(name)) |
---|
57 | |
---|
58 | except KeyError, e: |
---|
59 | raise AuthNInterfaceConfigError("Missing property setting for " |
---|
60 | "database connection: %s" % e) |
---|
61 | |
---|
62 | self._client = WSGISessionManagerClient(**prop) |
---|
63 | |
---|
64 | |
---|
65 | def logon(self, environ, userIdentifier, username, password): |
---|
66 | """Interface login method |
---|
67 | |
---|
68 | @type environ: dict |
---|
69 | @param environ: standard WSGI environ parameter |
---|
70 | |
---|
71 | @type username: basestring |
---|
72 | @param username: user identifier |
---|
73 | |
---|
74 | @type password: basestring |
---|
75 | @param password: corresponding password for username givens |
---|
76 | |
---|
77 | @raise AuthNInterfaceUsername2IdentifierMismatch: no OpenID |
---|
78 | identifiers match the given username |
---|
79 | @raise AuthNInterfaceInvalidCredentials: invalid username/password |
---|
80 | """ |
---|
81 | if userIdentifier is not None: |
---|
82 | # Check for a match between the OpenID user identifier and the |
---|
83 | # username |
---|
84 | try: |
---|
85 | dbEngine = create_engine(self.connectionString) |
---|
86 | connection = dbEngine.connect() |
---|
87 | except Exception, e: |
---|
88 | log.error('Connecting database for user logon query : %s' % e) |
---|
89 | raise |
---|
90 | |
---|
91 | try: |
---|
92 | try: |
---|
93 | queryInputs = dict(username=username, |
---|
94 | userIdentifier=userIdentifier) |
---|
95 | query=Template(self.logonSQLQuery).substitute(queryInputs) |
---|
96 | result = connection.execute(query) |
---|
97 | except Exception, e: |
---|
98 | log.error('Connecting database for user logon query : %s'% |
---|
99 | e) |
---|
100 | raise |
---|
101 | |
---|
102 | if not result.rowcount: |
---|
103 | raise AuthNInterfaceUsername2IdentifierMismatch() |
---|
104 | finally: |
---|
105 | connection.close() |
---|
106 | |
---|
107 | try: |
---|
108 | self._client.environ = environ |
---|
109 | self.sessionId = self._client.connect(username, |
---|
110 | passphrase=password)[-1] |
---|
111 | |
---|
112 | except AuthNServiceInvalidCredentials, e: |
---|
113 | log.exception(e) |
---|
114 | raise AuthNInterfaceInvalidCredentials() |
---|
115 | |
---|
116 | |
---|
117 | def username2UserIdentifiers(self, environ, username): |
---|
118 | """Map the login username to an identifier which will become the |
---|
119 | unique path suffix to the user's OpenID identifier. The |
---|
120 | OpenIDProviderMiddleware takes the ID URL template and adds it to this |
---|
121 | identifier e.g. |
---|
122 | |
---|
123 | identifier = self._authN.username2UserIdentifiers(username) |
---|
124 | identityURL = http://mysite/openid/${userIdentifier} |
---|
125 | |
---|
126 | @type environ: dict |
---|
127 | @param environ: standard WSGI environ parameter |
---|
128 | |
---|
129 | @type username: basestring |
---|
130 | @param username: user identifier |
---|
131 | |
---|
132 | @rtype: tuple |
---|
133 | @return: identifiers to be used to make OpenID user identity URLs. |
---|
134 | |
---|
135 | @raise AuthNInterfaceRetrieveError: error with retrieval of information |
---|
136 | to identifier e.g. error with database look-up. |
---|
137 | """ |
---|
138 | try: |
---|
139 | dbEngine = create_engine(self.connectionString) |
---|
140 | connection = dbEngine.connect() |
---|
141 | except Exception, e: |
---|
142 | log.error('Connecting database for user identifiers query : %s'%e) |
---|
143 | raise |
---|
144 | |
---|
145 | try: |
---|
146 | try: |
---|
147 | tmpl = Template(self.userIdentifiersSQLQuery) |
---|
148 | sqlQuery = tmpl.substitute(dict(username=username)) |
---|
149 | result = connection.execute(sqlQuery) |
---|
150 | if not result.rowcount: |
---|
151 | raise AuthNInterfaceRetrieveError() |
---|
152 | |
---|
153 | userIdentifiers = tuple([row.values()[0] for row in result]) |
---|
154 | except Exception, e: |
---|
155 | log.error('Querying database for user identifiers for user ' |
---|
156 | '"%s": %s' (username, e)) |
---|
157 | raise |
---|
158 | finally: |
---|
159 | connection.close() |
---|
160 | |
---|
161 | return userIdentifiers |
---|
162 | |
---|
163 | def logout(self): |
---|
164 | """logout from the Session Manager |
---|
165 | """ |
---|
166 | try: |
---|
167 | self._client.disconnect(sessID=self.sessionId) |
---|
168 | |
---|
169 | except Exception, e: |
---|
170 | log.exception(e) |
---|
171 | raise AuthNInterfaceInvalidCredentials() |
---|