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):
71 if not isinstance(cred, StringType):
73 "cannot validate credential %s - expecting a string" % cred)
74 error = ('TypeMismatch',
75 "checkCredentials: expected a string, received {} -- {}"
76 .format(type(cred), cred))
78 cred_obj = Credential(string=cred)
79 logger.info("failed to validate credential - dump=%s" %
80 cred_obj.dump_string(dump_parents=True))
81 error = sys.exc_info()[:2]
84 # if xrns are specified they cannot be None or empty string
88 raise BadArgs("Invalid urn or hrn")
90 if not isinstance(xrns, list):
93 slice_xrns = Xrn.filter_type(xrns, 'slice')
94 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
96 # we are not able to validate slivers in the traditional way so
97 # we make sure not to include sliver urns/hrns in the core validation
99 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
101 if not isinstance(creds, list):
103 logger.debug("Auth.checkCredentials with %d creds on hrns=%s" %
105 # won't work if either creds or hrns is empty - let's make it more
108 raise Forbidden("no credential provided")
112 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert,
113 speaking_for_xrn, self.trusted_cert_list)
115 if self.peer_cert and \
116 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
122 self.check(cred, operation, hrn)
125 error = log_invalid_cred(cred)
127 # make sure all sliver xrns are validated against the valid credentials
129 if not check_sliver_callback:
130 msg = "sliver verification callback method not found."
131 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
133 check_sliver_callback(valid, sliver_xrns)
136 raise Forbidden("Invalid credential %s -- %s" %
137 (error[0], error[1]))
141 def check(self, credential, operation, hrn=None):
143 Check the credential against the peer cert (callerGID) included
144 in the credential matches the caller that is connected to the
145 HTTPS connection, check if the credential was signed by a
146 trusted cert and check if the credential is allowed to perform
147 the specified operation.
149 cred = Credential(cred=credential)
150 self.client_cred = cred
151 logger.debug("Auth.check: handling hrn=%s and credential=%s" %
152 (hrn, cred.pretty_cred()))
154 if cred.type not in ['geni_sfa']:
155 raise CredentialNotVerifiable(
156 cred.type, "%s not supported" % cred.type)
157 self.client_gid = self.client_cred.get_gid_caller()
158 self.object_gid = self.client_cred.get_gid_object()
160 # make sure the client_gid is not blank
161 if not self.client_gid:
162 raise MissingCallerGID(self.client_cred.pretty_subject())
164 # validate the client cert if it exists
166 self.verifyPeerCert(self.peer_cert, self.client_gid)
168 # make sure the client is allowed to perform the operation
170 if not self.client_cred.can_perform(operation):
171 raise InsufficientRights(operation)
173 if self.trusted_cert_list:
174 self.client_cred.verify(self.trusted_cert_file_list,
175 self.config.SFA_CREDENTIAL_SCHEMA)
177 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
179 # Make sure the credential's target matches the specified hrn.
180 # This check does not apply to trusted peers
181 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
182 if hrn and self.client_gid.get_hrn() not in trusted_peers:
183 target_hrn = self.object_gid.get_hrn()
184 if not hrn == target_hrn:
185 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " %
189 def check_ticket(self, ticket):
191 Check if the ticket was signed by a trusted cert
193 if self.trusted_cert_list:
194 client_ticket = SfaTicket(string=ticket)
195 client_ticket.verify_chain(self.trusted_cert_list)
197 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
201 def verifyPeerCert(self, cert, gid):
202 # make sure the client_gid matches client's certificate
203 if not cert.is_pubkey(gid.get_pubkey()):
204 raise ConnectionKeyGIDMismatch(
205 gid.get_subject() + ":" + cert.get_subject())
207 def verifyGidRequestHash(self, gid, hash, arglist):
208 key = gid.get_pubkey()
209 if not key.verify_string(str(arglist), hash):
210 raise BadRequestHash(hash)
212 def verifyCredRequestHash(self, cred, hash, arglist):
213 gid = cred.get_gid_caller()
214 self.verifyGidRequestHash(gid, hash, arglist)
216 def validateGid(self, gid):
217 if self.trusted_cert_list:
218 gid.verify_chain(self.trusted_cert_list)
220 def validateCred(self, cred):
221 if self.trusted_cert_list:
222 cred.verify(self.trusted_cert_file_list)
224 def authenticateGid(self, gidStr, argList, requestHash=None):
225 gid = GID(string=gidStr)
226 self.validateGid(gid)
227 # request_hash is optional
229 self.verifyGidRequestHash(gid, requestHash, argList)
232 def authenticateCred(self, credStr, argList, requestHash=None):
233 cred = Credential(string=credStr)
234 self.validateCred(cred)
235 # request hash is optional
237 self.verifyCredRequestHash(cred, requestHash, argList)
240 def authenticateCert(self, certStr, requestHash):
241 cert = Certificate(string=certStr)
242 # xxx should be validateCred ??
243 self.validateCred(cert)
245 def gidNoop(self, gidStr, value, requestHash):
246 self.authenticateGid(gidStr, [gidStr, value], requestHash)
249 def credNoop(self, credStr, value, requestHash):
250 self.authenticateCred(credStr, [credStr, value], requestHash)
253 def verify_cred_is_me(self, credential):
255 cred = Credential(string=credential)
256 caller_gid = cred.get_gid_caller()
257 caller_hrn = caller_gid.get_hrn()
258 if caller_hrn != self.config.SFA_INTERFACE_HRN:
259 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
263 def get_auth_info(self, auth_hrn):
265 Given an authority name, return the information for that authority.
266 This is basically a stub that calls the hierarchy module.
268 @param auth_hrn human readable name of authority
271 return self.hierarchy.get_auth_info(auth_hrn)
273 def veriry_auth_belongs_to_me(self, name):
275 Verify that an authority belongs to our hierarchy.
276 This is basically left up to the implementation of the hierarchy
277 module. If the specified name does not belong, ane exception is
278 thrown indicating the caller should contact someone else.
280 @param auth_name human readable name of authority
283 # get auth info will throw an exception if the authority doesnt exist
284 self.get_auth_info(name)
286 def verify_object_belongs_to_me(self, name):
288 Verify that an object belongs to our hierarchy. By extension,
289 this implies that the authority that owns the object belongs
290 to our hierarchy. If it does not an exception is thrown.
292 @param name human readable name of object
294 auth_name = self.get_authority(name)
297 if name == self.config.SFA_INTERFACE_HRN:
299 self.verify_auth_belongs_to_me(auth_name)
301 def verify_auth_belongs_to_me(self, name):
302 # get auth info will throw an exception if the authority doesnt exist
303 self.get_auth_info(name)
305 def verify_object_permission(self, name):
307 Verify that the object gid that was specified in the credential
308 allows permission to the object 'name'. This is done by a simple
309 prefix test. For example, an object_gid for plc.arizona would
310 match the objects plc.arizona.slice1 and plc.arizona.
312 @param name human readable name to test
314 object_hrn = self.object_gid.get_hrn()
315 if object_hrn == name:
317 if name.startswith(object_hrn + "."):
319 # if name.startswith(get_authority(name)):
322 raise PermissionError(name)
324 def determine_user_rights(self, caller_hrn, reg_record):
326 Given a user credential and a record, determine what set of rights the
327 user should have to that record.
329 This is intended to replace determine_user_rights() and
330 verify_cancreate_credential()
334 type = reg_record.type
336 logger.debug("entering determine_user_rights with record %s and caller_hrn %s" %
337 (reg_record, caller_hrn))
340 # researchers in the slice are in the DB as-is
341 researcher_hrns = [user.hrn for user in reg_record.reg_researchers]
342 # locating PIs attached to that slice
343 slice_pis = reg_record.get_pis()
344 pi_hrns = [user.hrn for user in slice_pis]
345 if (caller_hrn in researcher_hrns + pi_hrns):
352 elif type == 'authority':
353 pi_hrns = [user.hrn for user in reg_record.reg_pis]
354 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
358 if (caller_hrn in pi_hrns):
361 # NOTE: for the PL implementation, this 'operators' list
362 # amounted to users with 'tech' role in that site
363 # it seems like this is not needed any longer, so for now I just drop that
364 # operator_hrns = reg_record.get('operator', [])
365 # if (caller_hrn in operator_hrns):
366 # rl.add('authority')
379 def get_authority(self, hrn):
380 return get_authority(hrn)
382 def filter_creds_by_caller(self, creds, caller_hrn_list):
384 Returns a list of creds who's gid caller matches the
387 if not isinstance(creds, list):
390 if not isinstance(caller_hrn_list, list):
391 caller_hrn_list = [caller_hrn_list]
394 tmp_cred = Credential(string=cred)
395 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: