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