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, speaking_for_hrn=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 ### Thierry : we have no options to pass determine_speaks_for in this context
78 # so only as a workaround here:
80 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert, \
81 options, self.trusted_cert_list)
83 if self.peer_cert and \
84 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
90 self.check(cred, operation, hrn)
93 error = log_invalid_cred(cred)
95 # make sure all sliver xrns are validated against the valid credentials
97 if not check_sliver_callback:
98 msg = "sliver verification callback method not found."
99 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
101 check_sliver_callback(valid, sliver_xrns)
104 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
106 if speaking_for_hrn and not speaks_for_cred:
107 raise InsufficientRights('Access denied: "geni_speaking_for" option specified but no valid speaks for credential found: %s -- %s' % (error[0],error[1]))
112 def check(self, credential, operation, hrn = None):
114 Check the credential against the peer cert (callerGID included
115 in the credential matches the caller that is connected to the
116 HTTPS connection, check if the credential was signed by a
117 trusted cert and check if the credential is allowed to perform
118 the specified operation.
120 cred = Credential(cred=credential)
121 self.client_cred = cred
122 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
123 (hrn,cred.get_summary_tostring()))
125 if cred.type not in ['geni_sfa']:
126 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
127 self.client_gid = self.client_cred.get_gid_caller()
128 self.object_gid = self.client_cred.get_gid_object()
130 # make sure the client_gid is not blank
131 if not self.client_gid:
132 raise MissingCallerGID(self.client_cred.get_subject())
134 # validate the client cert if it exists
136 self.verifyPeerCert(self.peer_cert, self.client_gid)
138 # make sure the client is allowed to perform the operation
140 if not self.client_cred.can_perform(operation):
141 raise InsufficientRights(operation)
143 if self.trusted_cert_list:
144 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
146 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
148 # Make sure the credential's target matches the specified hrn.
149 # This check does not apply to trusted peers
150 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
151 if hrn and self.client_gid.get_hrn() not in trusted_peers:
152 target_hrn = self.object_gid.get_hrn()
153 if not hrn == target_hrn:
154 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
158 def check_ticket(self, ticket):
160 Check if the tickt was signed by a trusted cert
162 if self.trusted_cert_list:
163 client_ticket = SfaTicket(string=ticket)
164 client_ticket.verify_chain(self.trusted_cert_list)
166 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
170 def verifyPeerCert(self, cert, gid):
171 # make sure the client_gid matches client's certificate
172 if not cert.is_pubkey(gid.get_pubkey()):
173 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
175 def verifyGidRequestHash(self, gid, hash, arglist):
176 key = gid.get_pubkey()
177 if not key.verify_string(str(arglist), hash):
178 raise BadRequestHash(hash)
180 def verifyCredRequestHash(self, cred, hash, arglist):
181 gid = cred.get_gid_caller()
182 self.verifyGidRequestHash(gid, hash, arglist)
184 def validateGid(self, gid):
185 if self.trusted_cert_list:
186 gid.verify_chain(self.trusted_cert_list)
188 def validateCred(self, cred):
189 if self.trusted_cert_list:
190 cred.verify(self.trusted_cert_file_list)
192 def authenticateGid(self, gidStr, argList, requestHash=None):
193 gid = GID(string = gidStr)
194 self.validateGid(gid)
195 # request_hash is optional
197 self.verifyGidRequestHash(gid, requestHash, argList)
200 def authenticateCred(self, credStr, argList, requestHash=None):
201 cred = Credential(string = credStr)
202 self.validateCred(cred)
203 # request hash is optional
205 self.verifyCredRequestHash(cred, requestHash, argList)
208 def authenticateCert(self, certStr, requestHash):
209 cert = Certificate(string=certStr)
210 # xxx should be validateCred ??
211 self.validateCred(cert)
213 def gidNoop(self, gidStr, value, requestHash):
214 self.authenticateGid(gidStr, [gidStr, value], requestHash)
217 def credNoop(self, credStr, value, requestHash):
218 self.authenticateCred(credStr, [credStr, value], requestHash)
221 def verify_cred_is_me(self, credential):
223 cred = Credential(string=credential)
224 caller_gid = cred.get_gid_caller()
225 caller_hrn = caller_gid.get_hrn()
226 if caller_hrn != self.config.SFA_INTERFACE_HRN:
227 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
231 def get_auth_info(self, auth_hrn):
233 Given an authority name, return the information for that authority.
234 This is basically a stub that calls the hierarchy module.
236 @param auth_hrn human readable name of authority
239 return self.hierarchy.get_auth_info(auth_hrn)
242 def veriry_auth_belongs_to_me(self, name):
244 Verify that an authority belongs to our hierarchy.
245 This is basically left up to the implementation of the hierarchy
246 module. If the specified name does not belong, ane exception is
247 thrown indicating the caller should contact someone else.
249 @param auth_name human readable name of authority
252 # get auth info will throw an exception if the authority doesnt exist
253 self.get_auth_info(name)
256 def verify_object_belongs_to_me(self, name):
258 Verify that an object belongs to our hierarchy. By extension,
259 this implies that the authority that owns the object belongs
260 to our hierarchy. If it does not an exception is thrown.
262 @param name human readable name of object
264 auth_name = self.get_authority(name)
267 if name == self.config.SFA_INTERFACE_HRN:
269 self.verify_auth_belongs_to_me(auth_name)
271 def verify_auth_belongs_to_me(self, name):
272 # get auth info will throw an exception if the authority doesnt exist
273 self.get_auth_info(name)
276 def verify_object_permission(self, name):
278 Verify that the object gid that was specified in the credential
279 allows permission to the object 'name'. This is done by a simple
280 prefix test. For example, an object_gid for plc.arizona would
281 match the objects plc.arizona.slice1 and plc.arizona.
283 @param name human readable name to test
285 object_hrn = self.object_gid.get_hrn()
286 if object_hrn == name:
288 if name.startswith(object_hrn + "."):
290 #if name.startswith(get_authority(name)):
293 raise PermissionError(name)
295 def determine_user_rights(self, caller_hrn, reg_record):
297 Given a user credential and a record, determine what set of rights the
298 user should have to that record.
300 This is intended to replace determine_user_rights() and
301 verify_cancreate_credential()
305 type = reg_record.type
307 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
310 # researchers in the slice are in the DB as-is
311 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
312 # locating PIs attached to that slice
313 slice_pis=reg_record.get_pis()
314 pi_hrns = [ user.hrn for user in slice_pis ]
315 if (caller_hrn in researcher_hrns + pi_hrns):
322 elif type == 'authority':
323 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
324 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
328 if (caller_hrn in pi_hrns):
331 # NOTE: for the PL implementation, this 'operators' list
332 # amounted to users with 'tech' role in that site
333 # it seems like this is not needed any longer, so for now I just drop that
334 # operator_hrns = reg_record.get('operator',[])
335 # if (caller_hrn in operator_hrns):
336 # rl.add('authority')
349 def get_authority(self, hrn):
350 return get_authority(hrn)
352 def filter_creds_by_caller(self, creds, caller_hrn_list):
354 Returns a list of creds who's gid caller matches the
357 if not isinstance(creds, list):
360 if not isinstance(caller_hrn_list, list):
361 caller_hrn_list = [caller_hrn_list]
364 tmp_cred = Credential(string=cred)
365 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: