    CAS_LOGO_URL = static("cas_server/logo.png")
    CAS_FAVICON_URL = static("cas_server/favicon.ico")
    CAS_LOGO_URL = None
    CAS_FAVICON_URL = None
CAS_SHOW_POWERED = True
CAS_COMPONENT_URLS = {
    "bootstrap3_css": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
    "bootstrap3_js": "//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js",
    "html5shiv": "//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js",
    "respond": "//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js",
    "bootstrap4_css": "//stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css",
    "bootstrap4_js": "//stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js",
    "jquery": "//code.jquery.com/jquery.min.js",
}
CAS_LOGIN_TEMPLATE = 'cas_server/bs4/login.html'
CAS_WARN_TEMPLATE = 'cas_server/bs4/warn.html'
CAS_LOGGED_TEMPLATE = 'cas_server/bs4/logged.html'
CAS_LOGOUT_TEMPLATE = 'cas_server/bs4/logout.html'
CAS_REDIRECT_TO_LOGIN_AFTER_LOGOUT = False
CAS_AUTH_CLASS = 'cas_server.auth.DjangoAuthUser'
CAS_PROXY_CA_CERTIFICATE_PATH = True
CAS_SLO_MAX_PARALLEL_REQUESTS = 10
CAS_SLO_TIMEOUT = 5
CAS_AUTH_SHARED_SECRET = ''
CAS_TGT_VALIDITY = None
CAS_TICKET_VALIDITY = 60
CAS_PGT_VALIDITY = 3600
CAS_TICKET_TIMEOUT = 24*3600
CAS_TICKET_LEN = 64
CAS_LT_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN)
CAS_ST_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN)
CAS_PT_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN)
CAS_PGT_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN)
CAS_PGTIOU_LEN = getattr(settings, 'CAS_TICKET_LEN', CAS_TICKET_LEN)
CAS_LOGIN_TICKET_PREFIX = u'LT'
CAS_SERVICE_TICKET_PREFIX = u'ST'
CAS_PROXY_TICKET_PREFIX = u'PT'
CAS_PROXY_GRANTING_TICKET_PREFIX = u'PGT'
CAS_PROXY_GRANTING_TICKET_IOU_PREFIX = u'PGTIOU'
CAS_SQL_HOST = 'localhost'
CAS_SQL_USERNAME = ''
CAS_SQL_PASSWORD = ''
CAS_SQL_DBNAME = ''
CAS_SQL_DBCHARSET = 'utf8'
CAS_SQL_USER_QUERY = 'SELECT user AS username, pass AS password, users.* FROM users WHERE user = %s'
CAS_SQL_PASSWORD_CHECK = 'crypt'
CAS_SQL_PASSWORD_CHARSET = "utf-8"
CAS_LDAP_SERVER = 'localhost'
CAS_LDAP_USER = None
CAS_LDAP_PASSWORD = None
CAS_LDAP_BASE_DN = None
CAS_LDAP_USER_QUERY = "(uid=%s)"
CAS_LDAP_USERNAME_ATTR = "uid"
CAS_LDAP_PASSWORD_ATTR = "userPassword"
CAS_LDAP_PASSWORD_CHECK = "ldap"
CAS_LDAP_PASSWORD_CHARSET = "utf-8"
CAS_LDAP_ATTRS_VIEW = 0
CAS_TEST_USER = 'test'
CAS_TEST_PASSWORD = 'test'
CAS_TEST_ATTRIBUTES = {
    'nom': 'Nymous',
    'prenom': 'Ano',
    'email': 'anonymous@example.net',
    'alias': ['demo1', 'demo2']
}
CAS_ENABLE_AJAX_AUTH = False
CAS_FEDERATE = False
CAS_FEDERATE_REMEMBER_TIMEOUT = 604800
CAS_NEW_VERSION_HTML_WARNING = True
CAS_NEW_VERSION_EMAIL_WARNING = True
CAS_NEW_VERSION_JSON_URL = "https://pypi.org/pypi/django-cas-server/json"
CAS_SHOW_SERVICE_MESSAGES = True
CAS_INFO_MESSAGES = {
    "cas_explained": {
        "message": _(
            u"The Central Authentication Service grants you access to most of our websites by "
            u"authenticating only once, so you don't need to type your credentials again unless "
            u"your session expires or you logout."
        ),
        "discardable": True,
        "type": "info",  # one of info, success, info, warning, danger
    },
}
CAS_INFO_MESSAGES_ORDER = []
GLOBALS = globals().copy()
MERGED_CAS_COMPONENT_URLS = CAS_COMPONENT_URLS.copy()     def python_2_unicode_compatible(func):
    def attributs(self):
    def attributs(self, value):
    def __str__(self):
    def build_username_from_suffix(username, suffix):
    def build_username(self, username):
    def __str__(self):
    def federated_username(self):
    def get_from_federated_username(cls, username):
    def clean_old_entries(cls):
    def clean_deleted_sessions(cls):
    def __str__(self):
    def clean_old_entries(cls):
    def delete(self, *args, **kwargs):
    def clean_old_entries(cls):
    def clean_deleted_sessions(cls):
    def attributs(self):
    def __str__(self):
    def logout(self, request=None):
    def get_ticket(self, ticket_class, service, service_pattern, renew):
    def get_service_url(self, service, service_pattern, renew):
    def __str__(self):
    def check_user(self, user):
    def validate(cls, service):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def send_slos(queryset_list):
    def clean_old_entries(cls):
    def logout(self, session, async_list=None):
    def get_class(ticket, classes=None):
    def username(self):
    def attributs_flat(self):
    def get(cls, ticket, renew=False, service=None):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def send_mails(cls):
