1 | """NDG Security Basic OpenID Authentication Interface. |
---|
2 | |
---|
3 | A demonstration implementation of an authentication interface for |
---|
4 | OpenIDProviderMiddleware WSGI. Username/password and OpenId user identifier |
---|
5 | details are read from a config file and passed as keywords. This class is not |
---|
6 | intended for production use. |
---|
7 | |
---|
8 | NERC DataGrid Project |
---|
9 | |
---|
10 | """ |
---|
11 | __author__ = "P J Kershaw" |
---|
12 | __date__ = "01/08/08" |
---|
13 | __copyright__ = "(C) 2009 Science and Technology Facilities Council" |
---|
14 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |
---|
15 | __revision__ = "$Id: $" |
---|
16 | import logging |
---|
17 | log = logging.getLogger(__name__) |
---|
18 | |
---|
19 | from ndg.security.server.wsgi.openid.provider.authninterface import \ |
---|
20 | AbstractAuthNInterface, AuthNInterfaceInvalidCredentials, \ |
---|
21 | AuthNInterfaceRetrieveError, AuthNInterfaceConfigError, \ |
---|
22 | AuthNInterfaceUsername2IdentifierMismatch |
---|
23 | |
---|
24 | |
---|
25 | class BasicAuthNInterface(AbstractAuthNInterface): |
---|
26 | '''Basic Authentication interface class for OpenIDProviderMiddleware |
---|
27 | |
---|
28 | it uses username/password details retrieved from config file / keyword |
---|
29 | entry. This class is for testing only. NOT for production use''' |
---|
30 | |
---|
31 | IDENTITY_URI_TMPL_KEYNAME = 'identityUriTemplate' |
---|
32 | USERCREDS_PROPERTY_KEYNAME = 'userCreds' |
---|
33 | USERCREDS_KEYNAMES = ('password', 'identifiers') |
---|
34 | |
---|
35 | propertyKeyNames = ( |
---|
36 | USERCREDS_PROPERTY_KEYNAME |
---|
37 | ) |
---|
38 | |
---|
39 | getUserIdentifier = staticmethod(lambda identityURI: |
---|
40 | identityURI.rsplit('/')[-1]) |
---|
41 | |
---|
42 | def __init__(self, **prop): |
---|
43 | """Make any initial settings |
---|
44 | |
---|
45 | Settings are held in a dictionary which can be set from **prop, |
---|
46 | a call to setProperties() or by passing settings in an XML file |
---|
47 | given by propFilePath |
---|
48 | |
---|
49 | @type **prop: dict |
---|
50 | @param **prop: set properties via keywords |
---|
51 | @raise AuthNInterfaceConfigError: error with configuration |
---|
52 | """ |
---|
53 | # Test/Admin username/password set from ini/kw args |
---|
54 | self._identityUriTemplate = prop.get( |
---|
55 | BasicAuthNInterface.IDENTITY_URI_TMPL_KEYNAME) |
---|
56 | userCredsField = prop.get( |
---|
57 | BasicAuthNInterface.USERCREDS_PROPERTY_KEYNAME) |
---|
58 | if not userCredsField: |
---|
59 | raise AuthNInterfaceConfigError('No "%s" config option found' % |
---|
60 | BasicAuthNInterface.USERCREDS_PROPERTY_KEYNAME) |
---|
61 | |
---|
62 | self._userCreds = {} |
---|
63 | for userEntry in userCredsField.split(): |
---|
64 | # Split username, password and OpenID name list |
---|
65 | userCreds = userEntry.strip().split(':') |
---|
66 | |
---|
67 | # Split OpenID name list |
---|
68 | userCreds[-1] = tuple(userCreds[-1].split(',')) |
---|
69 | |
---|
70 | # Convert into a dictionary indexed by username |
---|
71 | userCredsKeys = BasicAuthNInterface.USERCREDS_KEYNAMES |
---|
72 | self._userCreds[userCreds[0]] = dict(zip(userCredsKeys, |
---|
73 | userCreds[1:])) |
---|
74 | |
---|
75 | def logon(self, environ, identityURI, username, password): |
---|
76 | """Interface login method |
---|
77 | |
---|
78 | @type environ: dict |
---|
79 | @param environ: standard WSGI environ parameter |
---|
80 | |
---|
81 | @type identityURI: basestring |
---|
82 | @param identityURI: user's identity URL e.g. |
---|
83 | 'https://joebloggs.somewhere.ac.uk/' |
---|
84 | |
---|
85 | @type username: basestring |
---|
86 | @param username: username |
---|
87 | |
---|
88 | @type password: basestring |
---|
89 | @param password: corresponding password for username givens |
---|
90 | |
---|
91 | @raise AuthNInterfaceInvalidCredentials: invalid username/password |
---|
92 | @raise AuthNInterfaceUsername2IdentifierMismatch: no OpenID matching |
---|
93 | the given username |
---|
94 | """ |
---|
95 | if self._userCreds.get(username, {}).get('password') != password: |
---|
96 | raise AuthNInterfaceInvalidCredentials() |
---|
97 | |
---|
98 | # Assume identifier is at the end of the URI |
---|
99 | if identityURI is not None: |
---|
100 | userIdentifier = BasicAuthNInterface.getUserIdentifier(identityURI) |
---|
101 | |
---|
102 | if userIdentifier not in self._userCreds[username]['identifiers']: |
---|
103 | raise AuthNInterfaceUsername2IdentifierMismatch() |
---|
104 | |
---|
105 | def logout(self): |
---|
106 | pass |
---|
107 | |
---|
108 | def username2UserIdentifiers(self, environ, username): |
---|
109 | """Map the login username to an identifier which will become the |
---|
110 | unique path suffix to the user's OpenID identifier. The |
---|
111 | OpenIDProviderMiddleware takes self.urls['id_url'] and adds it to this |
---|
112 | identifier: |
---|
113 | |
---|
114 | identifier = self._authN.username2UserIdentifiers(environ,username) |
---|
115 | identityURL = self.urls['url_id'] + '/' + identifier |
---|
116 | |
---|
117 | @type environ: dict |
---|
118 | @param environ: standard WSGI environ parameter |
---|
119 | |
---|
120 | @type username: basestring |
---|
121 | @param username: user identifier |
---|
122 | |
---|
123 | @rtype: tuple |
---|
124 | @return: identifiers to be used to make OpenID user identity URLs. |
---|
125 | |
---|
126 | @raise AuthNInterfaceRetrieveError: error with retrieval of information |
---|
127 | to identifier e.g. error with database look-up. |
---|
128 | """ |
---|
129 | try: |
---|
130 | return self._userCreds[username]['identifiers'] |
---|
131 | except KeyError: |
---|
132 | raise AuthNInterfaceRetrieveError('No entries for "%s" user' % |
---|
133 | username) |
---|
134 | |
---|
135 | |
---|
136 | from ndg.security.server.wsgi.utils.sessionmanagerclient import \ |
---|
137 | WSGISessionManagerClient, AuthNServiceInvalidCredentials |
---|
138 | |
---|
139 | class BasicSessionManagerOpenIDAuthNInterface(BasicAuthNInterface): |
---|
140 | '''Authentication interface class for OpenIDProviderMiddleware to enable |
---|
141 | authentication to a Session Manager instance running in the same WSGI |
---|
142 | stack or via a SOAP call to a remote service. This is a basic test |
---|
143 | interface. See sessionmanager module for a full implementation linking to |
---|
144 | a database via SQLAlchemy |
---|
145 | ''' |
---|
146 | |
---|
147 | def __init__(self, **prop): |
---|
148 | """Extends BasicAuthNInterface initialising Session Manager Client |
---|
149 | |
---|
150 | @type **prop: dict |
---|
151 | @param **prop: set properties via keywords |
---|
152 | @raise AuthNInterfaceConfigError: error with configuration |
---|
153 | """ |
---|
154 | user2Identifier = prop.pop('username2UserIdentifiers') |
---|
155 | if user2Identifier: |
---|
156 | self._username2Identifier = {} |
---|
157 | for i in user2Identifier.split(): |
---|
158 | username, identifierStr = i.strip().split(':') |
---|
159 | identifiers = tuple(identifierStr.split(',')) |
---|
160 | self._username2Identifier[username] = identifiers |
---|
161 | else: |
---|
162 | raise AuthNInterfaceConfigError('No "user2Identifier" config ' |
---|
163 | 'option found') |
---|
164 | |
---|
165 | self._client = WSGISessionManagerClient(**prop) |
---|
166 | |
---|
167 | # This is set at login |
---|
168 | self.sessionId = None |
---|
169 | |
---|
170 | def logon(self, environ, userIdentifier, username, password): |
---|
171 | """Interface login method |
---|
172 | |
---|
173 | @type environ: dict |
---|
174 | @param environ: standard WSGI environ parameter |
---|
175 | |
---|
176 | @type username: basestring |
---|
177 | @param username: user identifier |
---|
178 | |
---|
179 | @type password: basestring |
---|
180 | @param password: corresponding password for username givens |
---|
181 | |
---|
182 | @raise AuthNInterfaceUsername2IdentifierMismatch: no OpenID |
---|
183 | identifiers match the given username |
---|
184 | @raise AuthNInterfaceInvalidCredentials: invalid username/password |
---|
185 | """ |
---|
186 | if userIdentifier is not None and \ |
---|
187 | userIdentifier not in self._username2Identifier.get(username): |
---|
188 | raise AuthNInterfaceUsername2IdentifierMismatch() |
---|
189 | |
---|
190 | try: |
---|
191 | self._client.environ = environ |
---|
192 | connectResp = self._client.connect(username, passphrase=password) |
---|
193 | self.sessionId = connectResp[-1] |
---|
194 | log.debug("Connected to Session Manager with session ID: %s", |
---|
195 | self.sessionId) |
---|
196 | |
---|
197 | except AuthNServiceInvalidCredentials, e: |
---|
198 | log.exception(e) |
---|
199 | raise AuthNInterfaceInvalidCredentials() |
---|
200 | |
---|
201 | def logout(self): |
---|
202 | """logout from the Session Manager |
---|
203 | """ |
---|
204 | try: |
---|
205 | self._client.disconnect(sessID=self.sessionId) |
---|
206 | |
---|
207 | except Exception, e: |
---|
208 | log.exception(e) |
---|
209 | raise AuthNInterfaceInvalidCredentials() |
---|