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
24 Credential based authentication
27 def __init__(self, peer_cert = None, config = None ):
28 self.peer_cert = peer_cert
29 self.hierarchy = Hierarchy()
31 self.config = Config()
32 self.load_trusted_certs()
34 def load_trusted_certs(self):
35 self.trusted_cert_list = TrustedRoots(self.config.get_trustedroots_dir()).get_list()
36 self.trusted_cert_file_list = TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
38 def checkCredentials(self, creds, operation, xrns=[], check_sliver_callback=None, speaking_for_hrn=None):
40 def log_invalid_cred(cred):
41 cred_obj=Credential(string=cred)
42 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
43 error = sys.exc_info()[:2]
46 # if xrns are specified they cannot be None or empty string
50 raise BadArgs("Invalid urn or hrn")
53 if not isinstance(xrns, list):
56 slice_xrns = Xrn.filter_type(xrns, 'slice')
57 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
59 # we are not able to validate slivers in the traditional way so
60 # we make sure not to include sliver urns/hrns in the core validation loop
61 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
63 speaks_for_cred = None
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]
73 self.check(cred, operation, hrn)
78 self.check(cred, operation, speaking_for_hrn)
79 speaks_for_cred = cred
82 error = log_invalid_cred(cred)
84 error = log_invalid_cred(cred)
87 # make sure all sliver xrns are validated against the valid credentials
89 if not check_sliver_callback:
90 msg = "sliver verification callback method not found."
91 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
93 check_sliver_callback(valid, sliver_xrns)
96 raise Forbidden("Invalid credential")
98 if speaking_for_hrn and not speaks_for_cred:
99 raise InsufficientRights('Access denied: "geni_speaking_for" option specified but no valid speaks for credential found: %s -- %s' % (error[0],error[1]))
104 def check(self, credential, operation, hrn = None):
106 Check the credential against the peer cert (callerGID included
107 in the credential matches the caller that is connected to the
108 HTTPS connection, check if the credential was signed by a
109 trusted cert and check if the credential is allowed to perform
110 the specified operation.
112 cred = Credential(cred=credential)
113 self.client_cred = cred
114 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
115 (hrn,cred.get_summary_tostring()))
117 if cred.type not in ['geni_sfa']:
118 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
119 self.client_gid = self.client_cred.get_gid_caller()
120 self.object_gid = self.client_cred.get_gid_object()
122 # make sure the client_gid is not blank
123 if not self.client_gid:
124 raise MissingCallerGID(self.client_cred.get_subject())
126 # validate the client cert if it exists
128 self.verifyPeerCert(self.peer_cert, self.client_gid)
130 # make sure the client is allowed to perform the operation
132 if not self.client_cred.can_perform(operation):
133 raise InsufficientRights(operation)
135 if self.trusted_cert_list:
136 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
138 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
140 # Make sure the credential's target matches the specified hrn.
141 # This check does not apply to trusted peers
142 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
143 if hrn and self.client_gid.get_hrn() not in trusted_peers:
144 target_hrn = self.object_gid.get_hrn()
145 if not hrn == target_hrn:
146 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
150 def check_ticket(self, ticket):
152 Check if the tickt was signed by a trusted cert
154 if self.trusted_cert_list:
155 client_ticket = SfaTicket(string=ticket)
156 client_ticket.verify_chain(self.trusted_cert_list)
158 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
162 def verifyPeerCert(self, cert, gid):
163 # make sure the client_gid matches client's certificate
164 if not cert.is_pubkey(gid.get_pubkey()):
165 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
167 def verifyGidRequestHash(self, gid, hash, arglist):
168 key = gid.get_pubkey()
169 if not key.verify_string(str(arglist), hash):
170 raise BadRequestHash(hash)
172 def verifyCredRequestHash(self, cred, hash, arglist):
173 gid = cred.get_gid_caller()
174 self.verifyGidRequestHash(gid, hash, arglist)
176 def validateGid(self, gid):
177 if self.trusted_cert_list:
178 gid.verify_chain(self.trusted_cert_list)
180 def validateCred(self, cred):
181 if self.trusted_cert_list:
182 cred.verify(self.trusted_cert_file_list)
184 def authenticateGid(self, gidStr, argList, requestHash=None):
185 gid = GID(string = gidStr)
186 self.validateGid(gid)
187 # request_hash is optional
189 self.verifyGidRequestHash(gid, requestHash, argList)
192 def authenticateCred(self, credStr, argList, requestHash=None):
193 cred = Credential(string = credStr)
194 self.validateCred(cred)
195 # request hash is optional
197 self.verifyCredRequestHash(cred, requestHash, argList)
200 def authenticateCert(self, certStr, requestHash):
201 cert = Certificate(string=certStr)
202 # xxx should be validateCred ??
203 self.validateCred(cert)
205 def gidNoop(self, gidStr, value, requestHash):
206 self.authenticateGid(gidStr, [gidStr, value], requestHash)
209 def credNoop(self, credStr, value, requestHash):
210 self.authenticateCred(credStr, [credStr, value], requestHash)
213 def verify_cred_is_me(self, credential):
215 cred = Credential(string=credential)
216 caller_gid = cred.get_gid_caller()
217 caller_hrn = caller_gid.get_hrn()
218 if caller_hrn != self.config.SFA_INTERFACE_HRN:
219 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
223 def get_auth_info(self, auth_hrn):
225 Given an authority name, return the information for that authority.
226 This is basically a stub that calls the hierarchy module.
228 @param auth_hrn human readable name of authority
231 return self.hierarchy.get_auth_info(auth_hrn)
234 def veriry_auth_belongs_to_me(self, name):
236 Verify that an authority belongs to our hierarchy.
237 This is basically left up to the implementation of the hierarchy
238 module. If the specified name does not belong, ane exception is
239 thrown indicating the caller should contact someone else.
241 @param auth_name human readable name of authority
244 # get auth info will throw an exception if the authority doesnt exist
245 self.get_auth_info(name)
248 def verify_object_belongs_to_me(self, name):
250 Verify that an object belongs to our hierarchy. By extension,
251 this implies that the authority that owns the object belongs
252 to our hierarchy. If it does not an exception is thrown.
254 @param name human readable name of object
256 auth_name = self.get_authority(name)
259 if name == self.config.SFA_INTERFACE_HRN:
261 self.verify_auth_belongs_to_me(auth_name)
263 def verify_auth_belongs_to_me(self, name):
264 # get auth info will throw an exception if the authority doesnt exist
265 self.get_auth_info(name)
268 def verify_object_permission(self, name):
270 Verify that the object gid that was specified in the credential
271 allows permission to the object 'name'. This is done by a simple
272 prefix test. For example, an object_gid for plc.arizona would
273 match the objects plc.arizona.slice1 and plc.arizona.
275 @param name human readable name to test
277 object_hrn = self.object_gid.get_hrn()
278 if object_hrn == name:
280 if name.startswith(object_hrn + "."):
282 #if name.startswith(get_authority(name)):
285 raise PermissionError(name)
287 def determine_user_rights(self, caller_hrn, reg_record):
289 Given a user credential and a record, determine what set of rights the
290 user should have to that record.
292 This is intended to replace determine_user_rights() and
293 verify_cancreate_credential()
297 type = reg_record.type
299 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
302 # researchers in the slice are in the DB as-is
303 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
304 # locating PIs attached to that slice
305 slice_pis=reg_record.get_pis()
306 pi_hrns = [ user.hrn for user in slice_pis ]
307 if (caller_hrn in researcher_hrns + pi_hrns):
314 elif type == 'authority':
315 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
316 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
320 if (caller_hrn in pi_hrns):
323 # NOTE: for the PL implementation, this 'operators' list
324 # amounted to users with 'tech' role in that site
325 # it seems like this is not needed any longer, so for now I just drop that
326 # operator_hrns = reg_record.get('operator',[])
327 # if (caller_hrn in operator_hrns):
328 # rl.add('authority')
341 def get_authority(self, hrn):
342 return get_authority(hrn)
344 def filter_creds_by_caller(self, creds, caller_hrn_list):
346 Returns a list of creds who's gid caller matches the
349 if not isinstance(creds, list):
352 if not isinstance(caller_hrn_list, list):
353 caller_hrn_list = [caller_hrn_list]
356 tmp_cred = Credential(string=cred)
357 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: