2 # SfaAPI authentication
6 from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \
7 BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied, CredentialNotVerifiable, Forbidden
8 from sfa.util.sfalogging import logger
9 from sfa.util.config import Config
10 from sfa.util.xrn import Xrn, get_authority
12 from sfa.trust.gid import GID
13 from sfa.trust.rights import Rights
14 from sfa.trust.certificate import Keypair, Certificate
15 from sfa.trust.credential import Credential
16 from sfa.trust.trustedroots import TrustedRoots
17 from sfa.trust.hierarchy import Hierarchy
18 from sfa.trust.sfaticket import SfaTicket
23 Credential based authentication
26 def __init__(self, peer_cert = None, config = None ):
27 self.peer_cert = peer_cert
28 self.hierarchy = Hierarchy()
30 self.config = Config()
31 self.load_trusted_certs()
33 def load_trusted_certs(self):
34 self.trusted_cert_list = TrustedRoots(self.config.get_trustedroots_dir()).get_list()
35 self.trusted_cert_file_list = TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
37 def checkCredentials(self, creds, operation, xrns=[], check_sliver_callback=None):
38 if not isinstance(xrns, list):
41 slice_xrns = Xrn.filter_type(xrns, 'slice')
42 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
44 # we are not able to validate slivers in the traditional way so
45 # we make sure not to include sliver urns/hrns in the core validation loop
46 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
48 if not isinstance(creds, list):
50 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
51 # won't work if either creds or hrns is empty - let's make it more explicit
52 if not creds: raise Forbidden("no credential provided")
53 if not hrns: hrns = [None]
57 self.check(cred, operation, hrn)
60 cred_obj=Credential(cred=cred)
61 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
62 error = sys.exc_info()[:2]
65 # make sure all sliver xrns are validated against the valid credentials
67 if not check_sliver_callback:
68 msg = "sliver verification callback method not found."
69 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
71 check_sliver_callback(valid, sliver_xrns)
74 msg = "Valid credential not found for method: %s" % operation
76 msg += " target: %s" % xrns
82 def check(self, credential, operation, hrn = None):
84 Check the credential against the peer cert (callerGID included
85 in the credential matches the caller that is connected to the
86 HTTPS connection, check if the credential was signed by a
87 trusted cert and check if the credential is allowed to perform
88 the specified operation.
90 cred = Credential(cred=credential)
91 self.client_cred = cred
92 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
93 (hrn,cred.get_summary_tostring()))
95 if cred.type not in ['geni_sfa']:
96 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
97 self.client_gid = self.client_cred.get_gid_caller()
98 self.object_gid = self.client_cred.get_gid_object()
100 # make sure the client_gid is not blank
101 if not self.client_gid:
102 raise MissingCallerGID(self.client_cred.get_subject())
104 # validate the client cert if it exists
106 self.verifyPeerCert(self.peer_cert, self.client_gid)
108 # make sure the client is allowed to perform the operation
110 if not self.client_cred.can_perform(operation):
111 raise InsufficientRights(operation)
113 if self.trusted_cert_list:
114 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
116 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
118 # Make sure the credential's target matches the specified hrn.
119 # This check does not apply to trusted peers
120 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
121 if hrn and self.client_gid.get_hrn() not in trusted_peers:
122 target_hrn = self.object_gid.get_hrn()
123 if not hrn == target_hrn:
124 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
128 def check_ticket(self, ticket):
130 Check if the tickt was signed by a trusted cert
132 if self.trusted_cert_list:
133 client_ticket = SfaTicket(string=ticket)
134 client_ticket.verify_chain(self.trusted_cert_list)
136 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
140 def verifyPeerCert(self, cert, gid):
141 # make sure the client_gid matches client's certificate
142 if not cert.is_pubkey(gid.get_pubkey()):
143 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
145 def verifyGidRequestHash(self, gid, hash, arglist):
146 key = gid.get_pubkey()
147 if not key.verify_string(str(arglist), hash):
148 raise BadRequestHash(hash)
150 def verifyCredRequestHash(self, cred, hash, arglist):
151 gid = cred.get_gid_caller()
152 self.verifyGidRequestHash(gid, hash, arglist)
154 def validateGid(self, gid):
155 if self.trusted_cert_list:
156 gid.verify_chain(self.trusted_cert_list)
158 def validateCred(self, cred):
159 if self.trusted_cert_list:
160 cred.verify(self.trusted_cert_file_list)
162 def authenticateGid(self, gidStr, argList, requestHash=None):
163 gid = GID(string = gidStr)
164 self.validateGid(gid)
165 # request_hash is optional
167 self.verifyGidRequestHash(gid, requestHash, argList)
170 def authenticateCred(self, credStr, argList, requestHash=None):
171 cred = Credential(string = credStr)
172 self.validateCred(cred)
173 # request hash is optional
175 self.verifyCredRequestHash(cred, requestHash, argList)
178 def authenticateCert(self, certStr, requestHash):
179 cert = Certificate(string=certStr)
180 # xxx should be validateCred ??
181 self.validateCred(cert)
183 def gidNoop(self, gidStr, value, requestHash):
184 self.authenticateGid(gidStr, [gidStr, value], requestHash)
187 def credNoop(self, credStr, value, requestHash):
188 self.authenticateCred(credStr, [credStr, value], requestHash)
191 def verify_cred_is_me(self, credential):
193 cred = Credential(string=credential)
194 caller_gid = cred.get_gid_caller()
195 caller_hrn = caller_gid.get_hrn()
196 if caller_hrn != self.config.SFA_INTERFACE_HRN:
197 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
201 def get_auth_info(self, auth_hrn):
203 Given an authority name, return the information for that authority.
204 This is basically a stub that calls the hierarchy module.
206 @param auth_hrn human readable name of authority
209 return self.hierarchy.get_auth_info(auth_hrn)
212 def veriry_auth_belongs_to_me(self, name):
214 Verify that an authority belongs to our hierarchy.
215 This is basically left up to the implementation of the hierarchy
216 module. If the specified name does not belong, ane exception is
217 thrown indicating the caller should contact someone else.
219 @param auth_name human readable name of authority
222 # get auth info will throw an exception if the authority doesnt exist
223 self.get_auth_info(name)
226 def verify_object_belongs_to_me(self, name):
228 Verify that an object belongs to our hierarchy. By extension,
229 this implies that the authority that owns the object belongs
230 to our hierarchy. If it does not an exception is thrown.
232 @param name human readable name of object
234 auth_name = self.get_authority(name)
237 if name == self.config.SFA_INTERFACE_HRN:
239 self.verify_auth_belongs_to_me(auth_name)
241 def verify_auth_belongs_to_me(self, name):
242 # get auth info will throw an exception if the authority doesnt exist
243 self.get_auth_info(name)
246 def verify_object_permission(self, name):
248 Verify that the object gid that was specified in the credential
249 allows permission to the object 'name'. This is done by a simple
250 prefix test. For example, an object_gid for plc.arizona would
251 match the objects plc.arizona.slice1 and plc.arizona.
253 @param name human readable name to test
255 object_hrn = self.object_gid.get_hrn()
256 if object_hrn == name:
258 if name.startswith(object_hrn + "."):
260 #if name.startswith(get_authority(name)):
263 raise PermissionError(name)
265 def determine_user_rights(self, caller_hrn, reg_record):
267 Given a user credential and a record, determine what set of rights the
268 user should have to that record.
270 This is intended to replace determine_user_rights() and
271 verify_cancreate_credential()
275 type = reg_record.type
277 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
280 # researchers in the slice are in the DB as-is
281 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
282 # locating PIs attached to that slice
283 slice_pis=reg_record.get_pis()
284 pi_hrns = [ user.hrn for user in slice_pis ]
285 if (caller_hrn in researcher_hrns + pi_hrns):
292 elif type == 'authority':
293 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
294 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
298 if (caller_hrn in pi_hrns):
301 # NOTE: for the PL implementation, this 'operators' list
302 # amounted to users with 'tech' role in that site
303 # it seems like this is not needed any longer, so for now I just drop that
304 # operator_hrns = reg_record.get('operator',[])
305 # if (caller_hrn in operator_hrns):
306 # rl.add('authority')
319 def get_authority(self, hrn):
320 return get_authority(hrn)
322 def filter_creds_by_caller(self, creds, caller_hrn_list):
324 Returns a list of creds who's gid caller matches the
327 if not isinstance(creds, list):
330 if not isinstance(caller_hrn_list, list):
331 caller_hrn_list = [caller_hrn_list]
334 tmp_cred = Credential(string=cred)
335 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: