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