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