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]
74 self.check(cred, operation, hrn)
79 self.check(cred, operation, speaking_for_hrn)
80 speaks_for_cred = cred
83 error = log_invalid_cred(cred)
85 error = log_invalid_cred(cred)
88 # make sure all sliver xrns are validated against the valid credentials
90 if not check_sliver_callback:
91 msg = "sliver verification callback method not found."
92 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
94 check_sliver_callback(valid, sliver_xrns)
97 raise Forbidden("Invalid credential %s -- %s"%(error[0],error[1]))
99 if speaking_for_hrn and not speaks_for_cred:
100 raise InsufficientRights('Access denied: "geni_speaking_for" option specified but no valid speaks for credential found: %s -- %s' % (error[0],error[1]))
105 def check(self, credential, operation, hrn = None):
107 Check the credential against the peer cert (callerGID included
108 in the credential matches the caller that is connected to the
109 HTTPS connection, check if the credential was signed by a
110 trusted cert and check if the credential is allowed to perform
111 the specified operation.
113 cred = Credential(cred=credential)
114 self.client_cred = cred
115 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
116 (hrn,cred.get_summary_tostring()))
118 if cred.type not in ['geni_sfa']:
119 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
120 self.client_gid = self.client_cred.get_gid_caller()
121 self.object_gid = self.client_cred.get_gid_object()
123 # make sure the client_gid is not blank
124 if not self.client_gid:
125 raise MissingCallerGID(self.client_cred.get_subject())
127 # validate the client cert if it exists
129 self.verifyPeerCert(self.peer_cert, self.client_gid)
131 # make sure the client is allowed to perform the operation
133 if not self.client_cred.can_perform(operation):
134 raise InsufficientRights(operation)
136 if self.trusted_cert_list:
137 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
139 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
141 # Make sure the credential's target matches the specified hrn.
142 # This check does not apply to trusted peers
143 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
144 if hrn and self.client_gid.get_hrn() not in trusted_peers:
145 target_hrn = self.object_gid.get_hrn()
146 if not hrn == target_hrn:
147 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
151 def check_ticket(self, ticket):
153 Check if the tickt was signed by a trusted cert
155 if self.trusted_cert_list:
156 client_ticket = SfaTicket(string=ticket)
157 client_ticket.verify_chain(self.trusted_cert_list)
159 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
163 def verifyPeerCert(self, cert, gid):
164 # make sure the client_gid matches client's certificate
165 if not cert.is_pubkey(gid.get_pubkey()):
166 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
168 def verifyGidRequestHash(self, gid, hash, arglist):
169 key = gid.get_pubkey()
170 if not key.verify_string(str(arglist), hash):
171 raise BadRequestHash(hash)
173 def verifyCredRequestHash(self, cred, hash, arglist):
174 gid = cred.get_gid_caller()
175 self.verifyGidRequestHash(gid, hash, arglist)
177 def validateGid(self, gid):
178 if self.trusted_cert_list:
179 gid.verify_chain(self.trusted_cert_list)
181 def validateCred(self, cred):
182 if self.trusted_cert_list:
183 cred.verify(self.trusted_cert_file_list)
185 def authenticateGid(self, gidStr, argList, requestHash=None):
186 gid = GID(string = gidStr)
187 self.validateGid(gid)
188 # request_hash is optional
190 self.verifyGidRequestHash(gid, requestHash, argList)
193 def authenticateCred(self, credStr, argList, requestHash=None):
194 cred = Credential(string = credStr)
195 self.validateCred(cred)
196 # request hash is optional
198 self.verifyCredRequestHash(cred, requestHash, argList)
201 def authenticateCert(self, certStr, requestHash):
202 cert = Certificate(string=certStr)
203 # xxx should be validateCred ??
204 self.validateCred(cert)
206 def gidNoop(self, gidStr, value, requestHash):
207 self.authenticateGid(gidStr, [gidStr, value], requestHash)
210 def credNoop(self, credStr, value, requestHash):
211 self.authenticateCred(credStr, [credStr, value], requestHash)
214 def verify_cred_is_me(self, credential):
216 cred = Credential(string=credential)
217 caller_gid = cred.get_gid_caller()
218 caller_hrn = caller_gid.get_hrn()
219 if caller_hrn != self.config.SFA_INTERFACE_HRN:
220 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
224 def get_auth_info(self, auth_hrn):
226 Given an authority name, return the information for that authority.
227 This is basically a stub that calls the hierarchy module.
229 @param auth_hrn human readable name of authority
232 return self.hierarchy.get_auth_info(auth_hrn)
235 def veriry_auth_belongs_to_me(self, name):
237 Verify that an authority belongs to our hierarchy.
238 This is basically left up to the implementation of the hierarchy
239 module. If the specified name does not belong, ane exception is
240 thrown indicating the caller should contact someone else.
242 @param auth_name human readable name of authority
245 # get auth info will throw an exception if the authority doesnt exist
246 self.get_auth_info(name)
249 def verify_object_belongs_to_me(self, name):
251 Verify that an object belongs to our hierarchy. By extension,
252 this implies that the authority that owns the object belongs
253 to our hierarchy. If it does not an exception is thrown.
255 @param name human readable name of object
257 auth_name = self.get_authority(name)
260 if name == self.config.SFA_INTERFACE_HRN:
262 self.verify_auth_belongs_to_me(auth_name)
264 def verify_auth_belongs_to_me(self, name):
265 # get auth info will throw an exception if the authority doesnt exist
266 self.get_auth_info(name)
269 def verify_object_permission(self, name):
271 Verify that the object gid that was specified in the credential
272 allows permission to the object 'name'. This is done by a simple
273 prefix test. For example, an object_gid for plc.arizona would
274 match the objects plc.arizona.slice1 and plc.arizona.
276 @param name human readable name to test
278 object_hrn = self.object_gid.get_hrn()
279 if object_hrn == name:
281 if name.startswith(object_hrn + "."):
283 #if name.startswith(get_authority(name)):
286 raise PermissionError(name)
288 def determine_user_rights(self, caller_hrn, reg_record):
290 Given a user credential and a record, determine what set of rights the
291 user should have to that record.
293 This is intended to replace determine_user_rights() and
294 verify_cancreate_credential()
298 type = reg_record.type
300 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
303 # researchers in the slice are in the DB as-is
304 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
305 # locating PIs attached to that slice
306 slice_pis=reg_record.get_pis()
307 pi_hrns = [ user.hrn for user in slice_pis ]
308 if (caller_hrn in researcher_hrns + pi_hrns):
315 elif type == 'authority':
316 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
317 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
321 if (caller_hrn in pi_hrns):
324 # NOTE: for the PL implementation, this 'operators' list
325 # amounted to users with 'tech' role in that site
326 # it seems like this is not needed any longer, so for now I just drop that
327 # operator_hrns = reg_record.get('operator',[])
328 # if (caller_hrn in operator_hrns):
329 # rl.add('authority')
342 def get_authority(self, hrn):
343 return get_authority(hrn)
345 def filter_creds_by_caller(self, creds, caller_hrn_list):
347 Returns a list of creds who's gid caller matches the
350 if not isinstance(creds, list):
353 if not isinstance(caller_hrn_list, list):
354 caller_hrn_list = [caller_hrn_list]
357 tmp_cred = Credential(string=cred)
358 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: