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