Switched from one authentication method to use the AuthnBroker to chose a set of methods based on the requested authentication context.
This commit is contained in:
		@@ -7,7 +7,8 @@
 | 
			
		||||
 | 
			
		||||
<form action="${action}" method="post">
 | 
			
		||||
    <input type="hidden" name="key" value="${key}"/>
 | 
			
		||||
    <input type="hidden" name="came_from" value="${came_from}"/>
 | 
			
		||||
    <input type="hidden" name="authn_reference" value="${authn_reference}"/>
 | 
			
		||||
    <input type="hidden" name="redirect_uri" value="${redirect_uri}"/>
 | 
			
		||||
 | 
			
		||||
    <div class="label">
 | 
			
		||||
        <label for="login">Username</label>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,16 +17,24 @@ from saml2 import BINDING_SOAP
 | 
			
		||||
from saml2 import BINDING_HTTP_REDIRECT
 | 
			
		||||
from saml2 import BINDING_HTTP_POST
 | 
			
		||||
from saml2 import time_util
 | 
			
		||||
from saml2.httputil import Response, NotFound
 | 
			
		||||
 | 
			
		||||
from saml2.authn_context import AuthnBroker
 | 
			
		||||
from saml2.authn_context import PASSWORD
 | 
			
		||||
from saml2.authn_context import UNSPECIFIED
 | 
			
		||||
from saml2.authn_context import authn_context_class_ref
 | 
			
		||||
from saml2.httputil import Response
 | 
			
		||||
from saml2.httputil import NotFound
 | 
			
		||||
from saml2.httputil import geturl
 | 
			
		||||
from saml2.httputil import get_post
 | 
			
		||||
from saml2.httputil import Redirect
 | 
			
		||||
from saml2.httputil import Unauthorized
 | 
			
		||||
from saml2.httputil import BadRequest
 | 
			
		||||
from saml2.httputil import ServiceError
 | 
			
		||||
from saml2.ident import Unknown
 | 
			
		||||
from saml2.s_utils import rndstr, UnknownPrincipal, UnsupportedBinding
 | 
			
		||||
from saml2.s_utils import rndstr
 | 
			
		||||
from saml2.s_utils import UnknownPrincipal
 | 
			
		||||
from saml2.s_utils import UnsupportedBinding
 | 
			
		||||
from saml2.s_utils import PolicyError
 | 
			
		||||
from saml2.saml import AUTHN_PASSWORD
 | 
			
		||||
from saml2.sigver import verify_redirect_signature
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger("saml2.idp")
 | 
			
		||||
@@ -176,12 +184,14 @@ class Service(object):
 | 
			
		||||
    #
 | 
			
		||||
    #     return resp(self.environ, self.start_response)
 | 
			
		||||
 | 
			
		||||
    def not_authn(self, key):
 | 
			
		||||
        pass
 | 
			
		||||
    def not_authn(self, key, requested_authn_context):
 | 
			
		||||
        ruri = geturl(self.environ, query=False)
 | 
			
		||||
        return do_authentication(self.environ, self.start_response,
 | 
			
		||||
                                 authn_context=requested_authn_context,
 | 
			
		||||
                                 key=key, redirect_uri=ruri)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
AUTHN = (AUTHN_PASSWORD, "http://lingon.catalogix.se/login")
 | 
			
		||||
 | 
			
		||||
REPOZE_ID_EQUIVALENT = "uid"
 | 
			
		||||
FORM_SPEC = """<form name="myform" method="post" action="%s">
 | 
			
		||||
@@ -194,6 +204,12 @@ FORM_SPEC = """<form name="myform" method="post" action="%s">
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthenticationNeeded(Exception):
 | 
			
		||||
    def __init__(self, authn_context=None, *args, **kwargs):
 | 
			
		||||
        Exception.__init__(*args, **kwargs)
 | 
			
		||||
        self.authn_context = authn_context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SSO(Service):
 | 
			
		||||
    def __init__(self, environ, start_response, user=None):
 | 
			
		||||
        Service.__init__(self, environ, start_response, user)
 | 
			
		||||
