source: TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/authninterface/sqlalchemy_authn.py @ 5869

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/authninterface/sqlalchemy_authn.py@5869
Revision 5869, 9.8 KB checked in by pjkersha, 10 years ago (diff)

Renaming to avoid import error.

Line 
1"""
2SQLAlchemy based Authentication interface for the OpenID Provider
3
4NERC DataGrid Project
5"""
6__author__ = "P J Kershaw"
7__date__ = "20/10/09"
8__copyright__ = "(C) 2009 Science and Technology Facilities Council"
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = '$Id: $'
12import logging
13log = logging.getLogger(__name__)
14try:
15    from hashlib import md5
16except ImportError:
17    # Allow for < Python 2.5
18    from md5 import md5
19
20import traceback
21from string import Template
22from sqlalchemy import create_engine, exc
23
24from ndg.security.server.wsgi.openid.provider.authninterface import \
25    AbstractAuthNInterface, AuthNInterfaceInvalidCredentials, \
26    AuthNInterfaceRetrieveError, AuthNInterfaceConfigError, \
27    AuthNInterfaceUsername2IdentifierMismatch
28
29
30class SQLAlchemyAuthnInterface(AbstractAuthNInterface):
31    '''Provide a database based Authentication interface to the OpenID Provider
32    making use of the SQLAlchemy database package'''
33   
34    getUserIdentifier = staticmethod(lambda identityURI: 
35                                     identityURI.rsplit('/')[-1])
36   
37    _str2Bool = lambda str: str.lower() in ["yes", "true", "t", "1"]
38    str2Bool = staticmethod(_str2Bool)
39   
40    CONNECTION_STRING_OPTNAME = 'connectionString'
41    LOGON_SQLQUERY_OPTNAME = 'logonSqlQuery'
42    USERNAME2USERIDENTIFIER_SQLQUERY_OPTNAME = 'username2UserIdentifierSqlQuery'
43    IS_MD5_ENCODED_PWD = 'isMD5EncodedPwd'
44   
45    __slots__ = (
46        CONNECTION_STRING_OPTNAME,
47        LOGON_SQLQUERY_OPTNAME,
48        USERNAME2USERIDENTIFIER_SQLQUERY_OPTNAME,
49        IS_MD5_ENCODED_PWD
50    )
51   
52    def __init__(self, **prop):
53        '''Instantiate object taking in settings from the input
54        properties'''
55        self.__connectionString = None
56        self.__logonSqlQuery = None
57        self.__username2UserIdentifierSqlQuery = None
58        self.__isMD5EncodedPwd = False
59       
60        try:
61            self.connectionString = prop.get(
62                            SQLAlchemyAuthnInterface.CONNECTION_STRING_OPTNAME)
63           
64            self.logonSqlQuery = prop.get(
65                            SQLAlchemyAuthnInterface.LOGON_SQLQUERY_OPTNAME)
66                     
67            self.username2UserIdentifierSqlQuery = prop.get(
68            SQLAlchemyAuthnInterface.USERNAME2USERIDENTIFIER_SQLQUERY_OPTNAME)
69 
70            self.isMD5EncodedPwd = prop.get(
71                            SQLAlchemyAuthnInterface.IS_MD5_ENCODED_PWD, 
72                            False)
73           
74        except TypeError, e:
75            raise AuthNInterfaceConfigError("Initialisation from keywords: %s"%
76                                            e)
77
78    def _getConnectionString(self):
79        return self.__connectionString
80
81    def _setConnectionString(self, value):
82        if not isinstance(value, basestring):
83            raise TypeError('Expecting string type for "connectionString" '
84                            'attribute; got %r' % type(value))
85        self.__connectionString = value
86
87    connectionString = property(fget=_getConnectionString, 
88                                fset=_setConnectionString, 
89                                doc="Database connection string")
90
91    def _getLogonSqlQuery(self):
92        return self.__logonSqlQuery
93
94    def _setLogonSqlQuery(self, value):
95        if not isinstance(value, basestring):
96            raise TypeError('Expecting string type for "logonSqlQuery" '
97                            'attribute; got %r' % type(value))
98        self.__logonSqlQuery = value
99
100    logonSqlQuery = property(fget=_getLogonSqlQuery, 
101                        fset=_setLogonSqlQuery, 
102                        doc="SQL Query for authentication request")
103
104    def _getUsername2UserIdentifierSqlQuery(self):
105        return self.__username2UserIdentifierSqlQuery
106
107    def _setUsername2UserIdentifierSqlQuery(self, value):
108        if not isinstance(value, basestring):
109            raise TypeError('Expecting string type for '
110                            '"username2UserIdentifierSqlQuery" attribute; '
111                            'got %r' % type(value))
112        self.__username2UserIdentifierSqlQuery = value
113
114    username2UserIdentifierSqlQuery = property(
115                                    fget=_getUsername2UserIdentifierSqlQuery, 
116                                    fset=_setUsername2UserIdentifierSqlQuery, 
117                                    doc="SQL Query for OpenID user identifier "
118                                        "look-up")
119   
120    def _getIsMD5EncodedPwd(self):
121        return self.__isMD5EncodedPwd
122
123    def _setIsMD5EncodedPwd(self, value):
124        if isinstance(value, bool):
125            self.__isMD5EncodedPwd = value
126        elif isinstance(value, basestring):
127            self.__isMD5EncodedPwd = SQLAlchemyAuthnInterface.str2Bool(value)
128        else:
129            raise TypeError('Expecting bool type for "isMD5EncodedPwd" '
130                            'attribute; got %r' % type(value))
131
132    isMD5EncodedPwd = property(fget=_getIsMD5EncodedPwd, 
133                               fset=_setIsMD5EncodedPwd,
134                               doc="Boolean set to True if password is MD5 "
135                                   "encrypted")
136
137    def logon(self, environ, identityURI, username, password):
138        """Interface login method
139       
140        @type environ: dict
141        @param environ: standard WSGI environ parameter
142
143        @type identityURI: basestring
144        @param identityURI: user's identity URL e.g.
145        'https://joebloggs.somewhere.ac.uk/'
146
147        @type username: basestring
148        @param username: username
149       
150        @type password: basestring
151        @param password: corresponding password for username givens
152       
153        @raise AuthNInterfaceInvalidCredentials: invalid username/password
154        @raise AuthNInterfaceUsername2IdentifierMismatch: no OpenID matching
155        the given username
156        @raise AuthNInterfaceConfigError: missing database engine plugin for
157        SQLAlchemy
158        """
159        if self.isMD5EncodedPwd:
160            try:
161                _password = md5(password).hexdigest()
162            except Exception, e:
163                raise AuthNInterfaceConfigError("%s exception raised making a "
164                                                "digest of the input "
165                                                "password: %s" % 
166                                                (type(e), 
167                                                 traceback.format_exc()))
168        else:
169            _password = password
170
171        try:
172            dbEngine = create_engine(self.connectionString)
173        except ImportError, e:
174            raise AuthNInterfaceConfigError("Missing database engine for "
175                                            "SQLAlchemy: %s" % e)
176        connection = dbEngine.connect()
177       
178        try:
179            queryInputs = dict(username=username, password=_password)
180            query = Template(self.logonSqlQuery).substitute(queryInputs)
181            result = connection.execute(query)
182
183        except exc.ProgrammingError:
184            raise AuthNInterfaceRetrieveError("Error with SQL Syntax: %s" %
185                                              traceback.format_exc())
186        finally:
187            connection.close()
188
189        if result.rowcount != 1:
190            raise AuthNInterfaceInvalidCredentials()
191
192    def logout(self):
193        """No special functionality is required for logout"""
194       
195    def username2UserIdentifiers(self, environ, username):
196        """Map the login username to an identifier which will become the
197        unique path suffix to the user's OpenID identifier.  The
198        OpenIDProviderMiddleware takes self.urls['id_url'] and adds it to this
199        identifier:
200       
201            identifier = self._authN.username2UserIdentifiers(environ,
202                                                              username)[0]
203            identifierKw = dict(userIdentifier=identifier)
204            identityURL = Template(self.urls['url_id'].substitute(identifierKw)
205       
206        @type environ: dict
207        @param environ: standard WSGI environ parameter
208
209        @type username: basestring
210        @param username: user identifier
211       
212        @rtype: tuple
213        @return: identifiers to be used to make OpenID user identity URLs.
214       
215        @raise AuthNInterfaceRetrieveError: error with retrieval of information
216        to identifier e.g. error with database look-up.
217        @raise AuthNInterfaceConfigError: missing database engine plugin for
218        SQLAlchemy
219        """
220
221        try:
222            dbEngine = create_engine(self.connectionString)
223        except ImportError, e:
224            raise AuthNInterfaceConfigError("Missing database engine for "
225                                            "SQLAlchemy: %s" % e)
226        connection = dbEngine.connect()
227       
228        try:
229            queryInputs = dict(username=username)
230            queryTmpl = Template(self.username2UserIdentifierSqlQuery)
231            query = queryTmpl.substitute(queryInputs)
232            result = connection.execute(query)
233
234        except exc.ProgrammingError:
235            raise AuthNInterfaceRetrieveError("Error with SQL Syntax: %s" %
236                                              traceback.format_exc())
237        finally:
238            connection.close()
239           
240        if result.rowcount == 0:
241            raise AuthNInterfaceInvalidCredentials('No entries for "%s" user' % 
242                                                  username)
243           
244        return tuple([i[0] for i in result.fetchall()])
245
246    def __getstate__(self):
247        '''Explicit pickling required with __slots__'''
248        return dict([(attrName, getattr(self, attrName)) \
249                     for attrName in SQLAlchemyAuthnInterface.__slots__])
250       
251    def __setstate__(self, attrDict):
252        '''Enable pickling for use with beaker.session'''
253        for attr, val in attrDict.items():
254            setattr(self, attr, val)           
Note: See TracBrowser for help on using the repository browser.