class JsonAttributes(models.Model):
    class Meta:
class FederatedIendityProvider(models.Model):
    class Meta:
class FederatedUser(JsonAttributes):
    class Meta:
class FederateSLO(models.Model):
    class Meta:
class UserAttributes(JsonAttributes):
    class Meta:
class User(models.Model):
    class Meta:
class ServicePatternException(Exception):
class BadUsername(ServicePatternException):
class BadFilter(ServicePatternException):
class UserFieldNotDefined(ServicePatternException):
class ServicePattern(models.Model):
    class Meta:
class Username(models.Model):
class ReplaceAttributName(models.Model):
    class Meta:
class FilterAttributValue(models.Model):
class ReplaceAttributValue(models.Model):
class Ticket(JsonAttributes):
    class Meta:
    class DoesNotExist(Exception):
class ServiceTicket(Ticket):
class ProxyTicket(Ticket):
class ProxyGrantingTicket(Ticket):
class Proxy(models.Model):
    class Meta:
class NewVersionWarning(models.Model):
    VALIDITY = settings.CAS_TICKET_VALIDITY
    TIMEOUT = settings.CAS_TICKET_TIMEOUT
    PREFIX = settings.CAS_SERVICE_TICKET_PREFIX
    PREFIX = settings.CAS_PROXY_TICKET_PREFIX
    PREFIX = settings.CAS_PROXY_GRANTING_TICKET_PREFIX
    VALIDITY = settings.CAS_PGT_VALIDITY
            LAST_VERSION = utils.last_version() def json_encode(obj):
def context(params):
def json_response(request, data):
def import_attr(path):
def redirect_params(url_name, params=None):
def reverse_params(url_name, params=None, **kwargs):
def copy_params(get_or_post_params, ignore=None):
def set_cookie(response, key, value, max_age):
def get_current_url(request, ignore_params=None):
def update_url(url, params):
def unpack_nested_exception(error):
def _gen_ticket(prefix=None, lg=settings.CAS_TICKET_LEN):
def gen_lt():
def gen_st():
def gen_pt():
def gen_pgt():
def gen_pgtiou():
def gen_saml_id():
def get_tuple(nuplet, index, default=None):
def crypt_salt_is_valid(salt):
    def _raise_bad_scheme(cls, scheme, valid, msg):
    def _test_scheme(cls, scheme):
    def _test_scheme_salt(cls, scheme):
    def _test_scheme_nosalt(cls, scheme):
    def hash(cls, scheme, password, salt=None, charset="utf8"):
    def get_scheme(cls, hashed_passord):
    def get_salt(cls, hashed_passord):
