Changed logging setup
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
|
||||
import re
|
||||
import sys
|
||||
@@ -28,6 +29,7 @@ from saml2.s_utils import sid, MissingValue
|
||||
from saml2.s_utils import factory
|
||||
from saml2.s_utils import assertion_factory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _filter_values(vals, vlist=None, must=False):
|
||||
""" Removes values from *vals* that does not appear in vlist
|
||||
|
||||
@@ -21,6 +21,11 @@ to do attribute aggregation.
|
||||
"""
|
||||
import saml2
|
||||
|
||||
import logging
|
||||
from saml2.client import Saml2Client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_BINDING = saml2.BINDING_SOAP
|
||||
|
||||
class AttributeResolver(object):
|
||||
@@ -32,7 +37,7 @@ class AttributeResolver(object):
|
||||
self.saml2client = saml2client
|
||||
self.metadata = saml2client.config.metadata
|
||||
else:
|
||||
self.saml2client = saml2.client.Saml2Client(config)
|
||||
self.saml2client = Saml2Client(config)
|
||||
|
||||
def extend(self, subject_id, issuer, vo_members, name_id_format=None,
|
||||
sp_name_qualifier=None, log=None, real_id=None):
|
||||
@@ -42,7 +47,7 @@ class AttributeResolver(object):
|
||||
:param issuer: Who am I the poses the query
|
||||
:param vo_members: The entity IDs of the IdP who I'm going to ask
|
||||
for extra attributes
|
||||
:param nameid_format: Used to make the IdPs aware of what's going
|
||||
:param name_id_format: Used to make the IdPs aware of what's going
|
||||
on here
|
||||
:param log: Where to log exciting information
|
||||
:return: A dictionary with all the collected information about the
|
||||
|
||||
@@ -28,6 +28,9 @@ import base64
|
||||
import urllib
|
||||
from saml2.s_utils import deflate_and_base64_encode
|
||||
from saml2.soap import SOAPClient, HTTPClient
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from xml.etree import cElementTree as ElementTree
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
import shelve
|
||||
from saml2 import time_util
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# The assumption is that any subject may consist of data
|
||||
# gathered from several different sources, all with their own
|
||||
|
||||
@@ -58,6 +58,9 @@ from saml2 import BINDING_HTTP_REDIRECT
|
||||
from saml2 import BINDING_SOAP
|
||||
from saml2 import BINDING_HTTP_POST
|
||||
from saml2 import BINDING_PAOS
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SSO_BINDING = saml2.BINDING_HTTP_REDIRECT
|
||||
|
||||
@@ -82,9 +85,8 @@ class LogoutError(Exception):
|
||||
class Saml2Client(object):
|
||||
""" The basic pySAML2 service provider class """
|
||||
|
||||
def __init__(self, config=None,
|
||||
identity_cache=None, state_cache=None,
|
||||
virtual_organization=None, config_file="", logger=None):
|
||||
def __init__(self, config=None, identity_cache=None, state_cache=None,
|
||||
virtual_organization=None, config_file=""):
|
||||
"""
|
||||
:param config: A saml2.config.Config instance
|
||||
:param identity_cache: Where the class should store identity information
|
||||
@@ -110,18 +112,12 @@ class Saml2Client(object):
|
||||
|
||||
self.metadata = self.config.metadata
|
||||
|
||||
if logger is None:
|
||||
self.logger = self.config.setup_logger()
|
||||
else:
|
||||
self.logger = logger
|
||||
|
||||
# we copy the config.debug variable in an internal
|
||||
# field for convenience and because we may need to
|
||||
# change it during the tests
|
||||
self.debug = self.config.debug
|
||||
|
||||
self.sec = security_context(self.config, log=self.logger,
|
||||
debug=self.debug)
|
||||
self.sec = security_context(self.config)
|
||||
|
||||
if virtual_organization:
|
||||
self.vorg = VirtualOrg(self, virtual_organization)
|
||||
@@ -173,8 +169,7 @@ class Saml2Client(object):
|
||||
try:
|
||||
return self.config.single_sign_on_services(entityid, binding)[0]
|
||||
except IndexError:
|
||||
if self.logger:
|
||||
self.logger.info("_sso_location: %s, %s" % (entityid,
|
||||
logger.info("_sso_location: %s, %s" % (entityid,
|
||||
binding))
|
||||
raise IdpUnspecified("No IdP to send to given the premises")
|
||||
|
||||
@@ -205,15 +200,13 @@ class Saml2Client(object):
|
||||
else:
|
||||
return None
|
||||
|
||||
def response(self, post, outstanding, log=None, decode=True,
|
||||
asynchop=True):
|
||||
def response(self, post, outstanding, decode=True, asynchop=True):
|
||||
""" Deal with an AuthnResponse or LogoutResponse
|
||||
|
||||
:param post: The reply as a dictionary
|
||||
:param outstanding: A dictionary with session IDs as keys and
|
||||
the original web request from the user before redirection
|
||||
as values.
|
||||
:param log: where loggin should go.
|
||||
:param decode: Whether the response is Base64 encoded or not
|
||||
:param asynchop: Whether the response was return over a asynchronous
|
||||
connection. SOAP for instance is synchronous
|
||||
@@ -230,39 +223,33 @@ class Saml2Client(object):
|
||||
except KeyError:
|
||||
raise Exception("Missing entity_id specification")
|
||||
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
reply_addr = self.service_url()
|
||||
|
||||
resp = None
|
||||
if saml_response:
|
||||
try:
|
||||
resp = response_factory(saml_response, self.config,
|
||||
reply_addr, outstanding, log,
|
||||
debug=self.debug, decode=decode,
|
||||
reply_addr, outstanding, decode=decode,
|
||||
asynchop=asynchop,
|
||||
allow_unsolicited=self.allow_unsolicited)
|
||||
except Exception, exc:
|
||||
if log:
|
||||
log.error("%s" % exc)
|
||||
logger.error("%s" % exc)
|
||||
return None
|
||||
if log:
|
||||
log.debug(">> %s", resp)
|
||||
logger.debug(">> %s", resp)
|
||||
|
||||
resp = resp.verify()
|
||||
if isinstance(resp, AuthnResponse):
|
||||
self.users.add_information_about_person(resp.session_info())
|
||||
if log:
|
||||
log.info("--- ADDED person info ----")
|
||||
logger.info("--- ADDED person info ----")
|
||||
elif isinstance(resp, LogoutResponse):
|
||||
self.handle_logout_response(resp, log)
|
||||
elif log:
|
||||
log.error("Response type not supported: %s" % saml2.class_name(resp))
|
||||
self.handle_logout_response(resp)
|
||||
else:
|
||||
logger.error("Response type not supported: %s" % (
|
||||
saml2.class_name(resp),))
|
||||
return resp
|
||||
|
||||
def authn_request(self, query_id, destination, service_url, spentityid,
|
||||
my_name="", vorg="", scoping=None, log=None, sign=None,
|
||||
my_name="", vorg="", scoping=None, sign=None,
|
||||
binding=saml2.BINDING_HTTP_POST,
|
||||
nameid_format=saml.NAMEID_FORMAT_TRANSIENT):
|
||||
""" Creates an authentication request.
|
||||
@@ -274,7 +261,6 @@ class Saml2Client(object):
|
||||
:param my_name: The name of this service.
|
||||
:param vorg: The vitual organization the service belongs to.
|
||||
:param scoping: The scope of the request
|
||||
:param log: A service to which logs should be written
|
||||
:param sign: Whether the request should be signed or not.
|
||||
:param binding: The protocol to use for the Response !!
|
||||
:return: <samlp:AuthnRequest> instance
|
||||
@@ -321,11 +307,7 @@ class Saml2Client(object):
|
||||
request.name_id_policy = name_id_policy
|
||||
request.issuer = self._issuer(spentityid)
|
||||
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if log:
|
||||
log.info("REQUEST: %s" % request)
|
||||
logger.info("REQUEST: %s" % request)
|
||||
|
||||
return signed_instance_factory(request, self.sec, to_sign)
|
||||
|
||||
@@ -357,16 +339,11 @@ class Saml2Client(object):
|
||||
my_name = self._my_name()
|
||||
|
||||
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if log:
|
||||
log.info("spentityid: %s" % spentityid)
|
||||
log.info("service_url: %s" % service_url)
|
||||
log.info("my_name: %s" % my_name)
|
||||
logger.info("spentityid: %s\nservice_url: %s\nmy_name: %s" % (
|
||||
spentityid, service_url, my_name))
|
||||
|
||||
return self.authn_request(session_id, location, service_url,
|
||||
spentityid, my_name, vorg, scoping, log,
|
||||
spentityid, my_name, vorg, scoping,
|
||||
sign, binding=binding)
|
||||
|
||||
def authenticate(self, entityid=None, relay_state="",
|
||||
@@ -471,7 +448,7 @@ class Saml2Client(object):
|
||||
|
||||
def attribute_query(self, subject_id, destination, issuer_id=None,
|
||||
attribute=None, sp_name_qualifier=None, name_qualifier=None,
|
||||
nameid_format=None, log=None, real_id=None):
|
||||
nameid_format=None, real_id=None):
|
||||
""" Does a attribute request to an attribute authority, this is
|
||||
by default done over SOAP. Other bindings could be used but not
|
||||
supported right now.
|
||||
@@ -486,60 +463,49 @@ class Saml2Client(object):
|
||||
:param name_qualifier: The unique identifier of the identity
|
||||
provider that generated the identifier.
|
||||
:param nameid_format: The format of the name ID
|
||||
:param log: Function to use for logging
|
||||
:param real_id: The identifier which is the key to this entity in the
|
||||
identity database
|
||||
:return: The attributes returned
|
||||
"""
|
||||
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
session_id = sid()
|
||||
issuer = self._issuer(issuer_id)
|
||||
|
||||
request = self.create_attribute_query(session_id, subject_id,
|
||||
destination, issuer, attribute, sp_name_qualifier,
|
||||
name_qualifier, nameid_format=nameid_format)
|
||||
|
||||
if log:
|
||||
log.info("Request, created: %s" % request)
|
||||
|
||||
logger.info("Request, created: %s" % request)
|
||||
|
||||
soapclient = SOAPClient(destination, self.config.key_file,
|
||||
self.config.cert_file,
|
||||
ca_certs=self.config.ca_certs)
|
||||
if log:
|
||||
log.info("SOAP client initiated")
|
||||
logger.info("SOAP client initiated")
|
||||
|
||||
try:
|
||||
response = soapclient.send(request)
|
||||
except Exception, exc:
|
||||
if log:
|
||||
log.info("SoapClient exception: %s" % (exc,))
|
||||
logger.info("SoapClient exception: %s" % (exc,))
|
||||
return None
|
||||
|
||||
if log:
|
||||
log.info("SOAP request sent and got response: %s" % response)
|
||||
logger.info("SOAP request sent and got response: %s" % response)
|
||||
# fil = open("response.xml", "w")
|
||||
# fil.write(response)
|
||||
# fil.close()
|
||||
|
||||
if response:
|
||||
if log:
|
||||
log.info("Verifying response")
|
||||
logger.info("Verifying response")
|
||||
|
||||
try:
|
||||
# synchronous operation
|
||||
aresp = attribute_response(self.config, issuer, log=log)
|
||||
aresp = attribute_response(self.config, issuer)
|
||||
except Exception, exc:
|
||||
if log:
|
||||
log.error("%s", (exc,))
|
||||
logger.error("%s", (exc,))
|
||||
return None
|
||||
|
||||
_resp = aresp.loads(response, False, soapclient.response).verify()
|
||||
if _resp is None:
|
||||
if log:
|
||||
log.error("Didn't like the response")
|
||||
logger.error("Didn't like the response")
|
||||
return None
|
||||
|
||||
session_info = _resp.session_info()
|
||||
@@ -548,13 +514,11 @@ class Saml2Client(object):
|
||||
if real_id is not None:
|
||||
session_info["name_id"] = real_id
|
||||
self.users.add_information_about_person(session_info)
|
||||
|
||||
if log:
|
||||
log.info("session: %s" % session_info)
|
||||
|
||||
logger.info("session: %s" % session_info)
|
||||
return session_info
|
||||
else:
|
||||
if log:
|
||||
log.info("No response")
|
||||
logger.info("No response")
|
||||
return None
|
||||
|
||||
def construct_logout_request(self, subject_id, destination,
|
||||
@@ -595,8 +559,8 @@ class Saml2Client(object):
|
||||
|
||||
return request
|
||||
|
||||
def global_logout(self, subject_id, reason="", expire=None,
|
||||
sign=None, log=None, return_to="/"):
|
||||
def global_logout(self, subject_id, reason="", expire=None, sign=None,
|
||||
return_to="/"):
|
||||
""" More or less a layer of indirection :-/
|
||||
Bootstrapping the whole thing by finding all the IdPs that should
|
||||
be notified.
|
||||
@@ -607,7 +571,6 @@ class Saml2Client(object):
|
||||
:param expire: The latest the log out should happen.
|
||||
:param sign: Whether the request should be signed or not.
|
||||
This also depends on what binding is used.
|
||||
:param log: A logging function
|
||||
:param return_to: Where to send the user after she has been
|
||||
logged out.
|
||||
:return: Depends on which binding is used:
|
||||
@@ -616,17 +579,13 @@ class Saml2Client(object):
|
||||
conversation.
|
||||
"""
|
||||
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if log:
|
||||
log.info("logout request for: %s" % subject_id)
|
||||
logger.info("logout request for: %s" % subject_id)
|
||||
|
||||
# find out which IdPs/AAs I should notify
|
||||
entity_ids = self.users.issuers_of_info(subject_id)
|
||||
|
||||
return self._logout(subject_id, entity_ids, reason, expire,
|
||||
sign, log, return_to)
|
||||
sign, return_to)
|
||||
|
||||
def _logout(self, subject_id, entity_ids, reason, expire,
|
||||
sign=None, log=None, return_to="/"):
|
||||
@@ -640,8 +599,6 @@ class Saml2Client(object):
|
||||
# for all where I can use the SOAP binding, do those first
|
||||
not_done = entity_ids[:]
|
||||
response = False
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
for entity_id in entity_ids:
|
||||
response = False
|
||||
@@ -654,9 +611,8 @@ class Saml2Client(object):
|
||||
continue
|
||||
|
||||
destination = destinations[0]
|
||||
|
||||
if log:
|
||||
log.info("destination to provider: %s" % destination)
|
||||
|
||||
logger.info("destination to provider: %s" % destination)
|
||||
request = self.construct_logout_request(subject_id, destination,
|
||||
entity_id, reason, expire)
|
||||
|
||||
@@ -670,9 +626,8 @@ class Saml2Client(object):
|
||||
request.signature = pre_signature_part(request.id,
|
||||
self.sec.my_cert, 1)
|
||||
to_sign = [(class_name(request), request.id)]
|
||||
|
||||
if log:
|
||||
log.info("REQUEST: %s" % request)
|
||||
|
||||
logger.info("REQUEST: %s" % request)
|
||||
|
||||
request = signed_instance_factory(request, self.sec, to_sign)
|
||||
|
||||
@@ -683,17 +638,14 @@ class Saml2Client(object):
|
||||
log=log,
|
||||
ca_certs=self.config.ca_certs)
|
||||
if response:
|
||||
if log:
|
||||
log.info("Verifying response")
|
||||
logger.info("Verifying response")
|
||||
response = self.logout_response(response, log)
|
||||
|
||||
if response:
|
||||
not_done.remove(entity_id)
|
||||
if log:
|
||||
log.info("OK response from %s" % destination)
|
||||
logger.info("OK response from %s" % destination)
|
||||
else:
|
||||
if log:
|
||||
log.info(
|
||||
logger.info(
|
||||
"NOT OK response from %s" % destination)
|
||||
|
||||
else:
|
||||
@@ -737,25 +689,19 @@ class Saml2Client(object):
|
||||
self.users.remove_person(subject_id)
|
||||
return True
|
||||
|
||||
def handle_logout_response(self, response, log):
|
||||
def handle_logout_response(self, response):
|
||||
""" handles a Logout response
|
||||
|
||||
:param response: A response.Response instance
|
||||
:param log: A logging function
|
||||
:return: 4-tuple of (session_id of the last sent logout request,
|
||||
response message, response headers and message)
|
||||
"""
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if log:
|
||||
log.info("state: %s" % (self.state,))
|
||||
logger.info("state: %s" % (self.state,))
|
||||
status = self.state[response.in_response_to]
|
||||
if log:
|
||||
log.info("status: %s" % (status,))
|
||||
logger.info("status: %s" % (status,))
|
||||
issuer = response.issuer()
|
||||
if log:
|
||||
log.info("issuer: %s" % issuer)
|
||||
logger.info("issuer: %s" % issuer)
|
||||
del self.state[response.in_response_to]
|
||||
if status["entity_ids"] == [issuer]: # done
|
||||
self.local_logout(status["subject_id"])
|
||||
@@ -766,14 +712,12 @@ class Saml2Client(object):
|
||||
status["entity_ids"],
|
||||
status["reason"],
|
||||
status["not_on_or_after"],
|
||||
status["sign"],
|
||||
log, )
|
||||
status["sign"])
|
||||
|
||||
def logout_response(self, xmlstr, log=None, binding=BINDING_SOAP):
|
||||
def logout_response(self, xmlstr, binding=BINDING_SOAP):
|
||||
""" Deal with a LogoutResponse
|
||||
|
||||
:param xmlstr: The response as a xml string
|
||||
:param log: logging function
|
||||
:param binding: What type of binding this message came through.
|
||||
:return: None if the reply doesn't contain a valid SAML LogoutResponse,
|
||||
otherwise the reponse if the logout was successful and None if it
|
||||
@@ -781,8 +725,6 @@ class Saml2Client(object):
|
||||
"""
|
||||
|
||||
response = None
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if xmlstr:
|
||||
try:
|
||||
@@ -790,16 +732,13 @@ class Saml2Client(object):
|
||||
return_addr = self.config.endpoint("single_logout_service",
|
||||
binding=binding)[0]
|
||||
except Exception:
|
||||
if log:
|
||||
log.info("Not supposed to handle this!")
|
||||
logger.info("Not supposed to handle this!")
|
||||
return None
|
||||
|
||||
try:
|
||||
response = LogoutResponse(self.sec, return_addr,
|
||||
debug=self.debug, log=log)
|
||||
response = LogoutResponse(self.sec, return_addr)
|
||||
except Exception, exc:
|
||||
if log:
|
||||
log.info("%s" % exc)
|
||||
logger.info("%s" % exc)
|
||||
return None
|
||||
|
||||
if binding == BINDING_HTTP_REDIRECT:
|
||||
@@ -807,8 +746,7 @@ class Saml2Client(object):
|
||||
elif binding == BINDING_HTTP_POST:
|
||||
xmlstr = base64.b64decode(xmlstr)
|
||||
|
||||
if log:
|
||||
log.debug("XMLSTR: %s" % xmlstr)
|
||||
logger.debug("XMLSTR: %s" % xmlstr)
|
||||
|
||||
response = response.loads(xmlstr, False)
|
||||
|
||||
@@ -817,11 +755,10 @@ class Saml2Client(object):
|
||||
|
||||
if not response:
|
||||
return None
|
||||
|
||||
if log:
|
||||
log.debug(response)
|
||||
|
||||
return self.handle_logout_response(response, log)
|
||||
logger.debug(response)
|
||||
|
||||
return self.handle_logout_response(response)
|
||||
|
||||
return response
|
||||
|
||||
@@ -836,8 +773,6 @@ class Saml2Client(object):
|
||||
"""
|
||||
headers = []
|
||||
success = False
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
try:
|
||||
saml_request = get['SAMLRequest']
|
||||
@@ -848,8 +783,7 @@ class Saml2Client(object):
|
||||
xml = decode_base64_and_inflate(saml_request)
|
||||
|
||||
request = samlp.logout_request_from_string(xml)
|
||||
if log:
|
||||
log.debug(request)
|
||||
logger.debug(request)
|
||||
|
||||
if request.name_id.text == subject_id:
|
||||
status = samlp.STATUS_SUCCESS
|
||||
@@ -862,8 +796,7 @@ class Saml2Client(object):
|
||||
request.id,
|
||||
status)
|
||||
|
||||
if log:
|
||||
log.info("RESPONSE: {0:>s}".format(response))
|
||||
logger.info("RESPONSE: {0:>s}".format(response))
|
||||
|
||||
if 'RelayState' in get:
|
||||
rstate = get['RelayState']
|
||||
@@ -876,7 +809,7 @@ class Saml2Client(object):
|
||||
|
||||
return headers, success
|
||||
|
||||
def logout_request(self, request, subject_id, log=None,
|
||||
def logout_request(self, request, subject_id,
|
||||
binding=BINDING_HTTP_REDIRECT):
|
||||
""" Deal with a LogoutRequest
|
||||
|
||||
@@ -885,11 +818,9 @@ class Saml2Client(object):
|
||||
:param subject_id: the id of the current logged user
|
||||
:return: What is returned also depends on which binding is used.
|
||||
"""
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if binding == BINDING_HTTP_REDIRECT:
|
||||
return self.http_redirect_logout_request(request, subject_id, log)
|
||||
return self.http_redirect_logout_request(request, subject_id)
|
||||
|
||||
def make_logout_response(self, idp_entity_id, request_id,
|
||||
status_code, binding=BINDING_HTTP_REDIRECT):
|
||||
@@ -951,7 +882,7 @@ class Saml2Client(object):
|
||||
action=None,
|
||||
resource=None, subject=None,
|
||||
binding=saml2.BINDING_HTTP_REDIRECT,
|
||||
log=None, sign=False):
|
||||
sign=False):
|
||||
""" Makes an authz decision query.
|
||||
|
||||
:param entityid: The entity ID of the IdP to send the request to
|
||||
@@ -960,7 +891,6 @@ class Saml2Client(object):
|
||||
:param resource:
|
||||
:param subject:
|
||||
:param binding: Which binding to use for sending the request
|
||||
:param log: Where to write log messages
|
||||
:param sign: Whether the request should be signed or not.
|
||||
:return: AuthzDecisionQuery instance
|
||||
"""
|
||||
@@ -977,13 +907,12 @@ class Saml2Client(object):
|
||||
_action,
|
||||
saml.Evidence(assertion=assertion),
|
||||
resource, subject,
|
||||
binding, log, sign)
|
||||
binding, sign)
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def authz_decision_query(self, entityid, action,
|
||||
evidence=None, resource=None, subject=None,
|
||||
binding=saml2.BINDING_HTTP_REDIRECT,
|
||||
log=None, sign=None):
|
||||
binding=saml2.BINDING_HTTP_REDIRECT, sign=None):
|
||||
""" Creates an authz decision query.
|
||||
|
||||
:param entityid: The entity ID of the IdP to send the request to
|
||||
@@ -992,7 +921,6 @@ class Saml2Client(object):
|
||||
:param resource: The resource you want to perform the action on
|
||||
:param subject: Who wants to do the thing
|
||||
:param binding: Which binding to use for sending the request
|
||||
:param log: Where to write log messages
|
||||
:param sign: Whether the request should be signed or not.
|
||||
:return: AuthzDecisionQuery instance
|
||||
"""
|
||||
@@ -1001,14 +929,8 @@ class Saml2Client(object):
|
||||
service_url = self.service_url()
|
||||
my_name = self._my_name()
|
||||
|
||||
if log is None:
|
||||
log = self.logger
|
||||
|
||||
if log:
|
||||
log.info("spentityid: %s" % spentityid)
|
||||
log.info("service_url: %s" % service_url)
|
||||
log.info("my_name: %s" % my_name)
|
||||
|
||||
logger.info("spentityid: %s\nservice_url: %s\nmy_name: %s" % (
|
||||
spentityid, service_url, my_name))
|
||||
|
||||
# authen_req = self.authn_request(session_id, location,
|
||||
# service_url, spentityid, my_name, vorg,
|
||||
@@ -1026,13 +948,13 @@ class Saml2Client(object):
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def authz_decision_query_response(self, response, log=None):
|
||||
def authz_decision_query_response(self, response):
|
||||
""" Verify that the response is OK """
|
||||
pass
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def do_authz_decision_query(self, entityid, assertion=None,
|
||||
log=None, sign=False):
|
||||
sign=False):
|
||||
|
||||
authz_decision_query = self.authz_decision_query(entityid, assertion)
|
||||
|
||||
@@ -1051,21 +973,17 @@ class Saml2Client(object):
|
||||
response = send_using_soap(authz_decision_query, destination,
|
||||
self.config.key_file,
|
||||
self.config.cert_file,
|
||||
log=log,
|
||||
ca_certs=self.config.ca_certs)
|
||||
if response:
|
||||
if log:
|
||||
log.info("Verifying response")
|
||||
response = self.authz_decision_query_response(response, log)
|
||||
logger.info("Verifying response")
|
||||
response = self.authz_decision_query_response(response)
|
||||
|
||||
if response:
|
||||
#not_done.remove(entity_id)
|
||||
if log:
|
||||
log.info("OK response from %s" % destination)
|
||||
logger.info("OK response from %s" % destination)
|
||||
return response
|
||||
else:
|
||||
if log:
|
||||
log.info("NOT OK response from %s" % destination)
|
||||
logger.info("NOT OK response from %s" % destination)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ from saml2.attribute_converter import ac_factory
|
||||
from saml2.assertion import Policy
|
||||
from saml2.sigver import get_xmlsec_binary
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file",
|
||||
"secret", "accepted_time_diff", "name", "ca_certs",
|
||||
"description",
|
||||
@@ -84,8 +86,6 @@ LOG_HANDLER = {
|
||||
}
|
||||
|
||||
LOG_FORMAT = "%(asctime)s %(name)s: %(levelname)s %(message)s"
|
||||
#LOG_FORMAT = "%(asctime)s %(name)s: %(levelname)s [%(sid)s][%(func)s] %
|
||||
# (message)s"
|
||||
|
||||
class ConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"""
|
||||
Contains classes used in the SAML ECP profile
|
||||
"""
|
||||
import logging
|
||||
|
||||
from saml2 import element_to_extension_element
|
||||
from saml2 import samlp
|
||||
@@ -35,6 +36,8 @@ from saml2.s_utils import sid
|
||||
|
||||
from saml2.response import authn_response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
||||
|
||||
def ecp_capable(headers):
|
||||
@@ -49,14 +52,12 @@ def ecp_capable(headers):
|
||||
ACTOR = "http://schemas.xmlsoap.org/soap/actor/next"
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def ecp_auth_request(cls, entityid=None, relay_state="",
|
||||
log=None, sign=False):
|
||||
def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
|
||||
""" Makes an authentication request.
|
||||
|
||||
:param entityid: The entity ID of the IdP to send the request to
|
||||
:param relay_state: To where the user should be returned after
|
||||
successfull log in.
|
||||
:param log: Where to write log messages
|
||||
:param sign: Whether the request should be signed or not.
|
||||
:return: AuthnRequest response
|
||||
"""
|
||||
@@ -111,13 +112,11 @@ def ecp_auth_request(cls, entityid=None, relay_state="",
|
||||
# <samlp:AuthnRequest>
|
||||
# ----------------------------------------
|
||||
|
||||
if log:
|
||||
log.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
|
||||
logger.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
|
||||
|
||||
location = cls._sso_location(entityid, binding=BINDING_SOAP)
|
||||
session_id = sid()
|
||||
authn_req = cls.authn(location, session_id, log=log,
|
||||
binding=BINDING_PAOS,
|
||||
authn_req = cls.authn(location, session_id, binding=BINDING_PAOS,
|
||||
service_url_binding=BINDING_PAOS)
|
||||
|
||||
body = soapenv.Body()
|
||||
@@ -144,9 +143,7 @@ def handle_ecp_authn_response(cls, soap_message, outstanding=None):
|
||||
item.c_namespace == ecp.NAMESPACE:
|
||||
_relay_state = item
|
||||
|
||||
response = authn_response(cls.config, cls.service_url(),
|
||||
outstanding, log=cls.logger,
|
||||
debug=cls.debug,
|
||||
response = authn_response(cls.config, cls.service_url(), outstanding,
|
||||
allow_unsolicited=True)
|
||||
|
||||
response.loads("%s" % rdict["body"], False, soap_message)
|
||||
@@ -182,9 +179,8 @@ class ECPServer(Server):
|
||||
|
||||
TODO: Still tentative
|
||||
"""
|
||||
def __init__(self, config_file="", config=None, _cache="",
|
||||
log=None, debug=0):
|
||||
Server.__init__(self, config_file, config, _cache, log, debug)
|
||||
def __init__(self, config_file="", config=None, _cache=""):
|
||||
Server.__init__(self, config_file, config, _cache)
|
||||
|
||||
def parse_ecp_authn_query(self):
|
||||
pass
|
||||
|
||||
@@ -21,6 +21,7 @@ programs.
|
||||
"""
|
||||
|
||||
import cookielib
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from saml2 import soap
|
||||
@@ -37,11 +38,12 @@ from saml2.metadata import MetaData
|
||||
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
|
||||
PAOS_HEADER_INFO = 'ver="%s";"%s"' % (paos.NAMESPACE, SERVICE)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, user, passwd, sp="", idp=None, metadata_file=None,
|
||||
xmlsec_binary=None, verbose=0, ca_certs="",
|
||||
disable_ssl_certificate_validation=True, logger=None,
|
||||
debug=False):
|
||||
disable_ssl_certificate_validation=True):
|
||||
"""
|
||||
:param user: user name
|
||||
:param passwd: user password
|
||||
@@ -55,15 +57,11 @@ class Client(object):
|
||||
:param disable_ssl_certificate_validation: If
|
||||
disable_ssl_certificate_validation is true, SSL cert validation
|
||||
will not be performed.
|
||||
:param logger: Somewhere to write logs to
|
||||
:param debug: Whether debug output is needed
|
||||
"""
|
||||
self._idp = idp
|
||||
self._sp = sp
|
||||
self.user = user
|
||||
self.passwd = passwd
|
||||
self.log = logger
|
||||
self.debug = debug
|
||||
self._verbose = verbose
|
||||
|
||||
if metadata_file:
|
||||
@@ -83,9 +81,7 @@ class Client(object):
|
||||
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
|
||||
|
||||
def _debug_info(self, text):
|
||||
if self.debug:
|
||||
if self.log:
|
||||
self.log.debug(text)
|
||||
logger.debug(text)
|
||||
|
||||
if self._verbose:
|
||||
print >> sys.stderr, text
|
||||
@@ -104,8 +100,7 @@ class Client(object):
|
||||
binding=binding)
|
||||
if ssos:
|
||||
self._idp = ssos[0]
|
||||
if self.debug:
|
||||
self.log.debug("IdP endpoint: '%s'" % self._idp)
|
||||
logger.debug("IdP endpoint: '%s'" % self._idp)
|
||||
return self._idp
|
||||
|
||||
raise Exception("No suitable endpoint found for entity id '%s'" % (
|
||||
@@ -244,8 +239,7 @@ class Client(object):
|
||||
self._debug_info("[P3] IdP response: %s" % response)
|
||||
|
||||
self.done_ecp = True
|
||||
if self.debug:
|
||||
self.log.debug("Done ECP")
|
||||
logger.debug("Done ECP")
|
||||
|
||||
return None
|
||||
|
||||
@@ -297,9 +291,7 @@ class Client(object):
|
||||
# header blocks may also be present
|
||||
try:
|
||||
respdict = soap.class_instances_from_soap_enveloped_saml_thingies(
|
||||
response,
|
||||
[paos, ecp,
|
||||
samlp])
|
||||
response,[paos, ecp,samlp])
|
||||
self.ecp_conversation(respdict, idp_entity_id)
|
||||
# should by now be authenticated so this should go smoothly
|
||||
response = op(**opargs)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -15,6 +16,8 @@ __author__ = 'rohe0002'
|
||||
|
||||
import xmlenc as enc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#<EncryptedData
|
||||
# xmlns="http://www.w3.org/2001/04/xmlenc#"
|
||||
# Type="http://www.w3.org/2001/04/xmlenc#Element">
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
# CookieJar class with httplib2.
|
||||
#
|
||||
#
|
||||
import logging
|
||||
|
||||
import re
|
||||
import cookielib
|
||||
@@ -40,6 +41,8 @@ from httplib2 import Http
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DummyRequest(object):
|
||||
"""Simulated urllib2.Request object for httplib2
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import logging
|
||||
|
||||
__author__ = 'rohe0002'
|
||||
|
||||
import cgi
|
||||
from urllib import quote
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Response(object):
|
||||
_template = None
|
||||
_status = '200 OK'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
|
||||
import memcache
|
||||
from saml2 import time_util
|
||||
@@ -8,6 +9,7 @@ from saml2.cache import ToOld, CacheError
|
||||
# gathered from several different sources, all with their own
|
||||
# timeout time.
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _key(prefix, name):
|
||||
return "%s_%s" % (prefix, name)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
|
||||
__author__ = 'rolandh'
|
||||
|
||||
@@ -11,6 +12,8 @@ from saml2 import time_util
|
||||
from saml2.cache import ToOld
|
||||
from saml2.time_util import TIME_FORMAT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Cache(object):
|
||||
def __init__(self, server=None, debug=0, db=None):
|
||||
if server:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"""
|
||||
Contains classes and functions to alleviate the handling of SAML metadata
|
||||
"""
|
||||
import logging
|
||||
|
||||
import httplib2
|
||||
import sys
|
||||
@@ -57,6 +58,8 @@ from saml2.sigver import pem_format
|
||||
from saml2.validate import valid_instance, NotValid
|
||||
from saml2.country_codes import D_COUNTRIES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def metadata_extension_modules():
|
||||
_pre = "saml2.extension"
|
||||
res = []
|
||||
@@ -86,8 +89,7 @@ def keep_updated(func, self=None, entity_id=None, *args, **kwargs):
|
||||
class MetaData(object):
|
||||
""" A class to manage metadata information """
|
||||
|
||||
def __init__(self, xmlsec_binary=None, attrconv=None, log=None):
|
||||
self.log = log
|
||||
def __init__(self, xmlsec_binary=None, attrconv=None):
|
||||
self.xmlsec_binary = xmlsec_binary
|
||||
self.attrconv = attrconv or []
|
||||
self._loc_key = {}
|
||||
@@ -388,15 +390,9 @@ class MetaData(object):
|
||||
def do_entity_descriptor(self, entity_descr, source, valid_until=0):
|
||||
try:
|
||||
if not valid(entity_descr.valid_until):
|
||||
if self.log:
|
||||
self.log.info(
|
||||
"Entity descriptor (entity id:%s) to old" % \
|
||||
entity_descr.entity_id)
|
||||
else:
|
||||
print >> sys.stderr, \
|
||||
"Entity descriptor (entity id:%s) to old" % \
|
||||
entity_descr.entity_id
|
||||
return
|
||||
logger.info("Entity descriptor (entity id:%s) to old" % (
|
||||
entity_descr.entity_id,))
|
||||
return
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -483,8 +479,7 @@ class MetaData(object):
|
||||
self.import_metadata(content, (url, cert))
|
||||
return True
|
||||
else:
|
||||
if self.log:
|
||||
self.log.info("Response status: %s" % response.status)
|
||||
logger.info("Response status: %s" % response.status)
|
||||
return False
|
||||
|
||||
@keep_updated
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
import logging
|
||||
from saml2.cache import Cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Population(object):
|
||||
def __init__(self, cache=None):
|
||||
if cache:
|
||||
@@ -33,8 +35,10 @@ class Population(object):
|
||||
def issuers_of_info(self, subject_id):
|
||||
return self.cache.entities(subject_id)
|
||||
|
||||
def get_identity(self, subject_id, entities=None, check_not_on_or_after=True):
|
||||
return self.cache.get_identity(subject_id, entities, check_not_on_or_after)
|
||||
def get_identity(self, subject_id, entities=None,
|
||||
check_not_on_or_after=True):
|
||||
return self.cache.get_identity(subject_id, entities,
|
||||
check_not_on_or_after)
|
||||
|
||||
def get_info_from(self, subject_id, entity_id):
|
||||
return self.cache.get(subject_id, entity_id)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from attribute_converter import to_local
|
||||
from saml2 import time_util
|
||||
@@ -9,20 +9,16 @@ from saml2.validate import valid_instance
|
||||
from saml2.validate import NotValid
|
||||
from saml2.response import IncorrectlySigned
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _dummy(_arg):
|
||||
return None
|
||||
|
||||
class Request(object):
|
||||
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
|
||||
debug=0):
|
||||
def __init__(self, sec_context, receiver_addrs, timeslack=0):
|
||||
self.sec = sec_context
|
||||
self.receiver_addrs = receiver_addrs
|
||||
self.timeslack = timeslack
|
||||
self.log = log
|
||||
self.debug = debug
|
||||
if self.debug and not self.log:
|
||||
self.debug = 0
|
||||
|
||||
self.xmlstr = ""
|
||||
self.name_id = ""
|
||||
self.message = None
|
||||
@@ -38,40 +34,32 @@ class Request(object):
|
||||
|
||||
def _loads(self, xmldata, decode=True):
|
||||
if decode:
|
||||
if self.debug:
|
||||
self.log.debug("Expected to decode and inflate xml data")
|
||||
logger.debug("Expected to decode and inflate xml data")
|
||||
decoded_xml = s_utils.decode_base64_and_inflate(xmldata)
|
||||
else:
|
||||
decoded_xml = xmldata
|
||||
|
||||
# own copy
|
||||
self.xmlstr = decoded_xml[:]
|
||||
if self.debug:
|
||||
self.log.info("xmlstr: %s" % (self.xmlstr,))
|
||||
logger.info("xmlstr: %s" % (self.xmlstr,))
|
||||
try:
|
||||
self.message = self.signature_check(decoded_xml)
|
||||
except TypeError:
|
||||
raise
|
||||
except Exception, excp:
|
||||
if self.log:
|
||||
self.log.info("EXCEPTION: %s", excp)
|
||||
logger.info("EXCEPTION: %s", excp)
|
||||
|
||||
if not self.message:
|
||||
if self.log:
|
||||
self.log.error("Response was not correctly signed")
|
||||
self.log.info(decoded_xml)
|
||||
logger.error("Response was not correctly signed")
|
||||
logger.info(decoded_xml)
|
||||
raise IncorrectlySigned()
|
||||
|
||||
if self.debug:
|
||||
self.log.info("request: %s" % (self.message,))
|
||||
|
||||
logger.info("request: %s" % (self.message,))
|
||||
|
||||
try:
|
||||
valid_instance(self.message)
|
||||
except NotValid, exc:
|
||||
if self.log:
|
||||
self.log.error("Not valid request: %s" % exc.args[0])
|
||||
else:
|
||||
print >> sys.stderr, "Not valid request: %s" % exc.args[0]
|
||||
logger.error("Not valid request: %s" % exc.args[0])
|
||||
raise
|
||||
|
||||
return self
|
||||
@@ -91,12 +79,8 @@ class Request(object):
|
||||
assert self.message.version == "2.0"
|
||||
if self.message.destination and \
|
||||
self.message.destination not in self.receiver_addrs:
|
||||
if self.log:
|
||||
self.log.error("%s != %s" % (self.message.destination,
|
||||
logger.error("%s != %s" % (self.message.destination,
|
||||
self.receiver_addrs))
|
||||
else:
|
||||
print >> sys.stderr, "%s != %s" % (self.message.destination,
|
||||
self.receiver_addrs)
|
||||
raise OtherError("Not destined for me!")
|
||||
|
||||
assert self.issue_instant_ok()
|
||||
@@ -138,16 +122,14 @@ class Request(object):
|
||||
class LogoutRequest(Request):
|
||||
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
|
||||
debug=0):
|
||||
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
|
||||
debug)
|
||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
||||
self.signature_check = self.sec.correctly_signed_logout_request
|
||||
|
||||
|
||||
class AttributeQuery(Request):
|
||||
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
|
||||
debug=0):
|
||||
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
|
||||
debug)
|
||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
||||
self.signature_check = self.sec.correctly_signed_attribute_query
|
||||
|
||||
def attribute(self):
|
||||
@@ -159,8 +141,7 @@ class AttributeQuery(Request):
|
||||
class AuthnRequest(Request):
|
||||
def __init__(self, sec_context, attribute_converters, receiver_addrs,
|
||||
log=None, timeslack=0, debug=0):
|
||||
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
|
||||
debug)
|
||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
||||
self.attribute_converters = attribute_converters
|
||||
self.signature_check = self.sec.correctly_signed_authn_request
|
||||
|
||||
@@ -172,8 +153,7 @@ class AuthnRequest(Request):
|
||||
class AuthzRequest(Request):
|
||||
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
|
||||
debug=0):
|
||||
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
|
||||
debug)
|
||||
Request.__init__(self, sec_context, receiver_addrs, timeslack)
|
||||
self.signature_check = self.sec.correctly_signed_logout_request
|
||||
|
||||
def action(self):
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import calendar
|
||||
import base64
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from saml2 import samlp
|
||||
@@ -38,6 +39,8 @@ from saml2.validate import valid_instance
|
||||
from saml2.validate import valid_address
|
||||
from saml2.validate import NotValid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class IncorrectlySigned(Exception):
|
||||
@@ -205,7 +208,7 @@ class StatusResponse(object):
|
||||
# print "issue_instant: %s" % self.response.issue_instant
|
||||
# print "%s < x < %s" % (lower, upper)
|
||||
issued_at = str_to_time(self.response.issue_instant)
|
||||
return issued_at > lower and issued_at < upper
|
||||
return lower < issued_at < upper
|
||||
|
||||
def _verify(self):
|
||||
if self.request_id and self.in_response_to and \
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
|
||||
import time
|
||||
import base64
|
||||
@@ -22,6 +23,8 @@ except ImportError:
|
||||
from md5 import md5
|
||||
import zlib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class VersionMismatch(Exception):
|
||||
pass
|
||||
|
||||
@@ -304,3 +307,35 @@ def verify_signature(secret, parts):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
FTICKS_FORMAT = "F-TICKS/SWAMID/2.0%s#"
|
||||
|
||||
def fticks_log(sp, logf, idp_entity_id, user_id, secret, assertion):
|
||||
"""
|
||||
'F-TICKS/' federationIdentifier '/' version *('#' attribute '=' value ) '#'
|
||||
Allowed attributes:
|
||||
TS the login time stamp
|
||||
RP the relying party entityID
|
||||
AP the asserting party entityID (typcially the IdP)
|
||||
PN a sha256-hash of the local principal name and a unique key
|
||||
AM the authentication method URN
|
||||
|
||||
:param sp: Client instance
|
||||
:param logf: The log function to use
|
||||
:param idp_entity_id: IdP entity ID
|
||||
:param user_id: The user identifier
|
||||
:param secret: A salt to make the hash more secure
|
||||
:param assertion: A SAML Assertion instance gotten from the IdP
|
||||
"""
|
||||
csum = hmac.new(secret, digestmod=hashlib.sha1)
|
||||
csum.update(user_id)
|
||||
|
||||
info = {
|
||||
"TS": time.time(),
|
||||
"RP": sp.entity_id,
|
||||
"AP": idp_entity_id,
|
||||
"PN": csum.hexdigest(),
|
||||
"AM": assertion.AuthnStatement.AuthnContext.AuthnContextClassRef.text
|
||||
}
|
||||
logf.info(FTICKS_FORMAT % "#".join(["%s=%s" % (a,v) for a,v in info]))
|
||||
@@ -18,6 +18,7 @@
|
||||
"""Contains classes and functions that a SAML2.0 Identity provider (IdP)
|
||||
or attribute authority (AA) may use to conclude its tasks.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import shelve
|
||||
import sys
|
||||
@@ -57,20 +58,20 @@ from saml2.config import config_factory
|
||||
|
||||
from saml2.assertion import Assertion, Policy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class UnknownVO(Exception):
|
||||
pass
|
||||
|
||||
class Identifier(object):
|
||||
""" A class that handles identifiers of objects """
|
||||
def __init__(self, db, voconf=None, debug=0, log=None):
|
||||
def __init__(self, db, voconf=None):
|
||||
if isinstance(db, basestring):
|
||||
self.map = shelve.open(db, writeback=True)
|
||||
else:
|
||||
self.map = db
|
||||
self.voconf = voconf
|
||||
self.debug = debug
|
||||
self.log = log
|
||||
|
||||
|
||||
def _store(self, typ, entity_id, local, remote):
|
||||
self.map["|".join([typ, entity_id, "f", local])] = remote
|
||||
self.map["|".join([typ, entity_id, "b", remote])] = local
|
||||
@@ -277,8 +278,7 @@ class Server(object):
|
||||
idb = addr
|
||||
|
||||
if idb is not None:
|
||||
self.ident = Identifier(idb, self.conf.virtual_organization,
|
||||
self.debug, self.log)
|
||||
self.ident = Identifier(idb, self.conf.virtual_organization)
|
||||
else:
|
||||
raise Exception("Couldn't open identity database: %s" %
|
||||
(dbspec,))
|
||||
|
||||
@@ -20,6 +20,7 @@ Based on the use of xmlsec1 binaries and not the python xmlsec module.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import logging
|
||||
import random
|
||||
import os
|
||||
import sys
|
||||
@@ -40,6 +41,8 @@ from saml2.time_util import instant
|
||||
from tempfile import NamedTemporaryFile
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SIG = "{%s#}%s" % (ds.NAMESPACE, "Signature")
|
||||
|
||||
def signed(item):
|
||||
@@ -321,9 +324,12 @@ def parse_xmlsec_output(output):
|
||||
|
||||
__DEBUG = 0
|
||||
|
||||
LOG_LINE = 60*"="+"\n%s\n"+60*"-"+"\n%s"+60*"="
|
||||
LOG_LINE_2 = 60*"="+"\n%s\n%s\n"+60*"-"+"\n%s"+60*"="
|
||||
|
||||
def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
|
||||
node_name=NODE_NAME, debug=False, node_id=None,
|
||||
log=None, id_attr=""):
|
||||
id_attr=""):
|
||||
""" Verifies the signature of a XML document.
|
||||
|
||||
:param enctext: The signed XML document
|
||||
@@ -374,12 +380,7 @@ def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
|
||||
print p_err
|
||||
verified = parse_xmlsec_output(p_err)
|
||||
except XmlsecError, exc:
|
||||
if log:
|
||||
log.error(60*"=")
|
||||
log.error(p_out)
|
||||
log.error(60*"-")
|
||||
log.error("%s" % exc)
|
||||
log.error(60*"=")
|
||||
logger.error(LOG_LINE % (p_out, exc))
|
||||
raise SignatureError("%s" % (exc,))
|
||||
|
||||
return verified
|
||||
@@ -421,12 +422,10 @@ def read_cert_from_file(cert_file, cert_type):
|
||||
data = open(cert_file).read()
|
||||
return base64.b64encode(str(data))
|
||||
|
||||
def security_context(conf, log=None, debug=None):
|
||||
def security_context(conf, debug=None):
|
||||
""" Creates a security context based on the configuration
|
||||
|
||||
:param conf: The configuration
|
||||
:param log: A logger if different from the one specified in the
|
||||
configuration
|
||||
:return: A SecurityContext instance
|
||||
"""
|
||||
if not conf:
|
||||
@@ -443,12 +442,11 @@ def security_context(conf, log=None, debug=None):
|
||||
|
||||
return SecurityContext(conf.xmlsec_binary, conf.key_file,
|
||||
cert_file=conf.cert_file, metadata=metadata,
|
||||
log=log, debug=debug,
|
||||
only_use_keys_in_metadata=_only_md)
|
||||
debug=debug, only_use_keys_in_metadata=_only_md)
|
||||
|
||||
class SecurityContext(object):
|
||||
def __init__(self, xmlsec_binary, key_file="", key_type= "pem",
|
||||
cert_file="", cert_type="pem", metadata=None, log=None,
|
||||
cert_file="", cert_type="pem", metadata=None,
|
||||
debug=False, template="", encrypt_key_type="des-192",
|
||||
only_use_keys_in_metadata=False):
|
||||
|
||||
@@ -465,7 +463,6 @@ class SecurityContext(object):
|
||||
|
||||
self.metadata = metadata
|
||||
self.only_use_keys_in_metadata = only_use_keys_in_metadata
|
||||
self.log = log
|
||||
self.debug = debug
|
||||
|
||||
if not template:
|
||||
@@ -476,12 +473,8 @@ class SecurityContext(object):
|
||||
|
||||
self.key_type = encrypt_key_type
|
||||
|
||||
if self.debug and not self.log:
|
||||
self.debug = 0
|
||||
|
||||
def correctly_signed(self, xml, must=False):
|
||||
if self.log:
|
||||
self.log.info("verify correct signature")
|
||||
logger.info("verify correct signature")
|
||||
return self.correctly_signed_response(xml, must)
|
||||
|
||||
def encrypt(self, text, recv_key="", template="", key_type=""):
|
||||
@@ -501,8 +494,7 @@ class SecurityContext(object):
|
||||
if not template:
|
||||
template = self.template
|
||||
|
||||
if self.log:
|
||||
self.log.info("input len: %d" % len(text))
|
||||
logger.info("input len: %d" % len(text))
|
||||
_, fil = make_temp("%s" % text, decode=False)
|
||||
ntf = NamedTemporaryFile()
|
||||
|
||||
@@ -513,8 +505,7 @@ class SecurityContext(object):
|
||||
"--output", ntf.name,
|
||||
template]
|
||||
|
||||
if self.debug:
|
||||
self.log.debug("Encryption command: %s" % " ".join(com_list))
|
||||
logger.debug("Encryption command: %s" % " ".join(com_list))
|
||||
|
||||
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
|
||||
|
||||
@@ -522,14 +513,8 @@ class SecurityContext(object):
|
||||
try:
|
||||
parse_xmlsec_output(p_err)
|
||||
except XmlsecError, exc:
|
||||
if self.debug:
|
||||
p_out = pof.stdout.read()
|
||||
self.log.error(60*"=")
|
||||
self.log.error(p_out)
|
||||
self.log.error(p_err)
|
||||
self.log.error(60*"-")
|
||||
self.log.error("%s" % exc)
|
||||
self.log.error(60*"=")
|
||||
p_out = pof.stdout.read()
|
||||
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
|
||||
raise DecryptError("%s" % (exc,))
|
||||
|
||||
ntf.seek(0)
|
||||
@@ -542,8 +527,7 @@ class SecurityContext(object):
|
||||
:return: The decrypted text
|
||||
"""
|
||||
|
||||
if self.log:
|
||||
self.log.info("input len: %d" % len(enctext))
|
||||
logger.info("input len: %d" % len(enctext))
|
||||
_, fil = make_temp("%s" % enctext, decode=False)
|
||||
ntf = NamedTemporaryFile()
|
||||
|
||||
@@ -553,8 +537,7 @@ class SecurityContext(object):
|
||||
"--id-attr:%s" % ID_ATTR, ENC_KEY_CLASS,
|
||||
fil]
|
||||
|
||||
if self.debug:
|
||||
self.log.debug("Decrypt command: %s" % " ".join(com_list))
|
||||
logger.debug("Decrypt command: %s" % " ".join(com_list))
|
||||
|
||||
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
|
||||
|
||||
@@ -562,14 +545,8 @@ class SecurityContext(object):
|
||||
try:
|
||||
parse_xmlsec_output(p_err)
|
||||
except XmlsecError, exc:
|
||||
if self.debug:
|
||||
p_out = pof.stdout.read()
|
||||
self.log.error(60*"=")
|
||||
self.log.error(p_out)
|
||||
self.log.error(p_err)
|
||||
self.log.error(60*"-")
|
||||
self.log.error("%s" % exc)
|
||||
self.log.error(60*"=")
|
||||
p_out = pof.stdout.read()
|
||||
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
|
||||
raise DecryptError("%s" % (exc,))
|
||||
|
||||
ntf.seek(0)
|
||||
@@ -639,12 +616,10 @@ class SecurityContext(object):
|
||||
verified = True
|
||||
break
|
||||
except XmlsecError, exc:
|
||||
if self.log:
|
||||
self.log.error("check_sig: %s" % exc)
|
||||
logger.error("check_sig: %s" % exc)
|
||||
pass
|
||||
except Exception, exc:
|
||||
if self.log:
|
||||
self.log.error("check_sig: %s" % exc)
|
||||
logger.error("check_sig: %s" % exc)
|
||||
raise
|
||||
|
||||
if not verified:
|
||||
@@ -776,21 +751,18 @@ class SecurityContext(object):
|
||||
# Try to find the signing cert in the assertion
|
||||
for assertion in response.assertion:
|
||||
if not assertion.signature:
|
||||
if self.debug:
|
||||
self.log.debug("unsigned")
|
||||
logger.debug("unsigned")
|
||||
if must:
|
||||
raise SignatureError("Signature missing")
|
||||
continue
|
||||
else:
|
||||
if self.debug:
|
||||
self.log.debug("signed")
|
||||
logger.debug("signed")
|
||||
|
||||
try:
|
||||
self._check_signature(decoded_xml, assertion,
|
||||
class_name(assertion), origdoc)
|
||||
except Exception, exc:
|
||||
if self.log:
|
||||
self.log.error("correctly_signed_response: %s" % exc)
|
||||
logger.error("correctly_signed_response: %s" % exc)
|
||||
raise
|
||||
|
||||
return response
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"""
|
||||
Suppport for the client part of the SAML2.0 SOAP binding.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from httplib2 import Http
|
||||
|
||||
@@ -37,6 +38,9 @@ except ImportError:
|
||||
#noinspection PyUnresolvedReferences
|
||||
from elementtree import ElementTree
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class XmlParseError(Exception):
|
||||
pass
|
||||
|
||||
@@ -197,9 +201,8 @@ def soap_fault(message=None, actor=None, code=None, detail=None):
|
||||
|
||||
class HTTPClient(object):
|
||||
""" For sending a message to a HTTP server using POST or GET """
|
||||
def __init__(self, path, keyfile=None, certfile=None, log=None,
|
||||
cookiejar=None, ca_certs="",
|
||||
disable_ssl_certificate_validation=True):
|
||||
def __init__(self, path, keyfile=None, certfile=None, cookiejar=None,
|
||||
ca_certs="", disable_ssl_certificate_validation=True):
|
||||
self.path = path
|
||||
if cookiejar is not None:
|
||||
self.cj = True
|
||||
@@ -210,7 +213,7 @@ class HTTPClient(object):
|
||||
self.cj = False
|
||||
self.server = Http(ca_certs=ca_certs,
|
||||
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
|
||||
self.log = log
|
||||
|
||||
self.response = None
|
||||
|
||||
if keyfile:
|
||||
@@ -299,13 +302,12 @@ class HTTPClient(object):
|
||||
|
||||
class SOAPClient(object):
|
||||
|
||||
def __init__(self, server_url, keyfile=None, certfile=None, log=None,
|
||||
def __init__(self, server_url, keyfile=None, certfile=None,
|
||||
cookiejar=None, ca_certs="",
|
||||
disable_ssl_certificate_validation=True):
|
||||
self.server = HTTPClient(server_url, keyfile, certfile, log,
|
||||
cookiejar, ca_certs=ca_certs,
|
||||
self.server = HTTPClient(server_url, keyfile, certfile, cookiejar,
|
||||
ca_certs=ca_certs,
|
||||
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
|
||||
self.log = log
|
||||
self.response = None
|
||||
|
||||
def send(self, request, path=None, headers=None, sign=None, sec=None):
|
||||
@@ -325,8 +327,7 @@ class SOAPClient(object):
|
||||
|
||||
self.response = _response
|
||||
if _response:
|
||||
if self.log:
|
||||
self.log.info("SOAP response: %s" % _response)
|
||||
logger.info("SOAP response: %s" % _response)
|
||||
return parse_soap_enveloped_saml_response(_response)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import logging
|
||||
from saml2.attribute_resolver import AttributeResolver
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class VirtualOrg(object):
|
||||
def __init__(self, sp, vorg, log=None):
|
||||
self.sp = sp # The parent SP client instance
|
||||
self.config = sp.config
|
||||
self.vorg_name = vorg
|
||||
if log is None:
|
||||
self.log = self.sp.logger
|
||||
else:
|
||||
self.log = log
|
||||
self.vorg_conf = self.config.vo_conf(self.vorg_name)
|
||||
|
||||
def _cache_session(self, session_info):
|
||||
@@ -44,8 +43,7 @@ class VirtualOrg(object):
|
||||
# Remove the ones I have cached data from about this subject
|
||||
vo_members = [m for m in vo_members if not self.sp.users.cache.active(
|
||||
subject_id, m)]
|
||||
if self.log:
|
||||
self.log.info("VO members (not cached): %s" % vo_members)
|
||||
logger.info("VO members (not cached): %s" % vo_members)
|
||||
return vo_members
|
||||
|
||||
def get_common_identifier(self, subject_id):
|
||||
@@ -61,12 +59,9 @@ class VirtualOrg(object):
|
||||
return None
|
||||
|
||||
def do_aggregation(self, subject_id, log=None):
|
||||
if log is None:
|
||||
log = self.log
|
||||
|
||||
if log:
|
||||
log.info("** Do VO aggregation **")
|
||||
log.info("SubjectID: %s, VO:%s" % (subject_id, self.vorg_name))
|
||||
|
||||
logger.info("** Do VO aggregation **\nSubjectID: %s, VO:%s" % (
|
||||
subject_id, self.vorg_name))
|
||||
|
||||
to_ask = self.members_to_ask(subject_id)
|
||||
if to_ask:
|
||||
@@ -90,11 +85,9 @@ class VirtualOrg(object):
|
||||
log=log, real_id=subject_id):
|
||||
_ = self._cache_session(session_info)
|
||||
|
||||
if log:
|
||||
log.info(
|
||||
">Issuers: %s" % self.sp.users.issuers_of_info(subject_id))
|
||||
log.info(
|
||||
"AVA: %s" % (self.sp.users.get_identity(subject_id),))
|
||||
logger.info(">Issuers: %s" % self.sp.users.issuers_of_info(
|
||||
subject_id))
|
||||
logger.info("AVA: %s" % (self.sp.users.get_identity(subject_id),))
|
||||
|
||||
return True
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user