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):
39 # if xrns are specified they cannot be None or empty string
43 raise BadArgs("Invalid urn or hrn")
46 if not isinstance(xrns, list):
49 slice_xrns = Xrn.filter_type(xrns, 'slice')
50 sliver_xrns = Xrn.filter_type(xrns, 'sliver')
52 # we are not able to validate slivers in the traditional way so
53 # we make sure not to include sliver urns/hrns in the core validation loop
54 hrns = [Xrn(xrn).hrn for xrn in xrns if xrn not in sliver_xrns]
56 if not isinstance(creds, list):
58 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
59 # won't work if either creds or hrns is empty - let's make it more explicit
60 if not creds: raise Forbidden("no credential provided")
61 if not hrns: hrns = [None]
65 self.check(cred, operation, hrn)
68 cred_obj=Credential(cred=cred)
69 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
70 error = sys.exc_info()[:2]
73 # make sure all sliver xrns are validated against the valid credentials
75 if not check_sliver_callback:
76 msg = "sliver verification callback method not found."
77 msg += " Unable to validate sliver xrns: %s" % sliver_xrns
79 check_sliver_callback(valid, sliver_xrns)
82 raise Forbidden("Invalid credential")
87 def check(self, credential, operation, hrn = None):
89 Check the credential against the peer cert (callerGID included
90 in the credential matches the caller that is connected to the
91 HTTPS connection, check if the credential was signed by a
92 trusted cert and check if the credential is allowed to perform
93 the specified operation.
95 cred = Credential(cred=credential)
96 self.client_cred = cred
97 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
98 (hrn,cred.get_summary_tostring()))
100 if cred.type not in ['geni_sfa']:
101 raise CredentialNotVerifiable(cred.type, "%s not supported" % cred.type)
102 self.client_gid = self.client_cred.get_gid_caller()
103 self.object_gid = self.client_cred.get_gid_object()
105 # make sure the client_gid is not blank
106 if not self.client_gid:
107 raise MissingCallerGID(self.client_cred.get_subject())
109 # validate the client cert if it exists
111 self.verifyPeerCert(self.peer_cert, self.client_gid)
113 # make sure the client is allowed to perform the operation
115 if not self.client_cred.can_perform(operation):
116 raise InsufficientRights(operation)
118 if self.trusted_cert_list:
119 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
121 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
123 # Make sure the credential's target matches the specified hrn.
124 # This check does not apply to trusted peers
125 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
126 if hrn and self.client_gid.get_hrn() not in trusted_peers:
127 target_hrn = self.object_gid.get_hrn()
128 if not hrn == target_hrn:
129 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
133 def check_ticket(self, ticket):
135 Check if the tickt was signed by a trusted cert
137 if self.trusted_cert_list:
138 client_ticket = SfaTicket(string=ticket)
139 client_ticket.verify_chain(self.trusted_cert_list)
141 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
145 def verifyPeerCert(self, cert, gid):
146 # make sure the client_gid matches client's certificate
147 if not cert.is_pubkey(gid.get_pubkey()):
148 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
150 def verifyGidRequestHash(self, gid, hash, arglist):
151 key = gid.get_pubkey()
152 if not key.verify_string(str(arglist), hash):
153 raise BadRequestHash(hash)
155 def verifyCredRequestHash(self, cred, hash, arglist):
156 gid = cred.get_gid_caller()
157 self.verifyGidRequestHash(gid, hash, arglist)
159 def validateGid(self, gid):
160 if self.trusted_cert_list:
161 gid.verify_chain(self.trusted_cert_list)
163 def validateCred(self, cred):
164 if self.trusted_cert_list:
165 cred.verify(self.trusted_cert_file_list)
167 def authenticateGid(self, gidStr, argList, requestHash=None):
168 gid = GID(string = gidStr)
169 self.validateGid(gid)
170 # request_hash is optional
172 self.verifyGidRequestHash(gid, requestHash, argList)
175 def authenticateCred(self, credStr, argList, requestHash=None):
176 cred = Credential(string = credStr)
177 self.validateCred(cred)
178 # request hash is optional
180 self.verifyCredRequestHash(cred, requestHash, argList)
183 def authenticateCert(self, certStr, requestHash):
184 cert = Certificate(string=certStr)
185 # xxx should be validateCred ??
186 self.validateCred(cert)
188 def gidNoop(self, gidStr, value, requestHash):
189 self.authenticateGid(gidStr, [gidStr, value], requestHash)
192 def credNoop(self, credStr, value, requestHash):
193 self.authenticateCred(credStr, [credStr, value], requestHash)
196 def verify_cred_is_me(self, credential):
198 cred = Credential(string=credential)
199 caller_gid = cred.get_gid_caller()
200 caller_hrn = caller_gid.get_hrn()
201 if caller_hrn != self.config.SFA_INTERFACE_HRN:
202 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
206 def get_auth_info(self, auth_hrn):
208 Given an authority name, return the information for that authority.
209 This is basically a stub that calls the hierarchy module.
211 @param auth_hrn human readable name of authority
214 return self.hierarchy.get_auth_info(auth_hrn)
217 def veriry_auth_belongs_to_me(self, name):
219 Verify that an authority belongs to our hierarchy.
220 This is basically left up to the implementation of the hierarchy
221 module. If the specified name does not belong, ane exception is
222 thrown indicating the caller should contact someone else.
224 @param auth_name human readable name of authority
227 # get auth info will throw an exception if the authority doesnt exist
228 self.get_auth_info(name)
231 def verify_object_belongs_to_me(self, name):
233 Verify that an object belongs to our hierarchy. By extension,
234 this implies that the authority that owns the object belongs
235 to our hierarchy. If it does not an exception is thrown.
237 @param name human readable name of object
239 auth_name = self.get_authority(name)
242 if name == self.config.SFA_INTERFACE_HRN:
244 self.verify_auth_belongs_to_me(auth_name)
246 def verify_auth_belongs_to_me(self, name):
247 # get auth info will throw an exception if the authority doesnt exist
248 self.get_auth_info(name)
251 def verify_object_permission(self, name):
253 Verify that the object gid that was specified in the credential
254 allows permission to the object 'name'. This is done by a simple
255 prefix test. For example, an object_gid for plc.arizona would
256 match the objects plc.arizona.slice1 and plc.arizona.
258 @param name human readable name to test
260 object_hrn = self.object_gid.get_hrn()
261 if object_hrn == name:
263 if name.startswith(object_hrn + "."):
265 #if name.startswith(get_authority(name)):
268 raise PermissionError(name)
270 def determine_user_rights(self, caller_hrn, reg_record):
272 Given a user credential and a record, determine what set of rights the
273 user should have to that record.
275 This is intended to replace determine_user_rights() and
276 verify_cancreate_credential()
280 type = reg_record.type
282 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
285 # researchers in the slice are in the DB as-is
286 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
287 # locating PIs attached to that slice
288 slice_pis=reg_record.get_pis()
289 pi_hrns = [ user.hrn for user in slice_pis ]
290 if (caller_hrn in researcher_hrns + pi_hrns):
297 elif type == 'authority':
298 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
299 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
303 if (caller_hrn in pi_hrns):
306 # NOTE: for the PL implementation, this 'operators' list
307 # amounted to users with 'tech' role in that site
308 # it seems like this is not needed any longer, so for now I just drop that
309 # operator_hrns = reg_record.get('operator',[])
310 # if (caller_hrn in operator_hrns):
311 # rl.add('authority')
324 def get_authority(self, hrn):
325 return get_authority(hrn)
327 def filter_creds_by_caller(self, creds, caller_hrn_list):
329 Returns a list of creds who's gid caller matches the
332 if not isinstance(creds, list):
335 if not isinstance(caller_hrn_list, list):
336 caller_hrn_list = [caller_hrn_list]
339 tmp_cred = Credential(string=cred)
340 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: