b88a472e62f3cbb3180eda9f694fcb987679f97d
[sfa.git] / sfa / trust / auth.py
1 #
2 # SfaAPI authentication 
3 #
4 import sys
5
6 from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \
7     BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied
8 from sfa.util.sfalogging import logger
9 from sfa.util.config import Config
10 from sfa.util.xrn import Xrn, get_authority
11
12 from sfa.trust.gid import GID
13 from sfa.trust.rights import Rights
14 from sfa.trust.certificate import Keypair, Certificate
15 from sfa.trust.credential import Credential
16 from sfa.trust.trustedroots import TrustedRoots
17 from sfa.trust.hierarchy import Hierarchy
18 from sfa.trust.sfaticket import SfaTicket
19
20
21 class Auth:
22     """
23     Credential based authentication
24     """
25
26     def __init__(self, peer_cert = None, config = None ):
27         self.peer_cert = peer_cert
28         self.hierarchy = Hierarchy()
29         if not config:
30             self.config = Config()
31         self.load_trusted_certs()
32
33     def load_trusted_certs(self):
34         self.trusted_cert_list = TrustedRoots(self.config.get_trustedroots_dir()).get_list()
35         self.trusted_cert_file_list = TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
36
37         
38         
39     def checkCredentials(self, creds, operation, xrns=[]):
40         if not isinstance(xrns, list):
41             xrns = [xrns]
42         hrns = [Xrn(xrn).hrn for xrn in xrns] 
43         valid = []
44         if not isinstance(creds, list):
45             creds = [creds]
46         logger.debug("Auth.checkCredentials with %d creds"%len(creds))
47         for cred in creds:
48             for hrn in hrns:
49                 try:
50                     self.check(cred, operation, hrn)
51                     valid.append(cred)
52                 except:
53                     cred_obj=Credential(string=cred)
54                     logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
55                     error = sys.exc_info()[:2]
56                     continue
57             
58         if not len(valid):
59             raise InsufficientRights('Access denied: %s -- %s' % (error[0],error[1]))
60         
61         return valid
62         
63         
64     def check(self, cred, operation, hrn = None):
65         """
66         Check the credential against the peer cert (callerGID included 
67         in the credential matches the caller that is connected to the 
68         HTTPS connection, check if the credential was signed by a 
69         trusted cert and check if the credential is allowed to perform 
70         the specified operation.    
71         """
72         self.client_cred = Credential(string = cred)
73         self.client_gid = self.client_cred.get_gid_caller()
74         self.object_gid = self.client_cred.get_gid_object()
75         
76         # make sure the client_gid is not blank
77         if not self.client_gid:
78             raise MissingCallerGID(self.client_cred.get_subject())
79        
80         # validate the client cert if it exists
81         if self.peer_cert:
82             self.verifyPeerCert(self.peer_cert, self.client_gid)                   
83
84         # make sure the client is allowed to perform the operation
85         if operation:
86             if not self.client_cred.can_perform(operation):
87                 raise InsufficientRights(operation)
88
89         if self.trusted_cert_list:
90             self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
91         else:
92            raise MissingTrustedRoots(self.config.get_trustedroots_dir())
93        
94         # Make sure the credential's target matches the specified hrn. 
95         # This check does not apply to trusted peers 
96         trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
97         if hrn and self.client_gid.get_hrn() not in trusted_peers:
98             target_hrn = self.object_gid.get_hrn()
99             if not hrn == target_hrn:
100                 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
101                                        (target_hrn, hrn) )       
102         return True
103
104     def check_ticket(self, ticket):
105         """
106         Check if the tickt was signed by a trusted cert
107         """
108         if self.trusted_cert_list:
109             client_ticket = SfaTicket(string=ticket)
110             client_ticket.verify_chain(self.trusted_cert_list)
111         else:
112            raise MissingTrustedRoots(self.config.get_trustedroots_dir())
113
114         return True 
115
116     def verifyPeerCert(self, cert, gid):
117         # make sure the client_gid matches client's certificate
118         if not cert.is_pubkey(gid.get_pubkey()):
119             raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())            
120
121     def verifyGidRequestHash(self, gid, hash, arglist):
122         key = gid.get_pubkey()
123         if not key.verify_string(str(arglist), hash):
124             raise BadRequestHash(hash)
125
126     def verifyCredRequestHash(self, cred, hash, arglist):
127         gid = cred.get_gid_caller()
128         self.verifyGidRequestHash(gid, hash, arglist)
129
130     def validateGid(self, gid):
131         if self.trusted_cert_list:
132             gid.verify_chain(self.trusted_cert_list)
133
134     def validateCred(self, cred):
135         if self.trusted_cert_list:
136             cred.verify(self.trusted_cert_file_list)
137
138     def authenticateGid(self, gidStr, argList, requestHash=None):
139         gid = GID(string = gidStr)
140         self.validateGid(gid)
141         # request_hash is optional
142         if requestHash:
143             self.verifyGidRequestHash(gid, requestHash, argList)
144         return gid
145
146     def authenticateCred(self, credStr, argList, requestHash=None):
147         cred = Credential(string = credStr)
148         self.validateCred(cred)
149         # request hash is optional
150         if requestHash:
151             self.verifyCredRequestHash(cred, requestHash, argList)
152         return cred
153
154     def authenticateCert(self, certStr, requestHash):
155         cert = Certificate(string=certStr)
156         # xxx should be validateCred ??
157         self.validateCred(cert)   
158
159     def gidNoop(self, gidStr, value, requestHash):
160         self.authenticateGid(gidStr, [gidStr, value], requestHash)
161         return value
162
163     def credNoop(self, credStr, value, requestHash):
164         self.authenticateCred(credStr, [credStr, value], requestHash)
165         return value
166
167     def verify_cred_is_me(self, credential):
168         is_me = False 
169         cred = Credential(string=credential)
170         caller_gid = cred.get_gid_caller()
171         caller_hrn = caller_gid.get_hrn()
172         if caller_hrn != self.config.SFA_INTERFACE_HRN:
173             raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
174
175         return   
176         
177     def get_auth_info(self, auth_hrn):
178         """
179         Given an authority name, return the information for that authority.
180         This is basically a stub that calls the hierarchy module.
181         
182         @param auth_hrn human readable name of authority  
183         """
184
185         return self.hierarchy.get_auth_info(auth_hrn)
186
187
188     def veriry_auth_belongs_to_me(self, name):
189         """
190         Verify that an authority belongs to our hierarchy. 
191         This is basically left up to the implementation of the hierarchy
192         module. If the specified name does not belong, ane exception is 
193         thrown indicating the caller should contact someone else.
194
195         @param auth_name human readable name of authority
196         """
197
198         # get auth info will throw an exception if the authority doesnt exist
199         self.get_auth_info(name)
200
201
202     def verify_object_belongs_to_me(self, name):
203         """
204         Verify that an object belongs to our hierarchy. By extension,
205         this implies that the authority that owns the object belongs
206         to our hierarchy. If it does not an exception is thrown.
207     
208         @param name human readable name of object        
209         """
210         auth_name = self.get_authority(name)
211         if not auth_name:
212             auth_name = name 
213         if name == self.config.SFA_INTERFACE_HRN:
214             return
215         self.verify_auth_belongs_to_me(auth_name) 
216              
217     def verify_auth_belongs_to_me(self, name):
218         # get auth info will throw an exception if the authority doesnt exist
219         self.get_auth_info(name) 
220
221
222     def verify_object_permission(self, name):
223         """
224         Verify that the object gid that was specified in the credential
225         allows permission to the object 'name'. This is done by a simple
226         prefix test. For example, an object_gid for plc.arizona would 
227         match the objects plc.arizona.slice1 and plc.arizona.
228     
229         @param name human readable name to test  
230         """
231         object_hrn = self.object_gid.get_hrn()
232         if object_hrn == name:
233             return
234         if name.startswith(object_hrn + "."):
235             return
236         #if name.startswith(get_authority(name)):
237             #return
238     
239         raise PermissionError(name)
240
241     def determine_user_rights(self, caller_hrn, reg_record):
242         """
243         Given a user credential and a record, determine what set of rights the
244         user should have to that record.
245         
246         This is intended to replace determine_user_rights() and
247         verify_cancreate_credential()
248         """
249
250         rl = Rights()
251         type = reg_record.type
252
253         logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
254
255         if type == 'slice':
256             # researchers in the slice are in the DB as-is
257             researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
258             # locating PIs attached to that slice
259             slice_pis=reg_record.get_pis()
260             pi_hrns = [ user.hrn for user in slice_pis ]
261             if (caller_hrn in researcher_hrns + pi_hrns):
262                 rl.add('refresh')
263                 rl.add('embed')
264                 rl.add('bind')
265                 rl.add('control')
266                 rl.add('info')
267
268         elif type == 'authority':
269             pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
270             if (caller_hrn == self.config.SFA_INTERFACE_HRN):
271                 rl.add('authority')
272                 rl.add('sa')
273                 rl.add('ma')
274             if (caller_hrn in pi_hrns):
275                 rl.add('authority')
276                 rl.add('sa')
277             # NOTE: for the PL implementation, this 'operators' list 
278             # amounted to users with 'tech' role in that site 
279             # it seems like this is not needed any longer, so for now I just drop that
280             # operator_hrns = reg_record.get('operator',[])
281             # if (caller_hrn in operator_hrns):
282             #    rl.add('authority')
283             #    rl.add('ma')
284
285         elif type == 'user':
286             rl.add('refresh')
287             rl.add('resolve')
288             rl.add('info')
289
290         elif type == 'node':
291             rl.add('operator')
292
293         return rl
294
295     def get_authority(self, hrn):
296         return get_authority(hrn)
297
298     def filter_creds_by_caller(self, creds, caller_hrn_list):
299         """
300         Returns a list of creds who's gid caller matches the 
301         specified caller hrn
302         """
303         if not isinstance(creds, list):
304             creds = [creds]
305         creds = []
306         if not isinstance(caller_hrn_list, list):
307             caller_hrn_list = [caller_hrn_list]
308         for cred in creds:
309             try:
310                 tmp_cred = Credential(string=cred)
311                 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]:
312                     creds.append(cred)
313             except: pass
314         return creds
315