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