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.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 = \
38 TrustedRoots(self.config.get_trustedroots_dir()).get_list()
39 self.trusted_cert_file_list = \
40 TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
42 # this convenience methods extracts speaking_for_xrn
43 # from the passed options using 'geni_speaking_for'
44 def checkCredentialsSpeaksFor(self, *args, **kwds):
45 if 'options' not in kwds:
47 "checkCredentialsSpeaksFor was not passed options=options")
49 # remove the options arg
50 options = kwds['options']
52 # compute the speaking_for_xrn arg and pass it to checkCredentials
54 speaking_for_xrn = None
56 speaking_for_xrn = options.get('geni_speaking_for', None)
57 kwds['speaking_for_xrn'] = speaking_for_xrn
58 return self.checkCredentials(*args, **kwds)
60 # do not use mutable as default argument
61 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
62 def checkCredentials(self, creds, operation, xrns=None,
63 check_sliver_callback=None,
64 speaking_for_xrn=None):
69 def log_invalid_cred(cred, exception):
70 if isinstance(cred, dict) and 'geni_value' in cred:
71 cred = cred['geni_value']
72 if not isinstance(cred, str):
74 "{}: cannot validate credential {}"
75 .format(exception, cred))
76 error = ('TypeMismatch',
77 "checkCredentials: expected a string, got {} -- {}"
78 .format(type(cred), cred))
80 cred_obj = Credential(string=cred)
81 logger.info("{}: failed to validate credential dump={}"
83 cred_obj.dump_string(dump_parents=True)))
84 error = sys.exc_info()[:2]
87 # if xrns are specified they cannot be None or empty string
91 raise BadArgs("Invalid urn or hrn")
93 if not isinstance(xrns, list):
96 # slice_xrns = Xrn.filter_type(xrns, 'slice')
97 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
99 # we are not able to validate slivers in the traditional way so
100 # we make sure not to include sliver urns/hrns in the core validation
102 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
104 if not isinstance(creds, list):
106 logger.debug("Auth.checkCredentials with %d creds on hrns=%s" %
108 # won't work if either creds or hrns is empty - let's make it more
111 raise Forbidden("no credential provided")
115 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
116 speaking_for_xrn, self.trusted_cert_list)
118 if self.peer_cert and \
119 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
125 self.check(cred, operation, hrn)
127 except Exception as exc:
128 error = log_invalid_cred(cred, exc)
130 # make sure all sliver xrns are validated against the valid credentials
132 if not check_sliver_callback:
133 msg = "sliver verification callback method not found."
134 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
136 check_sliver_callback(valid, sliver_xrns)
139 raise Forbidden("Invalid credential %s -- %s" %
140 (error[0], error[1]))
144 def check(self, credential, operation, hrn=None):
146 Check the credential against the peer cert (callerGID) included
147 in the credential matches the caller that is connected to the
148 HTTPS connection, check if the credential was signed by a
149 trusted cert and check if the credential is allowed to perform
150 the specified operation.
152 cred = Credential(cred=credential)
153 self.client_cred = cred
154 logger.debug("Auth.check: handling hrn=%s and credential=%s" %
155 (hrn, cred.pretty_cred()))
157 if cred.type not in ['geni_sfa']:
158 raise CredentialNotVerifiable(
159 cred.type, "%s not supported" % cred.type)
160 self.client_gid = self.client_cred.get_gid_caller()
161 self.object_gid = self.client_cred.get_gid_object()
163 # make sure the client_gid is not blank
164 if not self.client_gid:
165 raise MissingCallerGID(self.client_cred.pretty_subject())
167 # validate the client cert if it exists
169 self.verifyPeerCert(self.peer_cert, self.client_gid)
171 # make sure the client is allowed to perform the operation
173 if not self.client_cred.can_perform(operation):
174 raise InsufficientRights(operation)
176 if self.trusted_cert_list:
177 self.client_cred.verify(self.trusted_cert_file_list,
178 self.config.SFA_CREDENTIAL_SCHEMA)
180 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
182 # Make sure the credential's target matches the specified hrn.
183 # This check does not apply to trusted peers
184 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
185 if hrn and self.client_gid.get_hrn() not in trusted_peers:
186 target_hrn = self.object_gid.get_hrn()
187 if not hrn == target_hrn:
188 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " %
192 def check_ticket(self, ticket):
194 Check if the ticket was signed by a trusted cert
196 if self.trusted_cert_list:
197 client_ticket = SfaTicket(string=ticket)
198 client_ticket.verify_chain(self.trusted_cert_list)
200 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
204 def verifyPeerCert(self, cert, gid):
205 # make sure the client_gid matches client's certificate
206 if not cert.is_pubkey(gid.get_pubkey()):
207 raise ConnectionKeyGIDMismatch(
208 gid.get_subject() + ":" + cert.get_subject())
210 def verifyGidRequestHash(self, gid, hash, arglist):
211 key = gid.get_pubkey()
212 if not key.verify_string(str(arglist), hash):
213 raise BadRequestHash(hash)
215 def verifyCredRequestHash(self, cred, hash, arglist):
216 gid = cred.get_gid_caller()
217 self.verifyGidRequestHash(gid, hash, arglist)
219 def validateGid(self, gid):
220 if self.trusted_cert_list:
221 gid.verify_chain(self.trusted_cert_list)
223 def validateCred(self, cred):
224 if self.trusted_cert_list:
225 cred.verify(self.trusted_cert_file_list)
227 def authenticateGid(self, gidStr, argList, requestHash=None):
228 gid = GID(string=gidStr)
229 self.validateGid(gid)
230 # request_hash is optional
232 self.verifyGidRequestHash(gid, requestHash, argList)
235 def authenticateCred(self, credStr, argList, requestHash=None):
236 cred = Credential(string=credStr)
237 self.validateCred(cred)
238 # request hash is optional
240 self.verifyCredRequestHash(cred, requestHash, argList)
243 def authenticateCert(self, certStr, requestHash):
244 cert = Certificate(string=certStr)
245 # xxx should be validateCred ??
246 self.validateCred(cert)
248 def gidNoop(self, gidStr, value, requestHash):
249 self.authenticateGid(gidStr, [gidStr, value], requestHash)
252 def credNoop(self, credStr, value, requestHash):
253 self.authenticateCred(credStr, [credStr, value], requestHash)
256 def verify_cred_is_me(self, credential):
258 cred = Credential(string=credential)
259 caller_gid = cred.get_gid_caller()
260 caller_hrn = caller_gid.get_hrn()
261 if caller_hrn != self.config.SFA_INTERFACE_HRN:
262 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
266 def get_auth_info(self, auth_hrn):
268 Given an authority name, return the information for that authority.
269 This is basically a stub that calls the hierarchy module.
271 @param auth_hrn human readable name of authority
274 return self.hierarchy.get_auth_info(auth_hrn)
276 def veriry_auth_belongs_to_me(self, name):
278 Verify that an authority belongs to our hierarchy.
279 This is basically left up to the implementation of the hierarchy
280 module. If the specified name does not belong, ane exception is
281 thrown indicating the caller should contact someone else.
283 @param auth_name human readable name of authority
286 # get auth info will throw an exception if the authority doesnt exist
287 self.get_auth_info(name)
289 def verify_object_belongs_to_me(self, name):
291 Verify that an object belongs to our hierarchy. By extension,
292 this implies that the authority that owns the object belongs
293 to our hierarchy. If it does not an exception is thrown.
295 @param name human readable name of object
297 auth_name = self.get_authority(name)
300 if name == self.config.SFA_INTERFACE_HRN:
302 self.verify_auth_belongs_to_me(auth_name)
304 def verify_auth_belongs_to_me(self, name):
305 # get auth info will throw an exception if the authority doesnt exist
306 self.get_auth_info(name)
308 def verify_object_permission(self, name):
310 Verify that the object gid that was specified in the credential
311 allows permission to the object 'name'. This is done by a simple
312 prefix test. For example, an object_gid for plc.arizona would
313 match the objects plc.arizona.slice1 and plc.arizona.
315 @param name human readable name to test
317 object_hrn = self.object_gid.get_hrn()
318 if object_hrn == name:
320 if name.startswith(object_hrn + "."):
322 # if name.startswith(get_authority(name)):
325 raise PermissionError(name)
327 def determine_user_rights(self, caller_hrn, reg_record):
329 Given a user credential and a record, determine what set of rights the
330 user should have to that record.
332 This is intended to replace determine_user_rights() and
333 verify_cancreate_credential()
337 type = reg_record.type
339 logger.debug("entering determine_user_rights with record %s and caller_hrn %s" %
340 (reg_record, caller_hrn))
343 # researchers in the slice are in the DB as-is
344 researcher_hrns = [user.hrn for user in reg_record.reg_researchers]
345 # locating PIs attached to that slice
346 slice_pis = reg_record.get_pis()
347 pi_hrns = [user.hrn for user in slice_pis]
348 if (caller_hrn in researcher_hrns + pi_hrns):
355 elif type == 'authority':
356 pi_hrns = [user.hrn for user in reg_record.reg_pis]
357 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
361 if (caller_hrn in pi_hrns):
364 # NOTE: for the PL implementation, this 'operators' list
365 # amounted to users with 'tech' role in that site
366 # it seems like this is not needed any longer, so for now I just drop that
367 # operator_hrns = reg_record.get('operator', [])
368 # if (caller_hrn in operator_hrns):
369 # rl.add('authority')
382 def get_authority(self, hrn):
383 return get_authority(hrn)
385 def filter_creds_by_caller(self, creds, caller_hrn_list):
387 Returns a list of creds who's gid caller matches the
390 if not isinstance(creds, list):
393 if not isinstance(caller_hrn_list, list):
394 caller_hrn_list = [caller_hrn_list]
397 tmp_cred = Credential(string=cred)
398 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: