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