@@ -204,7 +220,7 @@ class SSO(Service):
 | 
			
		||||
        self.destination = None
 | 
			
		||||
        self.req_info = None
 | 
			
		||||
 | 
			
		||||
    def verify(self, query, binding):
 | 
			
		||||
    def verify_request(self, query, binding):
 | 
			
		||||
        """
 | 
			
		||||
        :param query: The SAML query, transport encoded
 | 
			
		||||
        :param binding: Which binding the query came in over
 | 
			
		||||
@@ -244,7 +260,7 @@ class SSO(Service):
 | 
			
		||||
 | 
			
		||||
    def do(self, query, binding_in, relay_state=""):
 | 
			
		||||
        try:
 | 
			
		||||
            resp_args, _resp = self.verify(query, binding_in)
 | 
			
		||||
            resp_args, _resp = self.verify_request(query, binding_in)
 | 
			
		||||
        except UnknownPrincipal, excp:
 | 
			
		||||
            logger.error("UnknownPrincipal: %s" % (excp,))
 | 
			
		||||
            resp = ServiceError("UnknownPrincipal: %s" % (excp,))
 | 
			
		||||
@@ -261,8 +277,10 @@ class SSO(Service):
 | 
			
		||||
            if REPOZE_ID_EQUIVALENT:
 | 
			
		||||
                identity[REPOZE_ID_EQUIVALENT] = self.user
 | 
			
		||||
            try:
 | 
			
		||||
                _resp = IDP.create_authn_response(identity, userid=self.user,
 | 
			
		||||
                                                  authn=AUTHN, **resp_args)
 | 
			
		||||
                _resp = IDP.create_authn_response(
 | 
			
		||||
                    identity, userid=self.user,
 | 
			
		||||
                    authn=AUTHN_BROKER[self.environ["idp.authn_ref"]],
 | 
			
		||||
                    **resp_args)
 | 
			
		||||
            except Exception, excp:
 | 
			
		||||
                logger.error("Exception: %s" % (excp,))
 | 
			
		||||
                resp = ServiceError("Exception: %s" % (excp,))
 | 
			
		||||
@@ -275,64 +293,80 @@ class SSO(Service):
 | 
			
		||||
        logger.debug("HTTPargs: %s" % http_args)
 | 
			
		||||
        return self.response(self.binding_out, http_args)
 | 
			
		||||
 | 
			
		||||
    def _authn(self, _dict):
 | 
			
		||||
        logger.debug("_auth: %s" % _dict)
 | 
			
		||||
        if not self.user:
 | 
			
		||||
            key = sha1("%s" % _dict).hexdigest()
 | 
			
		||||
            IDP.ticket[key] = _dict
 | 
			
		||||
            _resp = key
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
                _resp = IDP.ticket[_dict["key"]]
 | 
			
		||||
                del IDP.ticket[_dict["key"]]
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                key = sha1("%s" % _dict).hexdigest()
 | 
			
		||||
                IDP.ticket[key] = _dict
 | 
			
		||||
                _resp = key
 | 
			
		||||
 | 
			
		||||
        return _resp
 | 
			
		||||
    def _store_request(self, _dict):
 | 
			
		||||
        logger.debug("_store_request: %s" % _dict)
 | 
			
		||||
        key = sha1(_dict["SAMLRequest"]).hexdigest()
 | 
			
		||||
        # store the AuthnRequest
 | 
			
		||||
        IDP.ticket[key] = _dict
 | 
			
		||||
        return key
 | 
			
		||||
 | 
			
		||||
    def redirect(self):
 | 
			
		||||
        """ This is the HTTP-redirect endpoint """
 | 
			
		||||
        logger.info("--- In SSO Redirect ---")
 | 
			
		||||
        _info = self._authn(self.unpack_redirect())
 | 
			
		||||
        if isinstance(_info, basestring):
 | 
			
		||||
            return self.not_authn(_info)
 | 
			
		||||
        _info = self.unpack_redirect()
 | 
			
		||||
 | 
			
		||||
        if "SigAlg" in _info and "Signature" in _info:  # Signed request
 | 
			
		||||
        try:
 | 
			
		||||
            _info = IDP.ticket[_info["key"]]
 | 
			
		||||
            self.req_info = _info["req_info"]
 | 
			
		||||
            del IDP.ticket[_info["key"]]
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            self.req_info = IDP.parse_authn_request(_info["SAMLRequest"],
 | 
			
		||||
                                                    BINDING_HTTP_REDIRECT)
 | 
			
		||||
            issuer = self.req_info.message.issuer.text
 | 
			
		||||
            _certs = IDP.metadata.certs(issuer, "any", "signing")
 | 
			
		||||
            verified_ok = False
 | 
			
		||||
            for cert in _certs:
 | 
			
		||||
                if verify_redirect_signature(_info, cert):
 | 
			
		||||
                    verified_ok = True
 | 
			
		||||
                    break
 | 
			
		||||
            if not verified_ok:
 | 
			
		||||
                resp = BadRequest("Message signature verification failure")
 | 
			
		||||
                return resp(self.environ, self.start_response)
 | 
			
		||||
            _req = self.req_info.message
 | 
			
		||||
 | 
			
		||||
        return self.operation(_info, BINDING_HTTP_REDIRECT)
 | 
			
		||||
            if "SigAlg" in _info and "Signature" in _info:  # Signed request
 | 
			
		||||
                issuer = _req.issuer.text
 | 
			
		||||
                _certs = IDP.metadata.certs(issuer, "any", "signing")
 | 
			
		||||
                verified_ok = False
 | 
			
		||||
                for cert in _certs:
 | 
			
		||||
                    if verify_redirect_signature(_info, cert):
 | 
			
		||||
                        verified_ok = True
 | 
			
		||||
                        break
 | 
			
		||||
                if not verified_ok:
 | 
			
		||||
                    resp = BadRequest("Message signature verification failure")
 | 
			
		||||
                    return resp(self.environ, self.start_response)
 | 
			
		||||
 | 
			
		||||
            if self.user:
 | 
			
		||||
                if _req.force_authn:
 | 
			
		||||
                    _info["req_info"] = self.req_info
 | 
			
		||||
                    key = self._store_request(_info)
 | 
			
		||||
                    return self.not_authn(key, _req.requested_authn_context)
 | 
			
		||||
                else:
 | 
			
		||||
                    return self.operation(_info, BINDING_HTTP_REDIRECT)
 | 
			
		||||
            else:
 | 
			
		||||
                _info["req_info"] = self.req_info
 | 
			
		||||
                key = self._store_request(_info)
 | 
			
		||||
                return self.not_authn(key, _req.requested_authn_context)
 | 
			
		||||
        else:
 | 
			
		||||
            return self.operation(_info, BINDING_HTTP_REDIRECT)
 | 
			
		||||
 | 
			
		||||
    def post(self):
 | 
			
		||||
        """
 | 
			
		||||
        The HTTP-Post endpoint
 | 
			
		||||
        """
 | 
			
		||||
        logger.info("--- In SSO POST ---")
 | 
			
		||||
        _dict = self.unpack_either()
 | 
			
		||||
        _resp = self._authn(_dict)
 | 
			
		||||
        logger.debug("_req: %s" % _resp)
 | 
			
		||||
        if isinstance(_resp, basestring):
 | 
			
		||||
            return self.not_authn(_resp)
 | 
			
		||||
        return self.operation(_resp, BINDING_HTTP_POST)
 | 
			
		||||
        _info = self.unpack_either()
 | 
			
		||||
        self.req_info = IDP.parse_authn_request(
 | 
			
		||||
            _info["SAMLRequest"], BINDING_HTTP_POST)
 | 
			
		||||
        _req = self.req_info.message
 | 
			
		||||
        if self.user:
 | 
			
		||||
            if _req.force_authn:
 | 
			
		||||
                _info["req_info"] = self.req_info
 | 
			
		||||
                key = self._store_request(_info)
 | 
			
		||||
                return self.not_authn(key, _req.requested_authn_context)
 | 
			
		||||
            else:
 | 
			
		||||
                return self.operation(_info, BINDING_HTTP_POST)
 | 
			
		||||
        else:
 | 
			
		||||
            _info["req_info"] = self.req_info
 | 
			
		||||
            key = self._store_request(_info)
 | 
			
		||||
            return self.not_authn(key, _req.requested_authn_context)
 | 
			
		||||
 | 
			
		||||
    def artifact(self):
 | 
			
		||||
        # Can be either by HTTP_Redirect or HTTP_POST
 | 
			
		||||
        _req = self._authn(self.unpack_either())
 | 
			
		||||
        if isinstance(_req, basestring):
 | 
			
		||||
            return self.not_authn(_req)
 | 
			
		||||
        return self.artifact_operation(_req)
 | 
			
		||||
    # def artifact(self):
 | 
			
		||||
    #     # Can be either by HTTP_Redirect or HTTP_POST
 | 
			
		||||
    #     _req = self._store_request(self.unpack_either())
 | 
			
		||||
    #     if isinstance(_req, basestring):
 | 
			
		||||
    #         return self.not_authn(_req)
 | 
			
		||||
    #     return self.artifact_operation(_req)
 | 
			
		||||
 | 
			
		||||
    def ecp(self):
 | 
			
		||||
        # The ECP interface
 | 
			
		||||
@@ -368,32 +402,49 @@ class SSO(Service):
 | 
			
		||||
# === Authentication ====
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_authentication(environ, start_response, authn_context, key,
 | 
			
		||||
                      redirect_uri):
 | 
			
		||||
    """
 | 
			
		||||
    Display the login form
 | 
			
		||||
    """
 | 
			
		||||
    logger.debug("Do authentication")
 | 
			
		||||
    auth_info = AUTHN_BROKER.pick(authn_context)
 | 
			
		||||
 | 
			
		||||
    if len(auth_info):
 | 
			
		||||
        method, reference = auth_info[0]
 | 
			
		||||
        logger.debug("Authn chosen: %s (ref=%s)" % (method, reference))
 | 
			
		||||
        return method(environ, start_response, reference, key, redirect_uri)
 | 
			
		||||
    else:
 | 
			
		||||
        resp = Unauthorized("No usable authentication method")
 | 
			
		||||
        return resp(environ, start_response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
PASSWD = {"roland": "dianakra",
 | 
			
		||||
          "babs": "howes",
 | 
			
		||||
          "upper": "crust"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_authentication(environ, start_response, cookie=None):
 | 
			
		||||
def username_password_authn(environ, start_response, reference, key,
 | 
			
		||||
                            redirect_uri):
 | 
			
		||||
    """
 | 
			
		||||
    Display the login form
 | 
			
		||||
    """
 | 
			
		||||
    query = parse_qs(environ["QUERY_STRING"])
 | 
			
		||||
 | 
			
		||||
    logger.info("The login page")
 | 
			
		||||
    if cookie:
 | 
			
		||||
        headers = [cookie]
 | 
			
		||||
    else:
 | 
			
		||||
        headers = []
 | 
			
		||||
    headers = []
 | 
			
		||||
 | 
			
		||||
    resp = Response(mako_template="login.mako", template_lookup=LOOKUP,
 | 
			
		||||
                    headers=headers)
 | 
			
		||||
 | 
			
		||||
    argv = {
 | 
			
		||||
        "action": "/verify",
 | 
			
		||||
        "came_from": query["came_from"][0],
 | 
			
		||||
        "login": "",
 | 
			
		||||
        "password": "",
 | 
			
		||||
        "key": query["key"][0]
 | 
			
		||||
        "key": key,
 | 
			
		||||
        "authn_reference": reference,
 | 
			
		||||
        "redirect_uri": redirect_uri
 | 
			
		||||
    }
 | 
			
		||||
    logger.info("do_authentication argv: %s" % argv)
 | 
			
		||||
    return resp(environ, start_response, **argv)
 | 
			
		||||
@@ -426,10 +477,11 @@ def do_verify(environ, start_response, _):
 | 
			
		||||
        IDP.cache.uid2user[uid] = user
 | 
			
		||||
        IDP.cache.user2uid[user] = uid
 | 
			
		||||
        logger.debug("Register %s under '%s'" % (user, uid))
 | 
			
		||||
        kaka = set_cookie("idpauthn", "/", uid)
 | 
			
		||||
        lox = "http://%s%s?id=%s&key=%s" % (environ["HTTP_HOST"],
 | 
			
		||||
                                            query["came_from"][0], uid,
 | 
			
		||||
                                            query["key"][0])
 | 
			
		||||
 | 
			
		||||
        kaka = set_cookie("idpauthn", "/", uid, query["authn_reference"][0])
 | 
			
		||||
 | 
			
		||||
        lox = "%s?id=%s&key=%s" % (query["redirect_uri"][0], uid,
 | 
			
		||||
                                   query["key"][0])
 | 
			
		||||
        logger.debug("Redirect => %s" % lox)
 | 
			
		||||
        resp = Redirect(lox, headers=[kaka], content="text/html")
 | 
			
		||||
 | 
			
		||||
@@ -663,19 +715,20 @@ class NIM(Service):
 | 
			
		||||
# ----------------------------------------------------------------------------
 | 
			
		||||
# Cookie handling
 | 
			
		||||
# ----------------------------------------------------------------------------
 | 
			
		||||
def kaka2user(kaka):
 | 
			
		||||
def info_from_cookie(kaka):
 | 
			
		||||
    logger.debug("KAKA: %s" % kaka)
 | 
			
		||||
    if kaka:
 | 
			
		||||
        cookie_obj = SimpleCookie(kaka)
 | 
			
		||||
        morsel = cookie_obj.get("idpauthn", None)
 | 
			
		||||
        if morsel:
 | 
			
		||||
            try:
 | 
			
		||||
                return IDP.cache.uid2user[morsel.value]
 | 
			
		||||
                key, ref = base64.b64decode(morsel.value).split(":")
 | 
			
		||||
                return IDP.cache.uid2user[key], ref
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                return None
 | 
			
		||||
                return None, None
 | 
			
		||||
        else:
 | 
			
		||||
            logger.debug("No idpauthn cookie")
 | 
			
		||||
    return None
 | 
			
		||||
    return None, None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def delete_cookie(environ, name):
 | 
			
		||||
@@ -693,9 +746,9 @@ def delete_cookie(environ, name):
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_cookie(name, _, value):
 | 
			
		||||
def set_cookie(name, _, *args):
 | 
			
		||||
    cookie = SimpleCookie()
 | 
			
		||||
    cookie[name] = value
 | 
			
		||||
    cookie[name] = base64.b64encode(":".join(args))
 | 
			
		||||
    cookie[name]['path'] = "/"
 | 
			
		||||
    cookie[name]["expires"] = _expiration(5)  # 5 minutes from now
 | 
			
		||||
    logger.debug("Cookie expires: %s" % cookie[name]["expires"])
 | 
			
		||||
@@ -769,7 +822,8 @@ def application(environ, start_response):
 | 
			
		||||
 | 
			
		||||
    if kaka:
 | 
			
		||||
        logger.info("= KAKA =")
 | 
			
		||||
        user = kaka2user(kaka)
 | 
			
		||||
        user, authn_ref = info_from_cookie(kaka)
 | 
			
		||||
        environ["idp.authn_ref"] = authn_ref
 | 
			
		||||
    else:
 | 
			
		||||
        try:
 | 
			
		||||
            query = parse_qs(environ["QUERY_STRING"])
 | 
			
		||||
@@ -827,14 +881,21 @@ LOOKUP = TemplateLookup(directories=[ROOT + 'templates', ROOT + 'htdocs'],
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    import sys
 | 
			
		||||
    import socket
 | 
			
		||||
    from idp_user import USERS
 | 
			
		||||
    from idp_user import EXTRA
 | 
			
		||||
    from wsgiref.simple_server import make_server
 | 
			
		||||
 | 
			
		||||
    PORT = 8088
 | 
			
		||||
 | 
			
		||||
    IDP = server.Server(sys.argv[1], cache=Cache()
 | 
			
		||||
)
 | 
			
		||||
    AUTHN_BROKER = AuthnBroker()
 | 
			
		||||
    AUTHN_BROKER.add(authn_context_class_ref(PASSWORD),
 | 
			
		||||
                     username_password_authn, 10,
 | 
			
		||||
                     "http://%s" % socket.gethostname())
 | 
			
		||||
    AUTHN_BROKER.add(authn_context_class_ref(UNSPECIFIED),
 | 
			
		||||
                     "", 0, "http://%s" % socket.gethostname())
 | 
			
		||||
 | 
			
		||||
    IDP = server.Server(sys.argv[1], cache=Cache())
 | 
			
		||||
    IDP.ticket = {}
 | 
			
		||||
 | 
			
		||||
    SRV = make_server('', PORT, application)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user