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