Merge branch 'master' into senslab2
[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 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, hrn = None):
40         valid = []
41         if not isinstance(creds, list):
42             creds = [creds]
43         #print>>sys.stderr, "\r\n \r\n \t AUTH.PY checkCredentials hrn %s" %(hrn)
44         logger.debug("Auth.checkCredentials with %d creds"%len(creds))
45         for cred in creds:
46             try:
47                 self.check(cred, operation, hrn)
48                 valid.append(cred)
49             except:
50                 cred_obj=Credential(string=cred)
51                 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
52                 error = sys.exc_info()[:2]
53                 continue
54             
55         if not len(valid):
56             raise InsufficientRights('Access denied: %s -- %s' % (error[0],error[1]))
57         
58         return valid
59         
60         
61     def check(self, cred, operation, hrn = None):
62         """
63         Check the credential against the peer cert (callerGID included 
64         in the credential matches the caller that is connected to the 
65         HTTPS connection, check if the credential was signed by a 
66         trusted cert and check if the credential is allowed to perform 
67         the specified operation.    
68         """
69         self.client_cred = Credential(string = cred)
70         self.client_gid = self.client_cred.get_gid_caller()
71         self.object_gid = self.client_cred.get_gid_object()
72         #print>>sys.stderr, " \r\n \r\n \t AUTH.PY check client_gid %s  hrn %s object_gid %s" %(self.client_gid.get_hrn(),hrn, self.object_gid.get_hrn())
73         # make sure the client_gid is not blank
74         if not self.client_gid:
75             raise MissingCallerGID(self.client_cred.get_subject())
76        
77         # validate the client cert if it exists
78         if self.peer_cert:
79             self.verifyPeerCert(self.peer_cert, self.client_gid)                   
80
81         # make sure the client is allowed to perform the operation
82         if operation:    
83             #print>>sys.stderr, " \r\n \r\n \t AUTH.PY check operation %s trusted_cert_list %s " %(operation,self.trusted_cert_list)
84             if not self.client_cred.can_perform(operation):
85                 #print>>sys.stderr, " \r\n \r\n \t AUTH.PY InsufficientRights(operation)"
86                 raise InsufficientRights(operation)
87
88         if self.trusted_cert_list:
89             self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
90             #print>>sys.stderr, " \r\n \r\n \t AUTH.PY check  trusted_cert_file_list %s  self.config.SFA_CREDENTIAL_SCHEMA %s" %(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
91             
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         #print>>sys.stderr, " \r\n \r\n \t AUTH.PY check trusted_peers ", trusted_peers
99         if hrn and self.client_gid.get_hrn() not in trusted_peers:
100             
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         #strname = str(name).strip("['']")
236         if object_hrn == name:
237         #if object_hrn == strname:
238             return 
239         if name.startswith(object_hrn + ".") :
240         #if strname.startswith((object_hrn + ".")) is True:
241             return
242         #if name.startswith(get_authority(name)):
243             #return
244
245         raise PermissionError(name)
246
247     def determine_user_rights(self, caller_hrn, record):
248         """
249         Given a user credential and a record, determine what set of rights the
250         user should have to that record.
251         
252         This is intended to replace determine_rights() and
253         verify_cancreate_credential()
254         """
255
256         rl = Rights()
257         type = record['type']
258
259
260         if type=="slice":
261             researchers = record.get("researcher", [])
262             pis = record.get("PI", [])
263             if (caller_hrn in researchers + pis):
264                 rl.add("refresh")
265                 rl.add("embed")
266                 rl.add("bind")
267                 rl.add("control")
268                 rl.add("info")
269
270         elif type == "authority":
271             pis = record.get("PI", [])
272             operators = record.get("operator", [])
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 pis):
278                 rl.add("authority")
279                 rl.add("sa")
280             if (caller_hrn in operators):
281                 rl.add("authority")
282                 rl.add("ma")
283
284         elif type == "user":
285             rl.add("refresh")
286             rl.add("resolve")
287             rl.add("info")
288
289         elif type == "node":
290             rl.add("operator")
291
292         return rl
293
294     def verify_cancreate_credential(self, src_cred, record):
295         """
296         Verify that a user can retrive a particular type of credential.
297         For slices, the user must be on the researcher list. For SA and
298         MA the user must be on the pi and operator lists respectively
299         """
300
301         type = record.get_type()
302         cred_object_hrn = src_cred.get_gid_object().get_hrn()
303         if cred_object_hrn in [self.config.SFA_REGISTRY_ROOT_AUTH]:
304             return
305         if type=="slice":
306             researchers = record.get("researcher", [])
307             if not (cred_object_hrn in researchers):
308                 raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
309         elif type == "sa":
310             pis = record.get("pi", [])
311             if not (cred_object_hrn in pis):
312                 raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
313         elif type == "ma":
314             operators = record.get("operator", [])
315             if not (cred_object_hrn in operators):
316                 raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
317
318     def get_authority(self, hrn):
319         return get_authority(hrn)
320
321     def filter_creds_by_caller(self, creds, caller_hrn_list):
322         """
323         Returns a list of creds who's gid caller matches the 
324         specified caller hrn
325         """
326         if not isinstance(creds, list):
327             creds = [creds]
328         creds = []
329         if not isinstance(caller_hrn_list, list):
330             caller_hrn_list = [caller_hrn_list]
331         for cred in creds:
332             try:
333                 tmp_cred = Credential(string=cred)
334                 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]:
335                     creds.append(cred)
336             except: pass
337         return creds
338