def check_password(method, password, hashed_password, charset):
def decode_version(version):
def last_version():
def dictfetchall(cursor):
def logout_request(ticket):
def regexpr_validator(value):
        LAST_VERSION = last_version()
class LdapHashUserPassword(object):
    class BadScheme(ValueError):
    class BadHash(ValueError):
    class BadSalt(ValueError): def copy_form(form):
    """Copy form value into a dict"""
    params = {}
    for field in form:
        if field.value():
            params[field.name] = field.value()
        else:
            params[field.name] = ""
    return params def get_login_page_params(client=None):
    """Return a client and the POST params for the client to login"""
    if client is None:
        client = Client()
    response = client.get('/login')
    params = copy_form(response.context["form"])
    return client, params def get_auth_client(**update):
    """return a authenticated client"""
    client, params = get_login_page_params()
    params["username"] = settings.CAS_TEST_USER
    params["password"] = settings.CAS_TEST_PASSWORD
    params.update(update)

    response = client.post('/login', params)
    assert client.session.get("authenticated")

    if params.get("service"):
        return (client, response)
    else:
        return client def get_user_ticket_request(service):
    """Make an auth client to request a ticket for `service`, return the tuple (user, ticket)"""
    client = get_auth_client()
    response = client.get("/login", {"service": service})
    ticket_value = response['Location'].split('ticket=')[-1]
    user = models.User.objects.get(
        username=settings.CAS_TEST_USER,
        session_key=client.session.session_key
    )
    ticket = models.ServiceTicket.objects.get(value=ticket_value)
    return (user, ticket, client) def get_pgt():
    """return a dict contening a service, user and PGT ticket for this service"""
    (httpd, host, port) = HttpParamsHandler.run()[0:3]
    service = "http://%s:%s" % (host, port)

    (user, ticket) = get_user_ticket_request(service)[:2]

    client = Client()
    client.get('/serviceValidate', {'ticket': ticket.value, 'service': service, 'pgtUrl': service})
    params = httpd.PARAMS

    params["service"] = service
    params["user"] = user

    return params def get_proxy_ticket(service):
    """Return a ProxyTicket waiting for validation"""
    params = get_pgt()

    # get a proxy ticket
    client = Client()
    response = client.get('/proxy', {'pgt': params['pgtId'], 'targetService': service})
    root = etree.fromstring(response.content)
    proxy_ticket = root.xpath(
        "//cas:proxyTicket",
        namespaces={'cas': "http://www.yale.edu/tp/cas"}
    )
    proxy_ticket = proxy_ticket[0].text
    ticket = models.ProxyTicket.objects.get(value=proxy_ticket)
    return ticket def get_validated_ticket(service):
    """Return a tick that has being already validated. Used to test SLO"""
    (ticket, auth_client) = get_user_ticket_request(service)[1:3]

    client = Client()
    response = client.get('/validate', {'ticket': ticket.value, 'service': service})
    assert response.status_code == 200
    assert response.content == b'yes\ntest\n'

    ticket = models.ServiceTicket.objects.get(value=ticket.value)
    return (auth_client, ticket) class HttpParamsHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    """
        A simple http server that return 200 on GET or POST
        and store GET or POST parameters. Used in unit tests
    """

    def do_GET(self):
        """Called on a GET request on the BaseHTTPServer"""
        self.send_response(200)
        self.send_header(b"Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(b"ok")
        url = urlparse(self.path)
        params = dict(parse_qsl(url.query))
        self.server.PARAMS = params

    def do_POST(self):
        """Called on a POST request on the BaseHTTPServer"""
        ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
        if ctype == 'multipart/form-data':
            postvars = cgi.parse_multipart(self.rfile, pdict)
        elif ctype == 'application/x-www-form-urlencoded':
            length = int(self.headers.get('content-length'))
            postvars = parse_qs(self.rfile.read(length), keep_blank_values=1)
        else:
            postvars = {}
        self.server.PARAMS = postvars

    def log_message(self, *args):
        """silent any log message"""
        return

    @classmethod
    def run(cls, port=0):
        """Run a BaseHTTPServer using this class as handler"""
        server_class = BaseHTTPServer.HTTPServer
        httpd = server_class(("127.0.0.1", port), cls)
        (host, port) = httpd.socket.getsockname()

        def lauch():
            """routine to lauch in a background thread"""
            httpd.handle_request()
            httpd.server_close()

        httpd_thread = Thread(target=lauch)
        httpd_thread.daemon = True
        httpd_thread.start()
        return (httpd, host, port) class Http404Handler(HttpParamsHandler):
    """A simple http server that always return 404 not found. Used in unit tests"""
    def do_GET(self):
        """Called on a GET request on the BaseHTTPServer"""
        self.send_response(404)
        self.send_header(b"Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(b"error 404 not found")

    def do_POST(self):
        """Called on a POST request on the BaseHTTPServer"""
        return self.do_GET() class BaseServicePattern(object):
    """Mixing for setting up service pattern for testing"""
    @classmethod
    def setup_service_patterns(cls, proxy=False):
        """setting up service pattern"""
        # For general purpose testing
        cls.service = "https://www.example.com"
        cls.service_pattern = models.ServicePattern.objects.create(
            name="example",
            pattern=r"^https://www\.example\.com(/.*)?$",
            proxy=proxy,
        )
        models.ReplaceAttributName.objects.create(name="*", service_pattern=cls.service_pattern)

        # For testing the restrict_users attributes
        cls.service_restrict_user_fail = "https://restrict_user_fail.example.com"
        cls.service_pattern_restrict_user_fail = models.ServicePattern.objects.create(
            name="restrict_user_fail",
            pattern=r"^https://restrict_user_fail\.example\.com(/.*)?$",
            restrict_users=True,
            proxy=proxy,
        )
        cls.service_restrict_user_success = "https://restrict_user_success.example.com"
        cls.service_pattern_restrict_user_success = models.ServicePattern.objects.create(
            name="restrict_user_success",
            pattern=r"^https://restrict_user_success\.example\.com(/.*)?$",
            restrict_users=True,
            proxy=proxy,
        )
        models.Username.objects.create(
            value=settings.CAS_TEST_USER,
            service_pattern=cls.service_pattern_restrict_user_success
        )

        # For testing the user attributes filtering conditions
        cls.service_filter_fail = "https://filter_fail.example.com"
        cls.service_pattern_filter_fail = models.ServicePattern.objects.create(
            name="filter_fail",
            pattern=r"^https://filter_fail\.example\.com(/.*)?$",
            proxy=proxy,
        )
        models.FilterAttributValue.objects.create(
            attribut="right",
            pattern="^admin$",
            service_pattern=cls.service_pattern_filter_fail
        )
        cls.service_filter_fail_alt = "https://filter_fail_alt.example.com"
        cls.service_pattern_filter_fail_alt = models.ServicePattern.objects.create(
            name="filter_fail_alt",
            pattern=r"^https://filter_fail_alt\.example\.com(/.*)?$",
            proxy=proxy,
        )
        models.FilterAttributValue.objects.create(
            attribut="nom",
            pattern="^toto$",
            service_pattern=cls.service_pattern_filter_fail_alt
        )
        cls.service_filter_success = "https://filter_success.example.com"
        cls.service_pattern_filter_success = models.ServicePattern.objects.create(
            name="filter_success",
            pattern=r"^https://filter_success\.example\.com(/.*)?$",
            proxy=proxy,
        )
        models.FilterAttributValue.objects.create(
            attribut="email",
            pattern="^%s$" % re.escape(settings.CAS_TEST_ATTRIBUTES['email']),
            service_pattern=cls.service_pattern_filter_success
        )

        # For testing the user_field attributes
        cls.service_field_needed_fail = "https://field_needed_fail.example.com"
        cls.service_pattern_field_needed_fail = models.ServicePattern.objects.create(
            name="field_needed_fail",
            pattern=r"^https://field_needed_fail\.example\.com(/.*)?$",
            user_field="uid",
            proxy=proxy,
        )
        cls.service_field_needed_success = "https://field_needed_success.example.com"
        cls.service_pattern_field_needed_success = models.ServicePattern.objects.create(
            name="field_needed_success",
            pattern=r"^https://field_needed_success\.example\.com(/.*)?$",
            user_field="alias",
            proxy=proxy,
        )
        cls.service_field_needed_success_alt = "https://field_needed_success_alt.example.com"
        cls.service_pattern_field_needed_success = models.ServicePattern.objects.create(
            name="field_needed_success_alt",
            pattern=r"^https://field_needed_success_alt\.example\.com(/.*)?$",
            user_field="nom",
            proxy=proxy,
        ) class XmlContent(object):
    """Mixin for test on CAS XML responses"""
    def assert_error(self, response, code, text=None):
        """Assert a validation error"""
        self.assertEqual(response.status_code, 200)
        root = etree.fromstring(response.content)
        error = root.xpath(
            "//cas:authenticationFailure",
            namespaces={'cas': "http://www.yale.edu/tp/cas"}
        )
        self.assertEqual(len(error), 1)
        self.assertEqual(error[0].attrib['code'], code)
        if text is not None:
            self.assertEqual(error[0].text, text)

    def assert_success(self, response, username, original_attributes):
        """assert a ticket validation success"""
        self.assertEqual(response.status_code, 200)

        root = etree.fromstring(response.content)
        sucess = root.xpath(
            "//cas:authenticationSuccess",
            namespaces={'cas': "http://www.yale.edu/tp/cas"}
        )
        self.assertTrue(sucess)

        users = root.xpath("//cas:user", namespaces={'cas': "http://www.yale.edu/tp/cas"})
        self.assertEqual(len(users), 1)
        self.assertEqual(users[0].text, username)

        attributes = root.xpath(
            "//cas:attributes",
            namespaces={'cas': "http://www.yale.edu/tp/cas"}
        )
        self.assertEqual(len(attributes), 1)
        ignore_attrs = {
            "authenticationDate", "longTermAuthenticationRequestTokenUsed", "isFromNewLogin"
        }
        ignored_attrs = 0
        attrs1 = set()
        for attr in attributes[0]:
            name = attr.tag[len("http://www.yale.edu/tp/cas")+2:]
            if name not in ignore_attrs:
                attrs1.add((name, attr.text))
            else:
                ignored_attrs += 1

        attributes = root.xpath("//cas:attribute", namespaces={'cas': "http://www.yale.edu/tp/cas"})
        self.assertEqual(len(attributes), len(attrs1) + ignored_attrs)
        attrs2 = set()
        for attr in attributes:
            name = attr.attrib['name']
            if name not in ignore_attrs:
                attrs2.add((name, attr.attrib['value']))
        original = set()
        for key, value in original_attributes.items():
            if isinstance(value, list):
                for sub_value in value:
                    original.add((key, sub_value))
            else:
                original.add((key, value))
        self.assertEqual(attrs1, attrs2)
        self.assertEqual(attrs1, original)

        return root class CanLogin(object):
    """Assertion about login"""
    def assert_logged(
        self, client, response, warn=False,
        code=200, username=settings.CAS_TEST_USER
    ):
        """Assertions testing that client is well authenticated"""
        self.assertEqual(response.status_code, code)
        # this message is displayed to the user upon successful authentication
        self.assertIn(
            (
                b"You have successfully logged into "
                b"the Central Authentication Service"
            ),
            response.content
        )
        # these session variables a set if usccessfully authenticated
        self.assertEqual(client.session["username"], username)
        self.assertIs(client.session["warn"], warn)
        self.assertIs(client.session["authenticated"], True)

        # on successfull authentication, a corresponding user object is created
        self.assertTrue(
            models.User.objects.get(
                username=username,
                session_key=client.session.session_key
            )
        )

    def assert_login_failed(self, client, response, code=200):
        """Assertions testing a failed login attempt"""
        self.assertEqual(response.status_code, code)
        # this message is displayed to the user upon successful authentication, so it should not
        # appear
        self.assertNotIn(
            (
                b"You have successfully logged into "
                b"the Central Authentication Service"
            ),
            response.content
        )

        # if authentication has failed, these session variables should not be set
        self.assertTrue(client.session.get("username") is None)
        self.assertTrue(client.session.get("warn") is None)
        self.assertTrue(client.session.get("authenticated") is None)
from cas_server.default_settings import settings
from django.test import TestCase, Client
from django.test.utils import override_settings
from django.utils import timezone
from lxml import etree
from six.moves import range
from cas_server import models
from cas_server import utils
from cas_server.tests.utils import (
    copy_form,
    get_login_page_params,
    get_auth_client,
    get_user_ticket_request,
    get_pgt,
    get_proxy_ticket,
    get_validated_ticket,
    HttpParamsHandler,
    Http404Handler
)
from cas_server.tests.mixin import BaseServicePattern, XmlContent, CanLogin
import django
import random
import json
import mock
    def test_logout(self):
        """logout is idempotent"""
        # get a bare client
        client = Client()

        # call logout
        client.get("/logout")

        # we are still not logged
        self.assertFalse(client.session.get("username"))
        self.assertFalse(client.session.get("authenticated"))

    def test_logout_view(self):
        """test simple logout, logout only an user from one and only one sessions"""
        # get two authenticated client with the same test user (but two different sessions)
        client = get_auth_client()
        client2 = get_auth_client()

        # fetch login, the first client is well authenticated
        response = client.get("/login")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(
            (
                b"You have successfully logged into "
                b"the Central Authentication Service"
            ) in response.content
        )
        # and session variable are well
        self.assertTrue(client.session["username"] == settings.CAS_TEST_USER)
        self.assertTrue(client.session["authenticated"] is True)

        # call logout with the first client
        response = client.get("/logout")
        # the client is logged out
        self.assertEqual(response.status_code, 200)
        self.assertTrue(
            (
                b"You have successfully logged out from "
                b"the Central Authentication Service"
            ) in response.content
        )
        # and session variable a well cleaned
        self.assertFalse(client.session.get("username"))
        self.assertFalse(client.session.get("authenticated"))
        # client2 is still logged
        self.assertTrue(client2.session["username"] == settings.CAS_TEST_USER)
        self.assertTrue(client2.session["authenticated"] is True)

        response = client.get("/login")
        # fetch login, the second client is well authenticated
        self.assertEqual(response.status_code, 200)
        self.assertFalse(
            (
                b"You have successfully logged into "
                b"the Central Authentication Service"
            ) in response.content
        )

    def test_logout_from_all_session(self):
        """test logout from all my session"""
        # get two authenticated client with the same test user (but two different sessions)
        client = get_auth_client()
        client2 = get_auth_client()

        # call logout with the first client and ask to be logged out from all of this user sessions
        client.get("/logout?all=1")

        # both client are logged out
        self.assertFalse(client.session.get("username"))
        self.assertFalse(client.session.get("authenticated"))
        self.assertFalse(client2.session.get("username"))
        self.assertFalse(client2.session.get("authenticated"))

    def assert_redirect_to_service(self, client, response):
        """assert logout redirect to parameter"""
        # assert a redirection with a service
        self.assertEqual(response.status_code, 302)
        self.assertTrue(response.has_header("Location"))
        self.assertEqual(response["Location"], "https://www.example.com")

        response = client.get("/login")
        self.assertEqual(response.status_code, 200)
        # assert we are not longer logged in
        self.assertFalse(
            (
                b"You have successfully logged into "
                b"the Central Authentication Service"
            ) in response.content
        )

    def test_logout_view_url(self):
        """test logout redirect to url parameter"""
        # get a client that is authenticated
        client = get_auth_client()

        # logout with an url paramer
        response = client.get('/logout?url=https://www.example.com')
        # we are redirected to the addresse of the url parameter
        self.assert_redirect_to_service(client, response)

    def test_logout_view_service(self):
        """test logout redirect to service parameter"""
        # get a client that is authenticated
        client = get_auth_client()

        # logout with a service parameter
        response = client.get('/logout?service=https://www.example.com')
        # we are redirected to the addresse of the service parameter
        self.assert_redirect_to_service(client, response)

    def test_logout_slo(self):
        """test logout from a service with SLO support"""
        parameters = []

        # test normal SLO
        # setup a simple one request http server
        (httpd, host, port) = HttpParamsHandler.run()[0:3]
        # build a service url depending on which port the http server has binded
        service = "http://%s:%s" % (host, port)
        # get a ticket requested by client and being validated by the service

        (client, ticket) = get_validated_ticket(service)[:2]