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