2 # SfaAPI authentication
6 from sfa.util.faults import InsufficientRights, MissingCallerGID, \
7 MissingTrustedRoots, PermissionError, BadRequestHash, \
8 ConnectionKeyGIDMismatch, SfaPermissionDenied, CredentialNotVerifiable, \
10 from sfa.util.sfalogging import logger
11 from sfa.util.py23 import StringType
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:
48 "checkCredentialsSpeaksFor was not passed options=options")
50 # remove the options arg
51 options = kwds['options']
53 # compute the speaking_for_xrn arg and pass it to checkCredentials
55 speaking_for_xrn = None
57 speaking_for_xrn = options.get('geni_speaking_for', None)
58 kwds['speaking_for_xrn'] = speaking_for_xrn
59 return self.checkCredentials(*args, **kwds)
61 # do not use mutable as default argument
62 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
63 def checkCredentials(self, creds, operation, xrns=None,
64 check_sliver_callback=None,
65 speaking_for_xrn=None):
70 def log_invalid_cred(cred, exception):
71 if isinstance(cred, dict) and 'geni_value' in cred:
72 cred = cred['geni_value']
73 if not isinstance(cred, StringType):
75 "{}: cannot validate credential {}"
76 .format(exception, cred))
77 error = ('TypeMismatch',
78 "checkCredentials: expected a string, got {} -- {}"
79 .format(type(cred), cred))
81 cred_obj = Credential(string=cred)
82 logger.info("{}: failed to validate credential dump={}"
84 cred_obj.dump_string(dump_parents=True)))
85 error = sys.exc_info()[:2]
88 # if xrns are specified they cannot be None or empty string
92 raise BadArgs("Invalid urn or hrn")
94 if not isinstance(xrns, list):
97 # slice_xrns = Xrn.filter_type(xrns, 'slice')
98 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
100 # we are not able to validate slivers in the traditional way so
101 # we make sure not to include sliver urns/hrns in the core validation
103 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
105 if not isinstance(creds, list):
107 logger.debug("Auth.checkCredentials with %d creds on hrns=%s" %
109 # won't work if either creds or hrns is empty - let's make it more
112 raise Forbidden("no credential provided")
116 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
117 speaking_for_xrn, self.trusted_cert_list)
119 if self.peer_cert and \
120 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
126 self.check(cred, operation, hrn)
128 except Exception as exc:
129 error = log_invalid_cred(cred, exc)
131 # make sure all sliver xrns are validated against the valid credentials
133 if not check_sliver_callback:
134 msg = "sliver verification callback method not found."
135 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
137 check_sliver_callback(valid, sliver_xrns)
140 raise Forbidden("Invalid credential %s -- %s" %
141 (error[0], error[1]))
145 def check(self, credential, operation, hrn=None):
147 Check the credential against the peer cert (callerGID) included
148 in the credential matches the caller that is connected to the
149 HTTPS connection, check if the credential was signed by a
150 trusted cert and check if the credential is allowed to perform
151 the specified operation.
153 cred = Credential(cred=credential)
154 self.client_cred = cred
155 logger.debug("Auth.check: handling hrn=%s and credential=%s" %
156 (hrn, cred.pretty_cred()))
158 if cred.type not in ['geni_sfa']:
159 raise CredentialNotVerifiable(
160 cred.type, "%s not supported" % cred.type)
161 self.client_gid = self.client_cred.get_gid_caller()
162 self.object_gid = self.client_cred.get_gid_object()
164 # make sure the client_gid is not blank
165 if not self.client_gid:
166 raise MissingCallerGID(self.client_cred.pretty_subject())
168 # validate the client cert if it exists
170 self.verifyPeerCert(self.peer_cert, self.client_gid)
172 # make sure the client is allowed to perform the operation
174 if not self.client_cred.can_perform(operation):
175 raise InsufficientRights(operation)
177 if self.trusted_cert_list:
178 self.client_cred.verify(self.trusted_cert_file_list,
179 self.config.SFA_CREDENTIAL_SCHEMA)
181 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
183 # Make sure the credential's target matches the specified hrn.
184 # This check does not apply to trusted peers
185 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
186 if hrn and self.client_gid.get_hrn() not in trusted_peers:
187 target_hrn = self.object_gid.get_hrn()
188 if not hrn == target_hrn:
189 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " %
193 def check_ticket(self, ticket):
195 Check if the ticket was signed by a trusted cert
197 if self.trusted_cert_list:
198 client_ticket = SfaTicket(string=ticket)
199 client_ticket.verify_chain(self.trusted_cert_list)
201 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
205 def verifyPeerCert(self, cert, gid):
206 # make sure the client_gid matches client's certificate
207 if not cert.is_pubkey(gid.get_pubkey()):
208 raise ConnectionKeyGIDMismatch(
209 gid.get_subject() + ":" + cert.get_subject())
211 def verifyGidRequestHash(self, gid, hash, arglist):
212 key = gid.get_pubkey()
213 if not key.verify_string(str(arglist), hash):
214 raise BadRequestHash(hash)
216 def verifyCredRequestHash(self, cred, hash, arglist):
217 gid = cred.get_gid_caller()
218 self.verifyGidRequestHash(gid, hash, arglist)
220 def validateGid(self, gid):
221 if self.trusted_cert_list:
222 gid.verify_chain(self.trusted_cert_list)
224 def validateCred(self, cred):
225 if self.trusted_cert_list:
226 cred.verify(self.trusted_cert_file_list)
228 def authenticateGid(self, gidStr, argList, requestHash=None):
229 gid = GID(string=gidStr)
230 self.validateGid(gid)
231 # request_hash is optional
233 self.verifyGidRequestHash(gid, requestHash, argList)
236 def authenticateCred(self, credStr, argList, requestHash=None):
237 cred = Credential(string=credStr)
238 self.validateCred(cred)
239 # request hash is optional
241 self.verifyCredRequestHash(cred, requestHash, argList)
244 def authenticateCert(self, certStr, requestHash):
245 cert = Certificate(string=certStr)
246 # xxx should be validateCred ??
247 self.validateCred(cert)
249 def gidNoop(self, gidStr, value, requestHash):
250 self.authenticateGid(gidStr, [gidStr, value], requestHash)
253 def credNoop(self, credStr, value, requestHash):
254 self.authenticateCred(credStr, [credStr, value], requestHash)
257 def verify_cred_is_me(self, credential):
259 cred = Credential(string=credential)
260 caller_gid = cred.get_gid_caller()
261 caller_hrn = caller_gid.get_hrn()
262 if caller_hrn != self.config.SFA_INTERFACE_HRN:
263 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
267 def get_auth_info(self, auth_hrn):
269 Given an authority name, return the information for that authority.
270 This is basically a stub that calls the hierarchy module.
272 @param auth_hrn human readable name of authority
275 return self.hierarchy.get_auth_info(auth_hrn)
277 def veriry_auth_belongs_to_me(self, name):
279 Verify that an authority belongs to our hierarchy.
280 This is basically left up to the implementation of the hierarchy
281 module. If the specified name does not belong, ane exception is
282 thrown indicating the caller should contact someone else.
284 @param auth_name human readable name of authority
287 # get auth info will throw an exception if the authority doesnt exist
288 self.get_auth_info(name)
290 def verify_object_belongs_to_me(self, name):
292 Verify that an object belongs to our hierarchy. By extension,
293 this implies that the authority that owns the object belongs
294 to our hierarchy. If it does not an exception is thrown.
296 @param name human readable name of object
298 auth_name = self.get_authority(name)
301 if name == self.config.SFA_INTERFACE_HRN:
303 self.verify_auth_belongs_to_me(auth_name)
305 def verify_auth_belongs_to_me(self, name):
306 # get auth info will throw an exception if the authority doesnt exist
307 self.get_auth_info(name)
309 def verify_object_permission(self, name):
311 Verify that the object gid that was specified in the credential
312 allows permission to the object 'name'. This is done by a simple
313 prefix test. For example, an object_gid for plc.arizona would
314 match the objects plc.arizona.slice1 and plc.arizona.
316 @param name human readable name to test
318 object_hrn = self.object_gid.get_hrn()
319 if object_hrn == name:
321 if name.startswith(object_hrn + "."):
323 # if name.startswith(get_authority(name)):
326 raise PermissionError(name)
328 def determine_user_rights(self, caller_hrn, reg_record):
330 Given a user credential and a record, determine what set of rights the
331 user should have to that record.
333 This is intended to replace determine_user_rights() and
334 verify_cancreate_credential()
338 type = reg_record.type
340 logger.debug("entering determine_user_rights with record %s and caller_hrn %s" %
341 (reg_record, caller_hrn))
344 # researchers in the slice are in the DB as-is
345 researcher_hrns = [user.hrn for user in reg_record.reg_researchers]
346 # locating PIs attached to that slice
347 slice_pis = reg_record.get_pis()
348 pi_hrns = [user.hrn for user in slice_pis]
349 if (caller_hrn in researcher_hrns + pi_hrns):
356 elif type == 'authority':
357 pi_hrns = [user.hrn for user in reg_record.reg_pis]
358 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
362 if (caller_hrn in pi_hrns):
365 # NOTE: for the PL implementation, this 'operators' list
366 # amounted to users with 'tech' role in that site
367 # it seems like this is not needed any longer, so for now I just drop that
368 # operator_hrns = reg_record.get('operator', [])
369 # if (caller_hrn in operator_hrns):
370 # rl.add('authority')
383 def get_authority(self, hrn):
384 return get_authority(hrn)
386 def filter_creds_by_caller(self, creds, caller_hrn_list):
388 Returns a list of creds who's gid caller matches the
391 if not isinstance(creds, list):
394 if not isinstance(caller_hrn_list, list):
395 caller_hrn_list = [caller_hrn_list]
398 tmp_cred = Credential(string=cred)
399 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: