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