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 not isinstance(cred, StringType):
73 "{}: cannot validate credential {}"
74 .format(exception, cred))
75 error = ('TypeMismatch',
76 "checkCredentials: expected a string, got {} -- {}"
77 .format(type(cred), cred))
79 cred_obj = Credential(string=cred)
80 logger.info("{}: failed to validate credential dump={}"
82 cred_obj.dump_string(dump_parents=True)))
83 error = sys.exc_info()[:2]
86 # if xrns are specified they cannot be None or empty string
90 raise BadArgs("Invalid urn or hrn")
92 if not isinstance(xrns, list):
95 # slice_xrns = Xrn.filter_type(xrns, 'slice')
96 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
98 # we are not able to validate slivers in the traditional way so
99 # we make sure not to include sliver urns/hrns in the core validation
101 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
103 if not isinstance(creds, list):
105 logger.debug("Auth.checkCredentials with %d creds on hrns=%s" %
107 # won't work if either creds or hrns is empty - let's make it more
110 raise Forbidden("no credential provided")
114 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
115 speaking_for_xrn, self.trusted_cert_list)
117 if self.peer_cert and \
118 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
124 self.check(cred, operation, hrn)
126 except Exception as exc:
127 error = log_invalid_cred(cred, exc)
129 # make sure all sliver xrns are validated against the valid credentials
131 if not check_sliver_callback:
132 msg = "sliver verification callback method not found."
133 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
135 check_sliver_callback(valid, sliver_xrns)
138 raise Forbidden("Invalid credential %s -- %s" %
139 (error[0], error[1]))
143 def check(self, credential, operation, hrn=None):
145 Check the credential against the peer cert (callerGID) included
146 in the credential matches the caller that is connected to the
147 HTTPS connection, check if the credential was signed by a
148 trusted cert and check if the credential is allowed to perform
149 the specified operation.
151 cred = Credential(cred=credential)
152 self.client_cred = cred
153 logger.debug("Auth.check: handling hrn=%s and credential=%s" %
154 (hrn, cred.pretty_cred()))
156 if cred.type not in ['geni_sfa']:
157 raise CredentialNotVerifiable(
158 cred.type, "%s not supported" % cred.type)
159 self.client_gid = self.client_cred.get_gid_caller()
160 self.object_gid = self.client_cred.get_gid_object()
162 # make sure the client_gid is not blank
163 if not self.client_gid:
164 raise MissingCallerGID(self.client_cred.pretty_subject())
166 # validate the client cert if it exists
168 self.verifyPeerCert(self.peer_cert, self.client_gid)
170 # make sure the client is allowed to perform the operation
172 if not self.client_cred.can_perform(operation):
173 raise InsufficientRights(operation)
175 if self.trusted_cert_list:
176 self.client_cred.verify(self.trusted_cert_file_list,
177 self.config.SFA_CREDENTIAL_SCHEMA)
179 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
181 # Make sure the credential's target matches the specified hrn.
182 # This check does not apply to trusted peers
183 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
184 if hrn and self.client_gid.get_hrn() not in trusted_peers:
185 target_hrn = self.object_gid.get_hrn()
186 if not hrn == target_hrn:
187 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " %
191 def check_ticket(self, ticket):
193 Check if the ticket was signed by a trusted cert
195 if self.trusted_cert_list:
196 client_ticket = SfaTicket(string=ticket)
197 client_ticket.verify_chain(self.trusted_cert_list)
199 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
203 def verifyPeerCert(self, cert, gid):
204 # make sure the client_gid matches client's certificate
205 if not cert.is_pubkey(gid.get_pubkey()):
206 raise ConnectionKeyGIDMismatch(
207 gid.get_subject() + ":" + cert.get_subject())
209 def verifyGidRequestHash(self, gid, hash, arglist):
210 key = gid.get_pubkey()
211 if not key.verify_string(str(arglist), hash):
212 raise BadRequestHash(hash)
214 def verifyCredRequestHash(self, cred, hash, arglist):
215 gid = cred.get_gid_caller()
216 self.verifyGidRequestHash(gid, hash, arglist)
218 def validateGid(self, gid):
219 if self.trusted_cert_list:
220 gid.verify_chain(self.trusted_cert_list)
222 def validateCred(self, cred):
223 if self.trusted_cert_list:
224 cred.verify(self.trusted_cert_file_list)
226 def authenticateGid(self, gidStr, argList, requestHash=None):
227 gid = GID(string=gidStr)
228 self.validateGid(gid)
229 # request_hash is optional
231 self.verifyGidRequestHash(gid, requestHash, argList)
234 def authenticateCred(self, credStr, argList, requestHash=None):
235 cred = Credential(string=credStr)
236 self.validateCred(cred)
237 # request hash is optional
239 self.verifyCredRequestHash(cred, requestHash, argList)
242 def authenticateCert(self, certStr, requestHash):
243 cert = Certificate(string=certStr)
244 # xxx should be validateCred ??
245 self.validateCred(cert)
247 def gidNoop(self, gidStr, value, requestHash):
248 self.authenticateGid(gidStr, [gidStr, value], requestHash)
251 def credNoop(self, credStr, value, requestHash):
252 self.authenticateCred(credStr, [credStr, value], requestHash)
255 def verify_cred_is_me(self, credential):
257 cred = Credential(string=credential)
258 caller_gid = cred.get_gid_caller()
259 caller_hrn = caller_gid.get_hrn()
260 if caller_hrn != self.config.SFA_INTERFACE_HRN:
261 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
265 def get_auth_info(self, auth_hrn):
267 Given an authority name, return the information for that authority.
268 This is basically a stub that calls the hierarchy module.
270 @param auth_hrn human readable name of authority
273 return self.hierarchy.get_auth_info(auth_hrn)
275 def veriry_auth_belongs_to_me(self, name):
277 Verify that an authority belongs to our hierarchy.
278 This is basically left up to the implementation of the hierarchy
279 module. If the specified name does not belong, ane exception is
280 thrown indicating the caller should contact someone else.
282 @param auth_name human readable name of authority
285 # get auth info will throw an exception if the authority doesnt exist
286 self.get_auth_info(name)
288 def verify_object_belongs_to_me(self, name):
290 Verify that an object belongs to our hierarchy. By extension,
291 this implies that the authority that owns the object belongs
292 to our hierarchy. If it does not an exception is thrown.
294 @param name human readable name of object
296 auth_name = self.get_authority(name)
299 if name == self.config.SFA_INTERFACE_HRN:
301 self.verify_auth_belongs_to_me(auth_name)
303 def verify_auth_belongs_to_me(self, name):
304 # get auth info will throw an exception if the authority doesnt exist
305 self.get_auth_info(name)
307 def verify_object_permission(self, name):
309 Verify that the object gid that was specified in the credential
310 allows permission to the object 'name'. This is done by a simple
311 prefix test. For example, an object_gid for plc.arizona would
312 match the objects plc.arizona.slice1 and plc.arizona.
314 @param name human readable name to test
316 object_hrn = self.object_gid.get_hrn()
317 if object_hrn == name:
319 if name.startswith(object_hrn + "."):
321 # if name.startswith(get_authority(name)):
324 raise PermissionError(name)
326 def determine_user_rights(self, caller_hrn, reg_record):
328 Given a user credential and a record, determine what set of rights the
329 user should have to that record.
331 This is intended to replace determine_user_rights() and
332 verify_cancreate_credential()
336 type = reg_record.type
338 logger.debug("entering determine_user_rights with record %s and caller_hrn %s" %
339 (reg_record, caller_hrn))
342 # researchers in the slice are in the DB as-is
343 researcher_hrns = [user.hrn for user in reg_record.reg_researchers]
344 # locating PIs attached to that slice
345 slice_pis = reg_record.get_pis()
346 pi_hrns = [user.hrn for user in slice_pis]
347 if (caller_hrn in researcher_hrns + pi_hrns):
354 elif type == 'authority':
355 pi_hrns = [user.hrn for user in reg_record.reg_pis]
356 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
360 if (caller_hrn in pi_hrns):
363 # NOTE: for the PL implementation, this 'operators' list
364 # amounted to users with 'tech' role in that site
365 # it seems like this is not needed any longer, so for now I just drop that
366 # operator_hrns = reg_record.get('operator', [])
367 # if (caller_hrn in operator_hrns):
368 # rl.add('authority')
381 def get_authority(self, hrn):
382 return get_authority(hrn)
384 def filter_creds_by_caller(self, creds, caller_hrn_list):
386 Returns a list of creds who's gid caller matches the
389 if not isinstance(creds, list):
392 if not isinstance(caller_hrn_list, list):
393 caller_hrn_list = [caller_hrn_list]
396 tmp_cred = Credential(string=cred)
397 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: