2 # SfaAPI authentication
6 from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \
7 BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied
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()
38 def checkCredentials(self, creds, operation, xrns=[]):
39 if not isinstance(xrns, list):
41 hrns = [Xrn(xrn).hrn for xrn in xrns]
43 if not isinstance(creds, list):
45 logger.debug("Auth.checkCredentials with %d creds on hrns=%s"%(len(creds),hrns))
46 # won't work if either creds or hrns is empty - let's make it more explicit
47 if not creds: raise InsufficientRights("Access denied - no credential provided")
48 if not hrns: hrns = [None]
52 self.check(cred, operation, hrn)
55 cred_obj=Credential(string=cred)
56 logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
57 error = sys.exc_info()[:2]
61 raise InsufficientRights('Access denied: %s -- %s' % (error[0],error[1]))
66 def check(self, cred_string, operation, hrn = None):
68 Check the credential against the peer cert (callerGID included
69 in the credential matches the caller that is connected to the
70 HTTPS connection, check if the credential was signed by a
71 trusted cert and check if the credential is allowed to perform
72 the specified operation.
74 cred = Credential(string = cred_string)
75 self.client_cred = cred
76 logger.debug("Auth.check: handling hrn=%s and credential=%s"%\
77 (hrn,cred.get_summary_tostring()))
78 self.client_gid = self.client_cred.get_gid_caller()
79 self.object_gid = self.client_cred.get_gid_object()
81 # make sure the client_gid is not blank
82 if not self.client_gid:
83 raise MissingCallerGID(self.client_cred.get_subject())
85 # validate the client cert if it exists
87 self.verifyPeerCert(self.peer_cert, self.client_gid)
89 # make sure the client is allowed to perform the operation
91 if not self.client_cred.can_perform(operation):
92 raise InsufficientRights(operation)
94 if self.trusted_cert_list:
95 self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
97 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
99 # Make sure the credential's target matches the specified hrn.
100 # This check does not apply to trusted peers
101 trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list]
102 if hrn and self.client_gid.get_hrn() not in trusted_peers:
103 target_hrn = self.object_gid.get_hrn()
104 if not hrn == target_hrn:
105 raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
109 def check_ticket(self, ticket):
111 Check if the tickt was signed by a trusted cert
113 if self.trusted_cert_list:
114 client_ticket = SfaTicket(string=ticket)
115 client_ticket.verify_chain(self.trusted_cert_list)
117 raise MissingTrustedRoots(self.config.get_trustedroots_dir())
121 def verifyPeerCert(self, cert, gid):
122 # make sure the client_gid matches client's certificate
123 if not cert.is_pubkey(gid.get_pubkey()):
124 raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
126 def verifyGidRequestHash(self, gid, hash, arglist):
127 key = gid.get_pubkey()
128 if not key.verify_string(str(arglist), hash):
129 raise BadRequestHash(hash)
131 def verifyCredRequestHash(self, cred, hash, arglist):
132 gid = cred.get_gid_caller()
133 self.verifyGidRequestHash(gid, hash, arglist)
135 def validateGid(self, gid):
136 if self.trusted_cert_list:
137 gid.verify_chain(self.trusted_cert_list)
139 def validateCred(self, cred):
140 if self.trusted_cert_list:
141 cred.verify(self.trusted_cert_file_list)
143 def authenticateGid(self, gidStr, argList, requestHash=None):
144 gid = GID(string = gidStr)
145 self.validateGid(gid)
146 # request_hash is optional
148 self.verifyGidRequestHash(gid, requestHash, argList)
151 def authenticateCred(self, credStr, argList, requestHash=None):
152 cred = Credential(string = credStr)
153 self.validateCred(cred)
154 # request hash is optional
156 self.verifyCredRequestHash(cred, requestHash, argList)
159 def authenticateCert(self, certStr, requestHash):
160 cert = Certificate(string=certStr)
161 # xxx should be validateCred ??
162 self.validateCred(cert)
164 def gidNoop(self, gidStr, value, requestHash):
165 self.authenticateGid(gidStr, [gidStr, value], requestHash)
168 def credNoop(self, credStr, value, requestHash):
169 self.authenticateCred(credStr, [credStr, value], requestHash)
172 def verify_cred_is_me(self, credential):
174 cred = Credential(string=credential)
175 caller_gid = cred.get_gid_caller()
176 caller_hrn = caller_gid.get_hrn()
177 if caller_hrn != self.config.SFA_INTERFACE_HRN:
178 raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
182 def get_auth_info(self, auth_hrn):
184 Given an authority name, return the information for that authority.
185 This is basically a stub that calls the hierarchy module.
187 @param auth_hrn human readable name of authority
190 return self.hierarchy.get_auth_info(auth_hrn)
193 def veriry_auth_belongs_to_me(self, name):
195 Verify that an authority belongs to our hierarchy.
196 This is basically left up to the implementation of the hierarchy
197 module. If the specified name does not belong, ane exception is
198 thrown indicating the caller should contact someone else.
200 @param auth_name human readable name of authority
203 # get auth info will throw an exception if the authority doesnt exist
204 self.get_auth_info(name)
207 def verify_object_belongs_to_me(self, name):
209 Verify that an object belongs to our hierarchy. By extension,
210 this implies that the authority that owns the object belongs
211 to our hierarchy. If it does not an exception is thrown.
213 @param name human readable name of object
215 auth_name = self.get_authority(name)
218 if name == self.config.SFA_INTERFACE_HRN:
220 self.verify_auth_belongs_to_me(auth_name)
222 def verify_auth_belongs_to_me(self, name):
223 # get auth info will throw an exception if the authority doesnt exist
224 self.get_auth_info(name)
227 def verify_object_permission(self, name):
229 Verify that the object gid that was specified in the credential
230 allows permission to the object 'name'. This is done by a simple
231 prefix test. For example, an object_gid for plc.arizona would
232 match the objects plc.arizona.slice1 and plc.arizona.
234 @param name human readable name to test
236 object_hrn = self.object_gid.get_hrn()
237 if object_hrn == name:
239 if name.startswith(object_hrn + "."):
241 #if name.startswith(get_authority(name)):
244 raise PermissionError(name)
246 def determine_user_rights(self, caller_hrn, reg_record):
248 Given a user credential and a record, determine what set of rights the
249 user should have to that record.
251 This is intended to replace determine_user_rights() and
252 verify_cancreate_credential()
256 type = reg_record.type
258 logger.debug("entering determine_user_rights with record %s and caller_hrn %s"%(reg_record, caller_hrn))
261 # researchers in the slice are in the DB as-is
262 researcher_hrns = [ user.hrn for user in reg_record.reg_researchers ]
263 # locating PIs attached to that slice
264 slice_pis=reg_record.get_pis()
265 pi_hrns = [ user.hrn for user in slice_pis ]
266 if (caller_hrn in researcher_hrns + pi_hrns):
273 elif type == 'authority':
274 pi_hrns = [ user.hrn for user in reg_record.reg_pis ]
275 if (caller_hrn == self.config.SFA_INTERFACE_HRN):
279 if (caller_hrn in pi_hrns):
282 # NOTE: for the PL implementation, this 'operators' list
283 # amounted to users with 'tech' role in that site
284 # it seems like this is not needed any longer, so for now I just drop that
285 # operator_hrns = reg_record.get('operator',[])
286 # if (caller_hrn in operator_hrns):
287 # rl.add('authority')
300 def get_authority(self, hrn):
301 return get_authority(hrn)
303 def filter_creds_by_caller(self, creds, caller_hrn_list):
305 Returns a list of creds who's gid caller matches the
308 if not isinstance(creds, list):
311 if not isinstance(caller_hrn_list, list):
312 caller_hrn_list = [caller_hrn_list]
315 tmp_cred = Credential(string=cred)
316 if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]: