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