2 # SfaAPI authentication
6 from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \
7 BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied, CredentialNotVerifiable, Forbidden, \
9 from sfa.util.sfalogging import logger
10 from sfa.util.config import Config
11 from sfa.util.xrn import Xrn, get_authority
13 from sfa.trust.gid import GID
14 from sfa.trust.rights import Rights
15 from sfa.trust.certificate import Keypair, Certificate
16 from sfa.trust.credential import Credential
17 from sfa.trust.trustedroots import TrustedRoots
18 from sfa.trust.hierarchy import Hierarchy
19 from sfa.trust.sfaticket import SfaTicket
20 from sfa.trust.speaksfor_util import determine_speaks_for
25 Credential based authentication
28 def __init__(self, peer_cert = None, config = None ):
29 self.peer_cert = peer_cert
30 self.hierarchy = Hierarchy()
32 self.config = Config()
33 self.load_trusted_certs()
35 def load_trusted_certs(self):
36 self.trusted_cert_list = TrustedRoots(self.config.get_trustedroots_dir()).get_list()
37 self.trusted_cert_file_list = TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
39 # do not use mutable as default argument
40 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
41 def checkCredentials(self, creds, operation, xrns=None,
42 check_sliver_callback=None, options=None):
43 if xrns is None: xrns=[]
44 def log_invalid_cred(cred):
45 cred_obj=Credential(string=cred)
46 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
47 error = sys.exc_info()[:2]
50 # if xrns are specified they cannot be None or empty string
54 raise BadArgs("Invalid urn or hrn")
57 if not isinstance(xrns, list):
60 slice_xrns = Xrn.filter_type(xrns, 'slice')
61 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
63 # we are not able to validate slivers in the traditional way so
64 # we make sure not to include sliver urns/hrns in the core validation loop
65 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
67 if not isinstance(creds, list):
69 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
70 # won't work if either creds or hrns is empty - let's make it more explicit
71 if not creds: raise Forbidden("no credential provided")
72 if not hrns: hrns = [None]
75 # if speaks for gid matches caller cert then we've found a valid
76 # speaks for credential
77 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert, \
78 options, self.trusted_cert_list)
80 if self.peer_cert and \
81 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
87 self.check(cred, operation, hrn)
90 error = log_invalid_cred(cred)
92 # make sure all sliver xrns are validated against the valid credentials
94 if not check_sliver_callback:
95 msg = "sliver verification callback method not found."
96 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
98 check_sliver_callback(valid, sliver_xrns)
101 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
106 def check(self, credential, operation, hrn = None):
108 Check the credential against the peer cert (callerGID included
109 in the credential matches the caller that is connected to the
110 HTTPS connection, check if the credential was signed by a
111 trusted cert and check if the credential is allowed to perform
112 the specified operation.
114 cred = Credential(cred=credential)
115 self.client_cred = cred
116 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
117 (hrn,cred.get_summary_tostring()))
119 if cred.type not in ['geni_sfa']:
120 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
121 self.client_gid = self.client_cred.get_gid_caller()
122 self.object_gid = self.client_cred.get_gid_object()
124 # make sure the client_gid is not blank
125 if not self.client_gid:
126 raise MissingCallerGID(self.client_cred.get_subject())
128 # validate the client cert if it exists
130 self.verifyPeerCert(self.peer_cert, self.client_gid)
132 # make sure the client is allowed to perform the operation
134 if not self.client_cred.can_perform(operation):
135 raise InsufficientRights(operation)
137 if self.trusted_cert_list:
138 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
140 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
142 # Make sure the credential's target matches the specified hrn.
143 # This check does not apply to trusted peers
144 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
145 if hrn and self.client_gid.get_hrn() not in trusted_peers:
146 target_hrn = self.object_gid.get_hrn()
147 if not hrn == target_hrn:
148 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
152 def check_ticket(self, ticket):
154 Check if the tickt was signed by a trusted cert
156 if self.trusted_cert_list:
157 client_ticket = SfaTicket(string=ticket)
158 client_ticket.verify_chain(self.trusted_cert_list)
160 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
164 def verifyPeerCert(self, cert, gid):
165 # make sure the client_gid matches client's certificate
166 if not cert.is_pubkey(gid.get_pubkey()):
167 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
169 def verifyGidRequestHash(self, gid, hash, arglist):
170 key = gid.get_pubkey()
171 if not key.verify_string(str(arglist), hash):
172 raise BadRequestHash(hash)
174 def verifyCredRequestHash(self, cred, hash, arglist):
175 gid = cred.get_gid_caller()
176 self.verifyGidRequestHash(gid, hash, arglist)
178 def validateGid(self, gid):
179 if self.trusted_cert_list:
180 gid.verify_chain(self.trusted_cert_list)
182 def validateCred(self, cred):
183 if self.trusted_cert_list:
184 cred.verify(self.trusted_cert_file_list)
186 def authenticateGid(self, gidStr, argList, requestHash=None):
187 gid = GID(string = gidStr)
188 self.validateGid(gid)
189 # request_hash is optional
191 self.verifyGidRequestHash(gid, requestHash, argList)
194 def authenticateCred(self, credStr, argList, requestHash=None):
195 cred = Credential(string = credStr)
196 self.validateCred(cred)
197 # request hash is optional
199 self.verifyCredRequestHash(cred, requestHash, argList)
202 def authenticateCert(self, certStr, requestHash):
203 cert = Certificate(string=certStr)
204 # xxx should be validateCred ??
205 self.validateCred(cert)
207 def gidNoop(self, gidStr, value, requestHash):
208 self.authenticateGid(gidStr, [gidStr, value], requestHash)
211 def credNoop(self, credStr, value, requestHash):
212 self.authenticateCred(credStr, [credStr, value], requestHash)
215 def verify_cred_is_me(self, credential):
217 cred = Credential(string=credential)
218 caller_gid = cred.get_gid_caller()
219 caller_hrn = caller_gid.get_hrn()
220 if caller_hrn != self.config.SFA_INTERFACE_HRN:
221 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
225 def get_auth_info(self, auth_hrn):
227 Given an authority name, return the information for that authority.
228 This is basically a stub that calls the hierarchy module.
230 @param auth_hrn human readable name of authority
233 return self.hierarchy.get_auth_info(auth_hrn)
236 def veriry_auth_belongs_to_me(self, name):
238 Verify that an authority belongs to our hierarchy.
239 This is basically left up to the implementation of the hierarchy
240 module. If the specified name does not belong, ane exception is
241 thrown indicating the caller should contact someone else.
243 @param auth_name human readable name of authority
246 # get auth info will throw an exception if the authority doesnt exist
247 self.get_auth_info(name)
250 def verify_object_belongs_to_me(self, name):
252 Verify that an object belongs to our hierarchy. By extension,
253 this implies that the authority that owns the object belongs
254 to our hierarchy. If it does not an exception is thrown.
256 @param name human readable name of object
258 auth_name = self.get_authority(name)
261 if name == self.config.SFA_INTERFACE_HRN:
263 self.verify_auth_belongs_to_me(auth_name)
265 def verify_auth_belongs_to_me(self, name):
266 # get auth info will throw an exception if the authority doesnt exist
267 self.get_auth_info(name)
270 def verify_object_permission(self, name):
272 Verify that the object gid that was specified in the credential
273 allows permission to the object 'name'. This is done by a simple
274 prefix test. For example, an object_gid for plc.arizona would
275 match the objects plc.arizona.slice1 and plc.arizona.
277 @param name human readable name to test
279 object_hrn = self.object_gid.get_hrn()
280 if object_hrn == name:
282 if name.startswith(object_hrn + "."):
284 #if name.startswith(get_authority(name)):
287 raise PermissionError(name)
289 def determine_user_rights(self, caller_hrn, reg_record):
291 Given a user credential and a record, determine what set of rights the
292 user should have to that record.
294 This is intended to replace determine_user_rights() and
295 verify_cancreate_credential()
299 type = reg_record.type
301 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
304 # researchers in the slice are in the DB as-is
305 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
306 # locating PIs attached to that slice
307 slice_pis=reg_record.get_pis()
308 pi_hrns = [ user.hrn for user in slice_pis ]
309 if (caller_hrn in researcher_hrns + pi_hrns):
316 elif type == 'authority':
317 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
318 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
322 if (caller_hrn in pi_hrns):
325 # NOTE: for the PL implementation, this 'operators' list
326 # amounted to users with 'tech' role in that site
327 # it seems like this is not needed any longer, so for now I just drop that
328 # operator_hrns = reg_record.get('operator',[])
329 # if (caller_hrn in operator_hrns):
330 # rl.add('authority')
343 def get_authority(self, hrn):
344 return get_authority(hrn)
346 def filter_creds_by_caller(self, creds, caller_hrn_list):
348 Returns a list of creds who's gid caller matches the
351 if not isinstance(creds, list):
354 if not isinstance(caller_hrn_list, list):
355 caller_hrn_list = [caller_hrn_list]
358 tmp_cred = Credential(string=cred)
359 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: