2 # SfaAPI authentication
5 from types import StringTypes
7 from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \
8 BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied, CredentialNotVerifiable, Forbidden, \
10 from sfa.util.sfalogging import logger
11 from sfa.util.config import Config
12 from sfa.util.xrn import Xrn, get_authority
14 from sfa.trust.gid import GID
15 from sfa.trust.rights import Rights
16 from sfa.trust.certificate import Keypair, Certificate
17 from sfa.trust.credential import Credential
18 from sfa.trust.trustedroots import TrustedRoots
19 from sfa.trust.hierarchy import Hierarchy
20 from sfa.trust.sfaticket import SfaTicket
21 from sfa.trust.speaksfor_util import determine_speaks_for
26 Credential based authentication
29 def __init__(self, peer_cert = None, config = None ):
30 self.peer_cert = peer_cert
31 self.hierarchy = Hierarchy()
33 self.config = Config()
34 self.load_trusted_certs()
36 def load_trusted_certs(self):
37 self.trusted_cert_list = TrustedRoots(self.config.get_trustedroots_dir()).get_list()
38 self.trusted_cert_file_list = TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
40 # this convenience methods extracts speaking_for_xrn from the passed options using 'geni_speaking_for'
41 def checkCredentialsSpeaksFor (self, *args, **kwds):
42 if 'options' not in kwds:
43 logger.error ("checkCredentialsSpeaksFor was not passed options=options")
45 # remove the options arg
46 options=kwds['options']; del kwds['options']
47 # compute the speaking_for_xrn arg and pass it to checkCredentials
48 if options is None: speaking_for_xrn=None
49 else: speaking_for_xrn=options.get('geni_speaking_for',None)
50 kwds['speaking_for_xrn']=speaking_for_xrn
51 return self.checkCredentials (*args, **kwds)
53 # do not use mutable as default argument
54 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
55 def checkCredentials(self, creds, operation, xrns=None,
56 check_sliver_callback=None,
57 speaking_for_xrn=None):
58 if xrns is None: xrns=[]
59 def log_invalid_cred(cred):
60 if not isinstance (cred, StringTypes):
61 logger.info("cannot validate credential %s - expecting a string"%cred)
62 error="checkCredentials: expected a string, received %s"%(type(cred))
64 cred_obj=Credential(string=cred)
65 logger.info("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
66 error = sys.exc_info()[:2]
69 # if xrns are specified they cannot be None or empty string
73 raise BadArgs("Invalid urn or hrn")
76 if not isinstance(xrns, list):
79 slice_xrns = Xrn.filter_type(xrns, 'slice')
80 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
82 # we are not able to validate slivers in the traditional way so
83 # we make sure not to include sliver urns/hrns in the core validation loop
84 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
86 if not isinstance(creds, list):
88 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
89 # won't work if either creds or hrns is empty - let's make it more explicit
90 if not creds: raise Forbidden("no credential provided")
91 if not hrns: hrns = [None]
94 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
95 speaking_for_xrn, self.trusted_cert_list)
97 if self.peer_cert and \
98 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
104 self.check(cred, operation, hrn)
107 error = log_invalid_cred(cred)
109 # make sure all sliver xrns are validated against the valid credentials
111 if not check_sliver_callback:
112 msg = "sliver verification callback method not found."
113 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
115 check_sliver_callback(valid, sliver_xrns)
118 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
123 def check(self, credential, operation, hrn = None):
125 Check the credential against the peer cert (callerGID included
126 in the credential matches the caller that is connected to the
127 HTTPS connection, check if the credential was signed by a
128 trusted cert and check if the credential is allowed to perform
129 the specified operation.
131 cred = Credential(cred=credential)
132 self.client_cred = cred
133 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
134 (hrn,cred.get_summary_tostring()))
136 if cred.type not in ['geni_sfa']:
137 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
138 self.client_gid = self.client_cred.get_gid_caller()
139 self.object_gid = self.client_cred.get_gid_object()
141 # make sure the client_gid is not blank
142 if not self.client_gid:
143 raise MissingCallerGID(self.client_cred.get_subject())
145 # validate the client cert if it exists
147 self.verifyPeerCert(self.peer_cert, self.client_gid)
149 # make sure the client is allowed to perform the operation
151 if not self.client_cred.can_perform(operation):
152 raise InsufficientRights(operation)
154 if self.trusted_cert_list:
155 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
157 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
159 # Make sure the credential's target matches the specified hrn.
160 # This check does not apply to trusted peers
161 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
162 if hrn and self.client_gid.get_hrn() not in trusted_peers:
163 target_hrn = self.object_gid.get_hrn()
164 if not hrn == target_hrn:
165 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
169 def check_ticket(self, ticket):
171 Check if the tickt was signed by a trusted cert
173 if self.trusted_cert_list:
174 client_ticket = SfaTicket(string=ticket)
175 client_ticket.verify_chain(self.trusted_cert_list)
177 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
181 def verifyPeerCert(self, cert, gid):
182 # make sure the client_gid matches client's certificate
183 if not cert.is_pubkey(gid.get_pubkey()):
184 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
186 def verifyGidRequestHash(self, gid, hash, arglist):
187 key = gid.get_pubkey()
188 if not key.verify_string(str(arglist), hash):
189 raise BadRequestHash(hash)
191 def verifyCredRequestHash(self, cred, hash, arglist):
192 gid = cred.get_gid_caller()
193 self.verifyGidRequestHash(gid, hash, arglist)
195 def validateGid(self, gid):
196 if self.trusted_cert_list:
197 gid.verify_chain(self.trusted_cert_list)
199 def validateCred(self, cred):
200 if self.trusted_cert_list:
201 cred.verify(self.trusted_cert_file_list)
203 def authenticateGid(self, gidStr, argList, requestHash=None):
204 gid = GID(string = gidStr)
205 self.validateGid(gid)
206 # request_hash is optional
208 self.verifyGidRequestHash(gid, requestHash, argList)
211 def authenticateCred(self, credStr, argList, requestHash=None):
212 cred = Credential(string = credStr)
213 self.validateCred(cred)
214 # request hash is optional
216 self.verifyCredRequestHash(cred, requestHash, argList)
219 def authenticateCert(self, certStr, requestHash):
220 cert = Certificate(string=certStr)
221 # xxx should be validateCred ??
222 self.validateCred(cert)
224 def gidNoop(self, gidStr, value, requestHash):
225 self.authenticateGid(gidStr, [gidStr, value], requestHash)
228 def credNoop(self, credStr, value, requestHash):
229 self.authenticateCred(credStr, [credStr, value], requestHash)
232 def verify_cred_is_me(self, credential):
234 cred = Credential(string=credential)
235 caller_gid = cred.get_gid_caller()
236 caller_hrn = caller_gid.get_hrn()
237 if caller_hrn != self.config.SFA_INTERFACE_HRN:
238 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
242 def get_auth_info(self, auth_hrn):
244 Given an authority name, return the information for that authority.
245 This is basically a stub that calls the hierarchy module.
247 @param auth_hrn human readable name of authority
250 return self.hierarchy.get_auth_info(auth_hrn)
253 def veriry_auth_belongs_to_me(self, name):
255 Verify that an authority belongs to our hierarchy.
256 This is basically left up to the implementation of the hierarchy
257 module. If the specified name does not belong, ane exception is
258 thrown indicating the caller should contact someone else.
260 @param auth_name human readable name of authority
263 # get auth info will throw an exception if the authority doesnt exist
264 self.get_auth_info(name)
267 def verify_object_belongs_to_me(self, name):
269 Verify that an object belongs to our hierarchy. By extension,
270 this implies that the authority that owns the object belongs
271 to our hierarchy. If it does not an exception is thrown.
273 @param name human readable name of object
275 auth_name = self.get_authority(name)
278 if name == self.config.SFA_INTERFACE_HRN:
280 self.verify_auth_belongs_to_me(auth_name)
282 def verify_auth_belongs_to_me(self, name):
283 # get auth info will throw an exception if the authority doesnt exist
284 self.get_auth_info(name)
287 def verify_object_permission(self, name):
289 Verify that the object gid that was specified in the credential
290 allows permission to the object 'name'. This is done by a simple
291 prefix test. For example, an object_gid for plc.arizona would
292 match the objects plc.arizona.slice1 and plc.arizona.
294 @param name human readable name to test
296 object_hrn = self.object_gid.get_hrn()
297 if object_hrn == name:
299 if name.startswith(object_hrn + "."):
301 #if name.startswith(get_authority(name)):
304 raise PermissionError(name)
306 def determine_user_rights(self, caller_hrn, reg_record):
308 Given a user credential and a record, determine what set of rights the
309 user should have to that record.
311 This is intended to replace determine_user_rights() and
312 verify_cancreate_credential()
316 type = reg_record.type
318 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
321 # researchers in the slice are in the DB as-is
322 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
323 # locating PIs attached to that slice
324 slice_pis=reg_record.get_pis()
325 pi_hrns = [ user.hrn for user in slice_pis ]
326 if (caller_hrn in researcher_hrns + pi_hrns):
333 elif type == 'authority':
334 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
335 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
339 if (caller_hrn in pi_hrns):
342 # NOTE: for the PL implementation, this 'operators' list
343 # amounted to users with 'tech' role in that site
344 # it seems like this is not needed any longer, so for now I just drop that
345 # operator_hrns = reg_record.get('operator',[])
346 # if (caller_hrn in operator_hrns):
347 # rl.add('authority')
360 def get_authority(self, hrn):
361 return get_authority(hrn)
363 def filter_creds_by_caller(self, creds, caller_hrn_list):
365 Returns a list of creds who's gid caller matches the
368 if not isinstance(creds, list):
371 if not isinstance(caller_hrn_list, list):
372 caller_hrn_list = [caller_hrn_list]
375 tmp_cred = Credential(string=cred)
376 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: