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 # this convenience methods extracts speaking_for_xrn from the passed options using 'geni_speaking_for'
40 def checkCredentialsSpeaksFor (self, *args, **kwds):
41 if 'options' not in kwds:
42 logger.error ("checkCredentialsSpeaksFor was not passed options=options")
44 # remove the options arg
45 options=kwds['options']; del kwds['options']
46 # compute the speaking_for_xrn arg and pass it to checkCredentials
47 if options is None: speaking_for_xrn=None
48 else: speaking_for_xrn=options.get('geni_speaking_for',None)
49 kwds['speaking_for_xrn']=speaking_for_xrn
50 return self.checkCredentials (*args, **kwds)
52 # do not use mutable as default argument
53 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
54 def checkCredentials(self, creds, operation, xrns=None,
55 check_sliver_callback=None,
56 speaking_for_xrn=None):
57 if xrns is None: xrns=[]
58 def log_invalid_cred(cred):
59 cred_obj=Credential(string=cred)
60 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
61 error = sys.exc_info()[:2]
64 # if xrns are specified they cannot be None or empty string
68 raise BadArgs("Invalid urn or hrn")
71 if not isinstance(xrns, list):
74 slice_xrns = Xrn.filter_type(xrns, 'slice')
75 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
77 # we are not able to validate slivers in the traditional way so
78 # we make sure not to include sliver urns/hrns in the core validation loop
79 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
81 if not isinstance(creds, list):
83 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
84 # won't work if either creds or hrns is empty - let's make it more explicit
85 if not creds: raise Forbidden("no credential provided")
86 if not hrns: hrns = [None]
89 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
90 speaking_for_xrn, self.trusted_cert_list)
92 if self.peer_cert and \
93 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
99 self.check(cred, operation, hrn)
102 error = log_invalid_cred(cred)
104 # make sure all sliver xrns are validated against the valid credentials
106 if not check_sliver_callback:
107 msg = "sliver verification callback method not found."
108 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
110 check_sliver_callback(valid, sliver_xrns)
113 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
118 def check(self, credential, operation, hrn = None):
120 Check the credential against the peer cert (callerGID included
121 in the credential matches the caller that is connected to the
122 HTTPS connection, check if the credential was signed by a
123 trusted cert and check if the credential is allowed to perform
124 the specified operation.
126 cred = Credential(cred=credential)
127 self.client_cred = cred
128 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
129 (hrn,cred.get_summary_tostring()))
131 if cred.type not in ['geni_sfa']:
132 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
133 self.client_gid = self.client_cred.get_gid_caller()
134 self.object_gid = self.client_cred.get_gid_object()
136 # make sure the client_gid is not blank
137 if not self.client_gid:
138 raise MissingCallerGID(self.client_cred.get_subject())
140 # validate the client cert if it exists
142 self.verifyPeerCert(self.peer_cert, self.client_gid)
144 # make sure the client is allowed to perform the operation
146 if not self.client_cred.can_perform(operation):
147 raise InsufficientRights(operation)
149 if self.trusted_cert_list:
150 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
152 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
154 # Make sure the credential's target matches the specified hrn.
155 # This check does not apply to trusted peers
156 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
157 if hrn and self.client_gid.get_hrn() not in trusted_peers:
158 target_hrn = self.object_gid.get_hrn()
159 if not hrn == target_hrn:
160 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
164 def check_ticket(self, ticket):
166 Check if the tickt was signed by a trusted cert
168 if self.trusted_cert_list:
169 client_ticket = SfaTicket(string=ticket)
170 client_ticket.verify_chain(self.trusted_cert_list)
172 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
176 def verifyPeerCert(self, cert, gid):
177 # make sure the client_gid matches client's certificate
178 if not cert.is_pubkey(gid.get_pubkey()):
179 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
181 def verifyGidRequestHash(self, gid, hash, arglist):
182 key = gid.get_pubkey()
183 if not key.verify_string(str(arglist), hash):
184 raise BadRequestHash(hash)
186 def verifyCredRequestHash(self, cred, hash, arglist):
187 gid = cred.get_gid_caller()
188 self.verifyGidRequestHash(gid, hash, arglist)
190 def validateGid(self, gid):
191 if self.trusted_cert_list:
192 gid.verify_chain(self.trusted_cert_list)
194 def validateCred(self, cred):
195 if self.trusted_cert_list:
196 cred.verify(self.trusted_cert_file_list)
198 def authenticateGid(self, gidStr, argList, requestHash=None):
199 gid = GID(string = gidStr)
200 self.validateGid(gid)
201 # request_hash is optional
203 self.verifyGidRequestHash(gid, requestHash, argList)
206 def authenticateCred(self, credStr, argList, requestHash=None):
207 cred = Credential(string = credStr)
208 self.validateCred(cred)
209 # request hash is optional
211 self.verifyCredRequestHash(cred, requestHash, argList)
214 def authenticateCert(self, certStr, requestHash):
215 cert = Certificate(string=certStr)
216 # xxx should be validateCred ??
217 self.validateCred(cert)
219 def gidNoop(self, gidStr, value, requestHash):
220 self.authenticateGid(gidStr, [gidStr, value], requestHash)
223 def credNoop(self, credStr, value, requestHash):
224 self.authenticateCred(credStr, [credStr, value], requestHash)
227 def verify_cred_is_me(self, credential):
229 cred = Credential(string=credential)
230 caller_gid = cred.get_gid_caller()
231 caller_hrn = caller_gid.get_hrn()
232 if caller_hrn != self.config.SFA_INTERFACE_HRN:
233 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
237 def get_auth_info(self, auth_hrn):
239 Given an authority name, return the information for that authority.
240 This is basically a stub that calls the hierarchy module.
242 @param auth_hrn human readable name of authority
245 return self.hierarchy.get_auth_info(auth_hrn)
248 def veriry_auth_belongs_to_me(self, name):
250 Verify that an authority belongs to our hierarchy.
251 This is basically left up to the implementation of the hierarchy
252 module. If the specified name does not belong, ane exception is
253 thrown indicating the caller should contact someone else.
255 @param auth_name human readable name of authority
258 # get auth info will throw an exception if the authority doesnt exist
259 self.get_auth_info(name)
262 def verify_object_belongs_to_me(self, name):
264 Verify that an object belongs to our hierarchy. By extension,
265 this implies that the authority that owns the object belongs
266 to our hierarchy. If it does not an exception is thrown.
268 @param name human readable name of object
270 auth_name = self.get_authority(name)
273 if name == self.config.SFA_INTERFACE_HRN:
275 self.verify_auth_belongs_to_me(auth_name)
277 def verify_auth_belongs_to_me(self, name):
278 # get auth info will throw an exception if the authority doesnt exist
279 self.get_auth_info(name)
282 def verify_object_permission(self, name):
284 Verify that the object gid that was specified in the credential
285 allows permission to the object 'name'. This is done by a simple
286 prefix test. For example, an object_gid for plc.arizona would
287 match the objects plc.arizona.slice1 and plc.arizona.
289 @param name human readable name to test
291 object_hrn = self.object_gid.get_hrn()
292 if object_hrn == name:
294 if name.startswith(object_hrn + "."):
296 #if name.startswith(get_authority(name)):
299 raise PermissionError(name)
301 def determine_user_rights(self, caller_hrn, reg_record):
303 Given a user credential and a record, determine what set of rights the
304 user should have to that record.
306 This is intended to replace determine_user_rights() and
307 verify_cancreate_credential()
311 type = reg_record.type
313 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
316 # researchers in the slice are in the DB as-is
317 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
318 # locating PIs attached to that slice
319 slice_pis=reg_record.get_pis()
320 pi_hrns = [ user.hrn for user in slice_pis ]
321 if (caller_hrn in researcher_hrns + pi_hrns):
328 elif type == 'authority':
329 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
330 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
334 if (caller_hrn in pi_hrns):
337 # NOTE: for the PL implementation, this 'operators' list
338 # amounted to users with 'tech' role in that site
339 # it seems like this is not needed any longer, so for now I just drop that
340 # operator_hrns = reg_record.get('operator',[])
341 # if (caller_hrn in operator_hrns):
342 # rl.add('authority')
355 def get_authority(self, hrn):
356 return get_authority(hrn)
358 def filter_creds_by_caller(self, creds, caller_hrn_list):
360 Returns a list of creds who's gid caller matches the
363 if not isinstance(creds, list):
366 if not isinstance(caller_hrn_list, list):
367 caller_hrn_list = [caller_hrn_list]
370 tmp_cred = Credential(string=cred)
371 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: