federate.py 5.7 KB
Newer Older
Valentin Samir's avatar
Valentin Samir committed
1
# -*- coding: utf-8 -*-
Valentin Samir's avatar
Valentin Samir committed
2 3 4 5 6 7 8 9 10
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
# more details.
#
# You should have received a copy of the GNU General Public License version 3
# along with this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
Valentin Samir's avatar
Valentin Samir committed
11
# (c) 2016 Valentin Samir
Valentin Samir's avatar
Valentin Samir committed
12
"""federated mode helper classes"""
13
from .default_settings import SessionStore
14
from django.db import IntegrityError
Valentin Samir's avatar
Valentin Samir committed
15 16

from .cas import CASClient
17 18
from .models import FederatedUser, FederateSLO, User

19
import logging
20
from six.moves import urllib
21

22
#: logger facility
23 24
logger = logging.getLogger(__name__)

Valentin Samir's avatar
Valentin Samir committed
25 26

class CASFederateValidateUser(object):
27 28 29 30 31 32 33 34
    """
        Class CAS client used to authenticate the user again a CAS provider

        :param cas_server.models.FederatedIendityProvider provider: The provider to use for
            authenticate the user.
        :param unicode service_url: The service url to transmit to the ``provider``.
    """
    #: the provider returned username
Valentin Samir's avatar
Valentin Samir committed
35
    username = None
36
    #: the provider returned attributes
Valentin Samir's avatar
Valentin Samir committed
37
    attributs = {}
38
    #: the CAS client instance
Valentin Samir's avatar
Valentin Samir committed
39
    client = None
Valentin Samir's avatar
Valentin Samir committed
40 41 42 43
    #: the provider returned username this the provider suffix appended
    federated_username = None
    #: the identity provider
    provider = None
Valentin Samir's avatar
Valentin Samir committed
44 45 46

    def __init__(self, provider, service_url):
        self.provider = provider
47 48 49 50 51 52
        self.client = CASClient(
            service_url=service_url,
            version=provider.cas_protocol_version,
            server_url=provider.server_url,
            renew=False,
        )
Valentin Samir's avatar
Valentin Samir committed
53 54

    def get_login_url(self):
55 56 57 58
        """
            :return: the CAS provider login url
            :rtype: unicode
        """
59
        return self.client.get_login_url()
Valentin Samir's avatar
Valentin Samir committed
60 61

    def get_logout_url(self, redirect_url=None):
62 63 64 65 66 67
        """
            :param redirect_url: The url to redirect to after logout from the provider, if provided.
            :type redirect_url: :obj:`unicode` or :obj:`NoneType<types.NoneType>`
            :return: the CAS provider logout url
            :rtype: unicode
        """
68
        return self.client.get_logout_url(redirect_url)
Valentin Samir's avatar
Valentin Samir committed
69 70

    def verify_ticket(self, ticket):
71 72 73 74 75 76 77 78 79
        """
            test ``ticket`` agains the CAS provider, if valid, create a
            :class:`FederatedUser<cas_server.models.FederatedUser>` matching provider returned
            username and attributes.

            :param unicode ticket: The ticket to validate against the provider CAS
            :return: ``True`` if the validation succeed, else ``False``.
            :rtype: bool
        """
80 81 82
        try:
            username, attributs = self.client.verify_ticket(ticket)[:2]
        except urllib.error.URLError:
Valentin Samir's avatar
Valentin Samir committed
83 84
            return False
        if username is not None:
85 86
            if attributs is None:
                attributs = {}
Valentin Samir's avatar
Valentin Samir committed
87 88 89
            attributs["provider"] = self.provider
            self.username = username
            self.attributs = attributs
90 91 92 93 94 95 96
            user = FederatedUser.objects.update_or_create(
                username=username,
                provider=self.provider,
                defaults=dict(attributs=attributs, ticket=ticket)
            )[0]
            user.save()
            self.federated_username = user.federated_username
Valentin Samir's avatar
Valentin Samir committed
97 98 99
            return True
        else:
            return False
100

Valentin Samir's avatar
Valentin Samir committed
101 102
    @staticmethod
    def register_slo(username, session_key, ticket):
103 104 105 106 107 108 109 110 111
        """
            association a ``ticket`` with a (``username``, ``session_key``) for processing later SLO
            request by creating a :class:`cas_server.models.FederateSLO` object.

            :param unicode username: A logged user username, with the ``@`` component.
            :param unicode session_key: A logged user session_key matching ``username``.
            :param unicode ticket: A ticket used to authentication ``username`` for the session
                ``session_key``.
        """
112 113 114 115 116 117 118 119
        try:
            FederateSLO.objects.create(
                username=username,
                session_key=session_key,
                ticket=ticket
            )
        except IntegrityError:  # pragma: no cover (ignore if the FederateSLO already exists)
            pass
120 121

    def clean_sessions(self, logout_request):
122 123 124 125 126 127 128 129
        """
            process a SLO request: Search for ticket values in ``logout_request``. For each
            ticket value matching a :class:`cas_server.models.FederateSLO`, disconnect the
            corresponding user.

            :param unicode logout_request: An XML document contening one or more Single Log Out
                requests.
        """
130
        try:
131 132
            slos = self.client.get_saml_slos(logout_request) or []
        except NameError:  # pragma: no cover (should not happen)
Valentin Samir's avatar
Valentin Samir committed
133 134
            slos = []
        for slo in slos:
135
            for federate_slo in FederateSLO.objects.filter(ticket=slo.text):
136 137 138 139 140 141
                logger.info(
                    "Got an SLO requests for ticket %s, logging out user %s" % (
                        federate_slo.username,
                        federate_slo.ticket
                    )
                )
142 143 144 145 146 147 148 149 150 151 152 153
                session = SessionStore(session_key=federate_slo.session_key)
                session.flush()
                try:
                    user = User.objects.get(
                        username=federate_slo.username,
                        session_key=federate_slo.session_key
                    )
                    user.logout()
                    user.delete()
                except User.DoesNotExist:  # pragma: no cover (should not happen)
                    pass
                federate_slo.delete()