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 # do not use mutable as default argument
40 # http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments
41 def checkCredentials(self, creds, operation, xrns=None,
42 check_sliver_callback=None, speaking_for_hrn=None, options=None):
43 if xrns is None: xrns=[]
44 def log_invalid_cred(cred):
45 cred_obj=Credential(string=cred)
46 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
47 error = sys.exc_info()[:2]
50 # if xrns are specified they cannot be None or empty string
54 raise BadArgs("Invalid urn or hrn")
57 if not isinstance(xrns, list):
60 slice_xrns = Xrn.filter_type(xrns, 'slice')
61 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
63 # we are not able to validate slivers in the traditional way so
64 # we make sure not to include sliver urns/hrns in the core validation loop
65 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
67 if not isinstance(creds, list):
69 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
70 # won't work if either creds or hrns is empty - let's make it more explicit
71 if not creds: raise Forbidden("no credential provided")
72 if not hrns: hrns = [None]
75 # if speaks for gid matches caller cert then we've found a valid
76 # speaks for credential
77 speaks_for_gid = determine_speaks_for(logger, creds, self.peer_cert, \
78 options, self.trusted_cert_list)
80 if self.peer_cert and \
81 not self.peer_cert.is_pubkey(speaks_for_gid.get_pubkey()):
87 self.check(cred, operation, hrn)
90 error = log_invalid_cred(cred)
92 # make sure all sliver xrns are validated against the valid credentials
94 if not check_sliver_callback:
95 msg = "sliver verification callback method not found."
96 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
98 check_sliver_callback(valid, sliver_xrns)
101 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
103 if speaking_for_hrn and not speaks_for_cred:
104 raise InsufficientRights('Access denied: "geni_speaking_for" option specified but no valid speaks for credential found: %s -- %s' % (error[0],error[1]))
109 def check(self, credential, operation, hrn = None):
111 Check the credential against the peer cert (callerGID included
112 in the credential matches the caller that is connected to the
113 HTTPS connection, check if the credential was signed by a
114 trusted cert and check if the credential is allowed to perform
115 the specified operation.
117 cred = Credential(cred=credential)
118 self.client_cred = cred
119 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
120 (hrn,cred.get_summary_tostring()))
122 if cred.type not in ['geni_sfa']:
123 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
124 self.client_gid = self.client_cred.get_gid_caller()
125 self.object_gid = self.client_cred.get_gid_object()
127 # make sure the client_gid is not blank
128 if not self.client_gid:
129 raise MissingCallerGID(self.client_cred.get_subject())
131 # validate the client cert if it exists
133 self.verifyPeerCert(self.peer_cert, self.client_gid)
135 # make sure the client is allowed to perform the operation
137 if not self.client_cred.can_perform(operation):
138 raise InsufficientRights(operation)
140 if self.trusted_cert_list:
141 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
143 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
145 # Make sure the credential's target matches the specified hrn.
146 # This check does not apply to trusted peers
147 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
148 if hrn and self.client_gid.get_hrn() not in trusted_peers:
149 target_hrn = self.object_gid.get_hrn()
150 if not hrn == target_hrn:
151 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
155 def check_ticket(self, ticket):
157 Check if the tickt was signed by a trusted cert
159 if self.trusted_cert_list:
160 client_ticket = SfaTicket(string=ticket)
161 client_ticket.verify_chain(self.trusted_cert_list)
163 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
167 def verifyPeerCert(self, cert, gid):
168 # make sure the client_gid matches client's certificate
169 if not cert.is_pubkey(gid.get_pubkey()):
170 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
172 def verifyGidRequestHash(self, gid, hash, arglist):
173 key = gid.get_pubkey()
174 if not key.verify_string(str(arglist), hash):
175 raise BadRequestHash(hash)
177 def verifyCredRequestHash(self, cred, hash, arglist):
178 gid = cred.get_gid_caller()
179 self.verifyGidRequestHash(gid, hash, arglist)
181 def validateGid(self, gid):
182 if self.trusted_cert_list:
183 gid.verify_chain(self.trusted_cert_list)
185 def validateCred(self, cred):
186 if self.trusted_cert_list:
187 cred.verify(self.trusted_cert_file_list)
189 def authenticateGid(self, gidStr, argList, requestHash=None):
190 gid = GID(string = gidStr)
191 self.validateGid(gid)
192 # request_hash is optional
194 self.verifyGidRequestHash(gid, requestHash, argList)
197 def authenticateCred(self, credStr, argList, requestHash=None):
198 cred = Credential(string = credStr)
199 self.validateCred(cred)
200 # request hash is optional
202 self.verifyCredRequestHash(cred, requestHash, argList)
205 def authenticateCert(self, certStr, requestHash):
206 cert = Certificate(string=certStr)
207 # xxx should be validateCred ??
208 self.validateCred(cert)
210 def gidNoop(self, gidStr, value, requestHash):
211 self.authenticateGid(gidStr, [gidStr, value], requestHash)
214 def credNoop(self, credStr, value, requestHash):
215 self.authenticateCred(credStr, [credStr, value], requestHash)
218 def verify_cred_is_me(self, credential):
220 cred = Credential(string=credential)
221 caller_gid = cred.get_gid_caller()
222 caller_hrn = caller_gid.get_hrn()
223 if caller_hrn != self.config.SFA_INTERFACE_HRN:
224 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
228 def get_auth_info(self, auth_hrn):
230 Given an authority name, return the information for that authority.
231 This is basically a stub that calls the hierarchy module.
233 @param auth_hrn human readable name of authority
236 return self.hierarchy.get_auth_info(auth_hrn)
239 def veriry_auth_belongs_to_me(self, name):
241 Verify that an authority belongs to our hierarchy.
242 This is basically left up to the implementation of the hierarchy
243 module. If the specified name does not belong, ane exception is
244 thrown indicating the caller should contact someone else.
246 @param auth_name human readable name of authority
249 # get auth info will throw an exception if the authority doesnt exist
250 self.get_auth_info(name)
253 def verify_object_belongs_to_me(self, name):
255 Verify that an object belongs to our hierarchy. By extension,
256 this implies that the authority that owns the object belongs
257 to our hierarchy. If it does not an exception is thrown.
259 @param name human readable name of object
261 auth_name = self.get_authority(name)
264 if name == self.config.SFA_INTERFACE_HRN:
266 self.verify_auth_belongs_to_me(auth_name)
268 def verify_auth_belongs_to_me(self, name):
269 # get auth info will throw an exception if the authority doesnt exist
270 self.get_auth_info(name)
273 def verify_object_permission(self, name):
275 Verify that the object gid that was specified in the credential
276 allows permission to the object 'name'. This is done by a simple
277 prefix test. For example, an object_gid for plc.arizona would
278 match the objects plc.arizona.slice1 and plc.arizona.
280 @param name human readable name to test
282 object_hrn = self.object_gid.get_hrn()
283 if object_hrn == name:
285 if name.startswith(object_hrn + "."):
287 #if name.startswith(get_authority(name)):
290 raise PermissionError(name)
292 def determine_user_rights(self, caller_hrn, reg_record):
294 Given a user credential and a record, determine what set of rights the
295 user should have to that record.
297 This is intended to replace determine_user_rights() and
298 verify_cancreate_credential()
302 type = reg_record.type
304 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
307 # researchers in the slice are in the DB as-is
308 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
309 # locating PIs attached to that slice
310 slice_pis=reg_record.get_pis()
311 pi_hrns = [ user.hrn for user in slice_pis ]
312 if (caller_hrn in researcher_hrns + pi_hrns):
319 elif type == 'authority':
320 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
321 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
325 if (caller_hrn in pi_hrns):
328 # NOTE: for the PL implementation, this 'operators' list
329 # amounted to users with 'tech' role in that site
330 # it seems like this is not needed any longer, so for now I just drop that
331 # operator_hrns = reg_record.get('operator',[])
332 # if (caller_hrn in operator_hrns):
333 # rl.add('authority')
346 def get_authority(self, hrn):
347 return get_authority(hrn)
349 def filter_creds_by_caller(self, creds, caller_hrn_list):
351 Returns a list of creds who's gid caller matches the
354 if not isinstance(creds, list):
357 if not isinstance(caller_hrn_list, list):
358 caller_hrn_list = [caller_hrn_list]
361 tmp_cred = Credential(string=cred)
362 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: