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         
237         if object_hrn == strname:
238             return
239         if strname.startswith((object_hrn + ".")) is True:
240             return
241         #if name.startswith(get_authority(name)):
242             #return
243         #print>>sys.stderr, " \r\n \t AUTH.PY  verify_object_permission GROSECHECDELENFER "
244         raise PermissionError(name)
245
246     def determine_user_rights(self, caller_hrn, record):
247         """
248         Given a user credential and a record, determine what set of rights the
249         user should have to that record.
250         
251         This is intended to replace determine_rights() and
252         verify_cancreate_credential()
253         """
254
255         rl = Rights()
256         type = record['type']
257
258
259         if type=="slice":
260             researchers = record.get("researcher", [])
261             pis = record.get("PI", [])
262             if (caller_hrn in researchers + pis):
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             pis = record.get("PI", [])
271             operators = record.get("operator", [])
272             if (caller_hrn == self.config.SFA_INTERFACE_HRN):
273                 rl.add("authority")
274                 rl.add("sa")
275                 rl.add("ma")
276             if (caller_hrn in pis):
277                 rl.add("authority")
278                 rl.add("sa")
279             if (caller_hrn in operators):
280                 rl.add("authority")
281                 rl.add("ma")
282
283         elif type == "user":
284             rl.add("refresh")
285             rl.add("resolve")
286             rl.add("info")
287
288         elif type == "node":
289             rl.add("operator")
290
291         return rl
292
293     def verify_cancreate_credential(self, src_cred, record):
294         """
295         Verify that a user can retrive a particular type of credential.
296         For slices, the user must be on the researcher list. For SA and
297         MA the user must be on the pi and operator lists respectively
298         """
299
300         type = record.get_type()
301         cred_object_hrn = src_cred.get_gid_object().get_hrn()
302         if cred_object_hrn in [self.config.SFA_REGISTRY_ROOT_AUTH]:
303             return
304         if type=="slice":
305             researchers = record.get("researcher", [])
306             if not (cred_object_hrn in researchers):
307                 raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
308         elif type == "sa":
309             pis = record.get("pi", [])
310             if not (cred_object_hrn in pis):
311                 raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
312         elif type == "ma":
313             operators = record.get("operator", [])
314             if not (cred_object_hrn in operators):
315                 raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
316
317     def get_authority(self, hrn):
318         return get_authority(hrn)
319
320     def filter_creds_by_caller(self, creds, caller_hrn_list):
321         """
322         Returns a list of creds who's gid caller matches the 
323         specified caller hrn
324         """
325         if not isinstance(creds, list):
326             creds = [creds]
327         creds = []
328         if not isinstance(caller_hrn_list, list):
329             caller_hrn_list = [caller_hrn_list]
330         for cred in creds:
331             try:
332                 tmp_cred = Credential(string=cred)
333                 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]:
334                     creds.append(cred)
335             except: pass
336         return creds
337