source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/sqlobject.py @ 4567

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/sqlobject.py@4567
Revision 4567, 12.9 KB checked in by pjkersha, 11 years ago (diff)

New package to hold Credential Repository implementations. Renamed SQLObject module in line with lower case module name conventions.

  • Property svn:keywords set to Id
Line 
1"""SQLObject Object Relational Mapper database interface for NDG Security
2CredentialRepository
3
4NERC Data Grid Project
5"""
6__author__ = "P J Kershaw"
7__date__ = "03/10/06"
8__copyright__ = "(C) 2007 STFC & NERC"
9__license__ = \
10"""This software may be distributed under the terms of the Q Public
11License, version 1.0 or later."""
12__contact__ = "Philip.Kershaw@stfc.ac.uk"
13__revision__ = '$Id$'
14warnings.warn("sqlobject based implementation to be replaced with SQLAlchemy "
15              "equivalent in a future release", 
16              DeprecationWarning, stacklevel=2)
17
18# SQLObject Database interface
19from sqlobject import *
20
21# For parsing of properties files
22
23# For parsing of properties file
24try: # python 2.5
25    from xml.etree import cElementTree as ElementTree
26except ImportError:
27    # if you've installed it yourself it comes this way
28    import cElementTree as ElementTree
29
30from CredentialWallet import CredentialRepository as CredentialRepositoryBase
31from CredentialWallet import CredentialRepositoryError
32
33
34class CredentialRepository(CredentialRepositoryBase):
35    """Interface to Credential Repository Database
36   
37    Nb. inherits from CredentialWallet.CredentialRepository to ensure correct
38    interface to the wallet"""
39
40    # valid configuration property keywords
41    __validKeys = ['dbURI']
42   
43   
44    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
45        """Initialise Credentials Repository Database object.
46
47        If the connection string or properties file is set a connection
48        will be made
49
50        dbURI:              <db type>://<username>:<passwd>@<hostname>/dbname
51        propFilePath: file path to properties file
52
53        Nb. propFilePath setting overrides input dbURI
54        """
55        warnings.warn("sqlobject based implementation to be replaced with "
56                      "SQLAlchemy equivalent in a future release", 
57                      DeprecationWarning, stacklevel=2)
58           
59        self.__con = None
60        self.__prop = {}
61       
62        if propFilePath is not None:
63           
64            # Read database URI set in file
65            self.readProperties(propFilePath, dbPPhrase=dbPPhrase)
66           
67        elif prop != {}:
68           
69            # Database URI may have been set as an input keyword argument
70            self.setProperties(dbPPhrase=dbPPhrase, **prop)
71
72    def __setConnection(self,
73                        dbType=None,
74                        dbUserName=None,
75                        dbPPhrase=None,
76                        dbHostname=None,
77                        dbName=None,
78                        dbURI=None,
79                        chkConnection=True):
80        """Establish a database connection from a database URI
81
82        pass a URI OR the parameters to construct the URI
83           
84        dbURI: "<db type>://<username>:<passwd>:<hostname>/dbname"
85
86        or
87
88        dbURI: "<db type>://<username>:%PPHRASE%:<hostname>/dbname"
89        + passPhrase
90
91        - %PPHRASE% is substituted with the input passPhrase keyword
92       
93        or
94       
95        dbType:         database type e.g. 'mysql'
96        dbUserName:     username
97        dbPPhrase:      pass-phrase
98        dbHostname:     name of host where database resides
99        dbName:         name of the database
100
101
102        chkConnection:  check that the URI is able to connect to the
103        """
104
105        try:
106            if dbURI:
107                # Check for pass-phrase variable set in URI '%PPHRASE%'
108                dbURIspl = dbURI.split('%')
109                if len(dbURIspl) == 3:
110                   
111                    if dbPPhrase is None:
112                        raise CredentialRepositoryError, "No database pass-phrase set"
113                   
114                    dbURI = dbURIspl[0] + dbPPhrase + dbURIspl[2]
115               
116            else:
117                # Construct URI from individual inputs
118                dbURI = dbType + '://' + dbUserName + ':' + dbPPhrase + \
119                        ':' + dbHostname + '/' + dbName
120        except Exception, e:
121            # Checking form missing keywords
122            raise CredentialRepositoryError, "Error creating database URI: %s" % e
123
124        try:
125            self.__con = connectionForURI(dbURI)
126        except Exception, e:
127            raise CredentialRepositoryError, "Error creating database connection: %s" % e
128
129        if chkConnection:
130            try:
131                self.__con.makeConnection()
132               
133            except Exception, e:
134                raise CredentialRepositoryError, \
135                        "Error connecting to Credential Repository: %s" % e
136
137           
138        # Copy the connection object into the table classes
139        CredentialRepository.UserID._connection = self.__con
140        CredentialRepository.UserCredential._connection = self.__con
141         
142
143    def setProperties(self, dbPPhrase=None, **prop):
144        """Update existing properties from an input dictionary
145        Check input keys are valid names"""
146       
147        for key in prop.keys():
148            if key not in self.__validKeys:
149                raise CredentialRepositoryError, "Property name \"%s\" is invalid" % key
150               
151        self.__prop.update(prop)
152
153
154        # Update connection setting
155        if 'dbURI' in prop:
156            self.__setConnection(dbURI=prop['dbURI'], dbPPhrase=dbPPhrase)
157
158
159    def readProperties(self,
160                       propFilePath=None,
161                       propElem=None,
162                       dbPPhrase=None):
163        """Read the configuration properties for the CredentialRepository
164
165        propFilePath|propElem
166
167        propFilePath: set to read from the specified file
168        propElem:     set to read beginning from a cElementTree node"""
169
170        if propFilePath is not None:
171
172            try:
173                tree = ElementTree.parse(propFilePath)
174                propElem = tree.getroot()
175               
176            except IOError, e:
177                raise CredentialRepositoryError, \
178                                "Error parsing properties file \"%s\": %s" % \
179                                (e.filename, e.strerror)
180
181            except Exception, e:
182                raise CredentialRepositoryError, \
183                                "Error parsing properties file \"%s\": %s" % \
184                                (propFilePath, str(e))
185
186        if propElem is None:
187            raise CredentialRepositoryError, \
188    "Error parsing properties file \"%s\": root element is not defined" % \
189                                propFilePath
190
191
192        # Read properties into a dictionary
193        prop = {}
194        for elem in propElem:
195                   
196            # Check for environment variables in file paths
197            tagCaps = elem.tag.upper()
198            if 'FILE' in tagCaps or 'PATH' in tagCaps or 'DIR' in tagCaps:
199                elem.text = os.path.expandvars(elem.text)
200
201            prop[elem.tag] = elem.text
202           
203        self.setProperties(dbPPhrase=dbPPhrase, **prop)
204
205           
206    def addUser(self, userName, dn):
207        """A new user to Credentials Repository"""
208        try:
209            self.UserID(userName=userName, dn=dn)
210
211        except Exception, e:
212            raise CredentialRepositoryError, "Error adding new user '%s': %s" % \
213                                                                (userName, e)
214
215    def auditCredentials(self, dn=None, **attCertValidKeys):
216        """Check the attribute certificates held in the repository and delete
217        any that have expired
218
219        dn:                Only audit for the given user distinguished Name.
220                           if not set, all records are audited
221        attCertValidKeys:  keywords which set how to check the Attribute
222                           Certificate e.g. check validity time, XML
223                           signature, version etc.  Default is check
224                           validity time only"""
225
226        if attCertValidKeys == {}:
227            # Default to check only the validity time
228            attCertValidKeys = {    'chkTime':          True,
229                                    'chkVersion':       False,
230                                    'chkProvenance':    False,
231                                    'chkSig':           False }
232           
233        try:
234            if dn:
235                # Only audit for the given user distinguished Name
236                credList = self.UserCredential.selectBy(dn=dn)
237            else:
238                # Audit all credentials
239                credList = self.UserCredential.select()
240           
241        except Exception, e:
242            raise CredentialRepositoryError,"Selecting credentials from repository: " + \
243                                 str(e)
244
245        # Iterate through list of credentials deleting records where the
246        # certificate is invalid
247        try:
248            for cred in credList:
249                attCert = AttCertParse(cred.attCert)
250               
251                if not attCert.isValid(**attCertValidKeys):
252                    self.UserCredential.delete(cred.id)
253                   
254        except Exception, e:
255            try:
256                raise CredentialRepositoryError, "Deleting credentials for '%s': %s" % \
257                                                       (cred.dn, e)
258            except:
259                raise CredentialRepositoryError, "Deleting credentials: %s" % e
260
261
262    def getCredentials(self, dn):
263        """Get the list of credentials for a given user's DN"""
264
265        try:
266            return self.UserCredential.selectBy(dn=dn)
267           
268        except Exception, e:
269            raise CredentialRepositoryError, "Selecting credentials for %s: %s" % (dn, e)
270
271
272    def addCredentials(self, dn, attCertList):
273        """Add new attribute certificates for a user.  The user must have
274        been previously registered in the repository
275
276        dn:             users Distinguished name
277        attCertList:   list of attribute certificates"""
278       
279        try:
280            userCred = self.UserID.selectBy(dn=dn)
281           
282            if userCred.count() == 0:
283                # Add a new user record HERE instead of at user registration
284                # time.  This decouples CredentialRepository from MyProxy and
285                # user registration process. Previously, a user not recognised
286                # exception would have been raised here.  'userName' field
287                # of UserID table is now perhaps superfluous.
288                #
289                # P J Kershaw 26/04/06
290                self.addUser(X500DN(dn)['CN'], dn)
291
292        except Exception, e:
293            raise CredentialRepositoryError, "Checking for user \"%s\": %s" % (dn, e)
294
295       
296        # Carry out check? - filter out certs in db where a new cert
297        # supercedes it - i.e. expires later and has the same roles
298        # assigned - May be too complicated to implement
299        #uniqAttCertList = [attCert for attCert in attCertList \
300        #    if min([attCert == cred.attCert for cred in userCred])]
301       
302               
303        # Update database with new entries
304        try:
305            for attCert in attCertList:
306                self.UserCredential(dn=dn, attCert=str(attCert))
307
308        except Exception, e:
309            raise CredentialRepositoryError, "Adding new user credentials for " + \
310                                  "user %s: %s" % (dn, str(e))
311
312
313    #_________________________________________________________________________   
314    def _initTables(self, prompt=True):
315        """Use with EXTREME caution - this method will initialise the database
316        tables removing any previous records entered"""
317 
318        if prompt:
319            resp = raw_input(\
320        "Are you sure you want to initialise the database tables? (yes/no) ")
321   
322            if resp.upper() != "YES":
323                print "Tables unchanged"
324                return
325       
326        self.UserID.createTable()
327        self.UserCredential.createTable()
328        print "Tables created"
329
330           
331    #_________________________________________________________________________
332    # Database tables defined using SQLObject derived classes
333    # Nb. These are class variables of the CredentialRepository class
334    class UserID(SQLObject):
335        """SQLObject derived class to define Credentials Repository db table
336        to store user information"""
337
338        # to be assigned to connectionForURI(<db URI>)
339        _connection = None
340
341        # Force table name
342        _table = "UserID"
343
344        userName = StringCol(dbName='userName', length=30)
345        dn = StringCol(dbName='dn', length=128)
346
347
348    class UserCredential(SQLObject):
349        """SQLObject derived class to define Credentials Repository db table
350        to store user credentials information"""
351
352        # to be assigned to connectionForURI(<db URI>)
353        _connection = None
354
355        # Force table name
356        _table = "UserCredential"
357
358       
359        # User name field binds with UserCredential table
360        dn = StringCol(dbName='dn', length=128)
361
362        # Store complete attribute certificate text
363        attCert = StringCol(dbName='attCert')
Note: See TracBrowser for help on using the repository browser.