2 # SfaAPI authentication
5 from types import StringTypes
7 from sfa.util.faults import InsufficientRights, MissingCallerGID, \
8 MissingTrustedRoots, PermissionError, BadRequestHash, \
9 ConnectionKeyGIDMismatch, SfaPermissionDenied, CredentialNotVerifiable, \
11 from sfa.util.sfalogging import logger
12 from sfa.util.config import Config
13 from sfa.util.xrn import Xrn, get_authority
15 from sfa.trust.gid import GID
16 from sfa.trust.rights import Rights
17 from sfa.trust.certificate import Keypair, Certificate
18 from sfa.trust.credential import Credential
19 from sfa.trust.trustedroots import TrustedRoots
20 from sfa.trust.hierarchy import Hierarchy
21 from sfa.trust.sfaticket import SfaTicket
22 from sfa.trust.speaksfor_util import determine_speaks_for
27 Credential based authentication
30 def __init__(self, peer_cert = None, config = None ):
31 self.peer_cert = peer_cert
32 self.hierarchy = Hierarchy()
34 self.config = Config()
35 self.load_trusted_certs()
37 def load_trusted_certs(self):
38 self.trusted_cert_list = \
39 TrustedRoots(self.config.get_trustedroots_dir()).get_list()
40 self.trusted_cert_file_list = \
41 TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
43 # this convenience methods extracts speaking_for_xrn
44 # from the passed options using 'geni_speaking_for'
45 def checkCredentialsSpeaksFor (self, *args, **kwds):
46 if 'options' not in kwds:
47 logger.error ("checkCredentialsSpeaksFor was not passed options=options")
49 # remove the options arg
50 options=kwds['options']; del kwds['options']
51 # compute the speaking_for_xrn arg and pass it to checkCredentials
52 if options is None: speaking_for_xrn=None
53 else: speaking_for_xrn=options.get('geni_speaking_for',None)
54 kwds['speaking_for_xrn']=speaking_for_xrn
55 return self.checkCredentials (*args, **kwds)
57 # do not use mutable as default argument
58 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
59 def checkCredentials(self, creds, operation, xrns=None,
60 check_sliver_callback=None,
61 speaking_for_xrn=None):
62 if xrns is None: xrns=[]
63 def log_invalid_cred(cred):
64 if not isinstance (cred, StringTypes):
65 logger.info("cannot validate credential %s - expecting a string"%cred)
66 error="checkCredentials: expected a string, received %s"%(type(cred))
68 cred_obj=Credential(string=cred)
69 logger.info("failed to validate credential - dump=%s"%\
70 cred_obj.dump_string(dump_parents=True))
71 error = sys.exc_info()[:2]
74 # if xrns are specified they cannot be None or empty string
78 raise BadArgs("Invalid urn or hrn")
81 if not isinstance(xrns, list):
84 slice_xrns = Xrn.filter_type(xrns, 'slice')
85 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
87 # we are not able to validate slivers in the traditional way so
88 # we make sure not to include sliver urns/hrns in the core validation loop
89 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
91 if not isinstance(creds, list):
93 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
94 # won't work if either creds or hrns is empty - let's make it more explicit
95 if not creds: raise Forbidden("no credential provided")
96 if not hrns: hrns = [None]
99 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
100 speaking_for_xrn, self.trusted_cert_list)
102 if self.peer_cert and \
103 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
109 self.check(cred, operation, hrn)
112 error = log_invalid_cred(cred)
114 # make sure all sliver xrns are validated against the valid credentials
116 if not check_sliver_callback:
117 msg = "sliver verification callback method not found."
118 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
120 check_sliver_callback(valid, sliver_xrns)
123 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
128 def check(self, credential, operation, hrn = None):
130 Check the credential against the peer cert (callerGID) included
131 in the credential matches the caller that is connected to the
132 HTTPS connection, check if the credential was signed by a
133 trusted cert and check if the credential is allowed to perform
134 the specified operation.
136 cred = Credential(cred=credential)
137 self.client_cred = cred
138 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
139 (hrn,cred.pretty_cred()))
141 if cred.type not in ['geni_sfa']:
142 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
143 self.client_gid = self.client_cred.get_gid_caller()
144 self.object_gid = self.client_cred.get_gid_object()
146 # make sure the client_gid is not blank
147 if not self.client_gid:
148 raise MissingCallerGID(self.client_cred.pretty_subject())
150 # validate the client cert if it exists
152 self.verifyPeerCert(self.peer_cert, self.client_gid)
154 # make sure the client is allowed to perform the operation
156 if not self.client_cred.can_perform(operation):
157 raise InsufficientRights(operation)
159 if self.trusted_cert_list:
160 self.client_cred.verify(self.trusted_cert_file_list,
161 self.config.SFA_CREDENTIAL_SCHEMA)
163 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
165 # Make sure the credential's target matches the specified hrn.
166 # This check does not apply to trusted peers
167 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
168 if hrn and self.client_gid.get_hrn() not in trusted_peers:
169 target_hrn = self.object_gid.get_hrn()
170 if not hrn == target_hrn:
171 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
175 def check_ticket(self, ticket):
177 Check if the ticket was signed by a trusted cert
179 if self.trusted_cert_list:
180 client_ticket = SfaTicket(string=ticket)
181 client_ticket.verify_chain(self.trusted_cert_list)
183 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
187 def verifyPeerCert(self, cert, gid):
188 # make sure the client_gid matches client's certificate
189 if not cert.is_pubkey(gid.get_pubkey()):
190 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
192 def verifyGidRequestHash(self, gid, hash, arglist):
193 key = gid.get_pubkey()
194 if not key.verify_string(str(arglist), hash):
195 raise BadRequestHash(hash)
197 def verifyCredRequestHash(self, cred, hash, arglist):
198 gid = cred.get_gid_caller()
199 self.verifyGidRequestHash(gid, hash, arglist)
201 def validateGid(self, gid):
202 if self.trusted_cert_list:
203 gid.verify_chain(self.trusted_cert_list)
205 def validateCred(self, cred):
206 if self.trusted_cert_list:
207 cred.verify(self.trusted_cert_file_list)
209 def authenticateGid(self, gidStr, argList, requestHash=None):
210 gid = GID(string = gidStr)
211 self.validateGid(gid)
212 # request_hash is optional
214 self.verifyGidRequestHash(gid, requestHash, argList)
217 def authenticateCred(self, credStr, argList, requestHash=None):
218 cred = Credential(string = credStr)
219 self.validateCred(cred)
220 # request hash is optional
222 self.verifyCredRequestHash(cred, requestHash, argList)
225 def authenticateCert(self, certStr, requestHash):
226 cert = Certificate(string=certStr)
227 # xxx should be validateCred ??
228 self.validateCred(cert)
230 def gidNoop(self, gidStr, value, requestHash):
231 self.authenticateGid(gidStr, [gidStr, value], requestHash)
234 def credNoop(self, credStr, value, requestHash):
235 self.authenticateCred(credStr, [credStr, value], requestHash)
238 def verify_cred_is_me(self, credential):
240 cred = Credential(string=credential)
241 caller_gid = cred.get_gid_caller()
242 caller_hrn = caller_gid.get_hrn()
243 if caller_hrn != self.config.SFA_INTERFACE_HRN:
244 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
248 def get_auth_info(self, auth_hrn):
250 Given an authority name, return the information for that authority.
251 This is basically a stub that calls the hierarchy module.
253 @param auth_hrn human readable name of authority
256 return self.hierarchy.get_auth_info(auth_hrn)
259 def veriry_auth_belongs_to_me(self, name):
261 Verify that an authority belongs to our hierarchy.
262 This is basically left up to the implementation of the hierarchy
263 module. If the specified name does not belong, ane exception is
264 thrown indicating the caller should contact someone else.
266 @param auth_name human readable name of authority
269 # get auth info will throw an exception if the authority doesnt exist
270 self.get_auth_info(name)
273 def verify_object_belongs_to_me(self, name):
275 Verify that an object belongs to our hierarchy. By extension,
276 this implies that the authority that owns the object belongs
277 to our hierarchy. If it does not an exception is thrown.
279 @param name human readable name of object
281 auth_name = self.get_authority(name)
284 if name == self.config.SFA_INTERFACE_HRN:
286 self.verify_auth_belongs_to_me(auth_name)
288 def verify_auth_belongs_to_me(self, name):
289 # get auth info will throw an exception if the authority doesnt exist
290 self.get_auth_info(name)
293 def verify_object_permission(self, name):
295 Verify that the object gid that was specified in the credential
296 allows permission to the object 'name'. This is done by a simple
297 prefix test. For example, an object_gid for plc.arizona would
298 match the objects plc.arizona.slice1 and plc.arizona.
300 @param name human readable name to test
302 object_hrn = self.object_gid.get_hrn()
303 if object_hrn == name:
305 if name.startswith(object_hrn + "."):
307 #if name.startswith(get_authority(name)):
310 raise PermissionError(name)
312 def determine_user_rights(self, caller_hrn, reg_record):
314 Given a user credential and a record, determine what set of rights the
315 user should have to that record.
317 This is intended to replace determine_user_rights() and
318 verify_cancreate_credential()
322 type = reg_record.type
324 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%\
325 (reg_record, caller_hrn))
328 # researchers in the slice are in the DB as-is
329 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
330 # locating PIs attached to that slice
331 slice_pis=reg_record.get_pis()
332 pi_hrns = [ user.hrn for user in slice_pis ]
333 if (caller_hrn in researcher_hrns + pi_hrns):
340 elif type == 'authority':
341 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
342 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
346 if (caller_hrn in pi_hrns):
349 # NOTE: for the PL implementation, this 'operators' list
350 # amounted to users with 'tech' role in that site
351 # it seems like this is not needed any longer, so for now I just drop that
352 # operator_hrns = reg_record.get('operator',[])
353 # if (caller_hrn in operator_hrns):
354 # rl.add('authority')
367 def get_authority(self, hrn):
368 return get_authority(hrn)
370 def filter_creds_by_caller(self, creds, caller_hrn_list):
372 Returns a list of creds who's gid caller matches the
375 if not isinstance(creds, list):
378 if not isinstance(caller_hrn_list, list):
379 caller_hrn_list = [caller_hrn_list]
382 tmp_cred = Credential(string=cred)
383 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: