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 def checkCredentials(self, creds, operation, xrns=[], check_sliver_callback=None, speaking_for_hrn=None):
41 def log_invalid_cred(cred):
42 cred_obj=Credential(string=cred)
43 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
44 error = sys.exc_info()[:2]
47 # if xrns are specified they cannot be None or empty string
51 raise BadArgs("Invalid urn or hrn")
54 if not isinstance(xrns, list):
57 slice_xrns = Xrn.filter_type(xrns, 'slice')
58 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
60 # we are not able to validate slivers in the traditional way so
61 # we make sure not to include sliver urns/hrns in the core validation loop
62 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
64 if not isinstance(creds, list):
66 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
67 # won't work if either creds or hrns is empty - let's make it more explicit
68 if not creds: raise Forbidden("no credential provided")
69 if not hrns: hrns = [None]
72 if self.peer_cert and \
73 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
79 self.check(cred, operation, hrn)
82 error = log_invalid_cred(cred)
84 # make sure all sliver xrns are validated against the valid credentials
86 if not check_sliver_callback:
87 msg = "sliver verification callback method not found."
88 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
90 check_sliver_callback(valid, sliver_xrns)
93 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
95 if speaking_for_hrn and not speaks_for_cred:
96 raise InsufficientRights('Access denied: "geni_speaking_for" option specified but no valid speaks for credential found: %s -- %s' % (error[0],error[1]))
101 def check(self, credential, operation, hrn = None):
103 Check the credential against the peer cert (callerGID included
104 in the credential matches the caller that is connected to the
105 HTTPS connection, check if the credential was signed by a
106 trusted cert and check if the credential is allowed to perform
107 the specified operation.
109 cred = Credential(cred=credential)
110 self.client_cred = cred
111 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
112 (hrn,cred.get_summary_tostring()))
114 if cred.type not in ['geni_sfa']:
115 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
116 self.client_gid = self.client_cred.get_gid_caller()
117 self.object_gid = self.client_cred.get_gid_object()
119 # make sure the client_gid is not blank
120 if not self.client_gid:
121 raise MissingCallerGID(self.client_cred.get_subject())
123 # validate the client cert if it exists
125 self.verifyPeerCert(self.peer_cert, self.client_gid)
127 # make sure the client is allowed to perform the operation
129 if not self.client_cred.can_perform(operation):
130 raise InsufficientRights(operation)
132 if self.trusted_cert_list:
133 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
135 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
137 # Make sure the credential's target matches the specified hrn.
138 # This check does not apply to trusted peers
139 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
140 if hrn and self.client_gid.get_hrn() not in trusted_peers:
141 target_hrn = self.object_gid.get_hrn()
142 if not hrn == target_hrn:
143 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
147 def check_ticket(self, ticket):
149 Check if the tickt was signed by a trusted cert
151 if self.trusted_cert_list:
152 client_ticket = SfaTicket(string=ticket)
153 client_ticket.verify_chain(self.trusted_cert_list)
155 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
159 def verifyPeerCert(self, cert, gid):
160 # make sure the client_gid matches client's certificate
161 if not cert.is_pubkey(gid.get_pubkey()):
162 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
164 def verifyGidRequestHash(self, gid, hash, arglist):
165 key = gid.get_pubkey()
166 if not key.verify_string(str(arglist), hash):
167 raise BadRequestHash(hash)
169 def verifyCredRequestHash(self, cred, hash, arglist):
170 gid = cred.get_gid_caller()
171 self.verifyGidRequestHash(gid, hash, arglist)
173 def validateGid(self, gid):
174 if self.trusted_cert_list:
175 gid.verify_chain(self.trusted_cert_list)
177 def validateCred(self, cred):
178 if self.trusted_cert_list:
179 cred.verify(self.trusted_cert_file_list)
181 def authenticateGid(self, gidStr, argList, requestHash=None):
182 gid = GID(string = gidStr)
183 self.validateGid(gid)
184 # request_hash is optional
186 self.verifyGidRequestHash(gid, requestHash, argList)
189 def authenticateCred(self, credStr, argList, requestHash=None):
190 cred = Credential(string = credStr)
191 self.validateCred(cred)
192 # request hash is optional
194 self.verifyCredRequestHash(cred, requestHash, argList)
197 def authenticateCert(self, certStr, requestHash):
198 cert = Certificate(string=certStr)
199 # xxx should be validateCred ??
200 self.validateCred(cert)
202 def gidNoop(self, gidStr, value, requestHash):
203 self.authenticateGid(gidStr, [gidStr, value], requestHash)
206 def credNoop(self, credStr, value, requestHash):
207 self.authenticateCred(credStr, [credStr, value], requestHash)
210 def verify_cred_is_me(self, credential):
212 cred = Credential(string=credential)
213 caller_gid = cred.get_gid_caller()
214 caller_hrn = caller_gid.get_hrn()
215 if caller_hrn != self.config.SFA_INTERFACE_HRN:
216 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
220 def get_auth_info(self, auth_hrn):
222 Given an authority name, return the information for that authority.
223 This is basically a stub that calls the hierarchy module.
225 @param auth_hrn human readable name of authority
228 return self.hierarchy.get_auth_info(auth_hrn)
231 def veriry_auth_belongs_to_me(self, name):
233 Verify that an authority belongs to our hierarchy.
234 This is basically left up to the implementation of the hierarchy
235 module. If the specified name does not belong, ane exception is
236 thrown indicating the caller should contact someone else.
238 @param auth_name human readable name of authority
241 # get auth info will throw an exception if the authority doesnt exist
242 self.get_auth_info(name)
245 def verify_object_belongs_to_me(self, name):
247 Verify that an object belongs to our hierarchy. By extension,
248 this implies that the authority that owns the object belongs
249 to our hierarchy. If it does not an exception is thrown.
251 @param name human readable name of object
253 auth_name = self.get_authority(name)
256 if name == self.config.SFA_INTERFACE_HRN:
258 self.verify_auth_belongs_to_me(auth_name)
260 def verify_auth_belongs_to_me(self, name):
261 # get auth info will throw an exception if the authority doesnt exist
262 self.get_auth_info(name)
265 def verify_object_permission(self, name):
267 Verify that the object gid that was specified in the credential
268 allows permission to the object 'name'. This is done by a simple
269 prefix test. For example, an object_gid for plc.arizona would
270 match the objects plc.arizona.slice1 and plc.arizona.
272 @param name human readable name to test
274 object_hrn = self.object_gid.get_hrn()
275 if object_hrn == name:
277 if name.startswith(object_hrn + "."):
279 #if name.startswith(get_authority(name)):
282 raise PermissionError(name)
284 def determine_user_rights(self, caller_hrn, reg_record):
286 Given a user credential and a record, determine what set of rights the
287 user should have to that record.
289 This is intended to replace determine_user_rights() and
290 verify_cancreate_credential()
294 type = reg_record.type
296 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
299 # researchers in the slice are in the DB as-is
300 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
301 # locating PIs attached to that slice
302 slice_pis=reg_record.get_pis()
303 pi_hrns = [ user.hrn for user in slice_pis ]
304 if (caller_hrn in researcher_hrns + pi_hrns):
311 elif type == 'authority':
312 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
313 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
317 if (caller_hrn in pi_hrns):
320 # NOTE: for the PL implementation, this 'operators' list
321 # amounted to users with 'tech' role in that site
322 # it seems like this is not needed any longer, so for now I just drop that
323 # operator_hrns = reg_record.get('operator',[])
324 # if (caller_hrn in operator_hrns):
325 # rl.add('authority')
338 def get_authority(self, hrn):
339 return get_authority(hrn)
341 def filter_creds_by_caller(self, creds, caller_hrn_list):
343 Returns a list of creds who's gid caller matches the
346 if not isinstance(creds, list):
349 if not isinstance(caller_hrn_list, list):
350 caller_hrn_list = [caller_hrn_list]
353 tmp_cred = Credential(string=cred)
354 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: