class CASError(Exception):
    pass class InvalidTicket(CASError):
    pass class InvalidService(CASError):
    pass class NotSSOService(CASError):
    pass class InvalidTicketSpec(InvalidTicket):
    pass def createNonVerifyingHTTPClient(reactor, agent_kwds=None, **kwds):
    agent_kwds = normalizeDict_(agent_kwds)
    agent_kwds['contextFactory'] = NonVerifyingContextFactory()
    return HTTPClient(Agent(reactor, **agent_kwds), **kwds) def createVerifyingHTTPClient(
        reactor, 
        agent_kwds=None, 
        policy_factory=BrowserLikePolicyForHTTPS, 
        **kwds):
    agent_kwds = normalizeDict_(agent_kwds)
    agent_kwds['contextFactory'] = policy_factory()
    return HTTPClient(Agent(reactor, **agent_kwds), **kwds) class ITicketStore(Interface):
    
    lt_lifespan = Attribute('LT lifespan in seconds.')
    st_lifespan = Attribute('ST lifespan in seconds.')
    pt_lifespan = Attribute('PT lifespan in seconds.')
    tgt_lifespan = Attribute('TGC lifespan in seconds.')
    pgt_lifespan = Attribute('PGT lifespan in seconds.')
    ticket_size = Attribute('Size of ticket ID in characters.')
    
    isSSOService = Attribute(
                            'Function that accepts a service and returns True if' \
                            'the service may participate in SSO.')
    
    def mkLoginTicket(service):
        """
        Make a login ticket.
        
        @type service: C{string}
        @param service: The service URL.
        
        @rtpe: C{string}
        @return: ticket ID
        """
        
    def useLoginTicket(ticket, service):
        """
        Consume a login ticket.
        Returns a dict with key `service`.
        """
        
    def mkServiceTicket(service, tgt, primaryCredentials):
        """
        """
        
    def useServiceTicket(ticket, service, requirePrimaryCredentials=False):
        """
        """
        
    def mkProxyTicket(service, pgt):
        """
        """
        
    def useServiceOrProxyTicket(ticket, service, requirePrimaryCredentials=False):
        """
        """
        
    def mkProxyGrantingTicket(service, ticket, tgt, pgturl, proxy_chain=None):
        """
        """
        
    def mkTicketGrantingCookie(avatar_id):
        """
        """
        
    def useTicketGrantingCookie(tgt, service):
        """
        """
        
    def expireTGT(tgt):
        """
        """
        
    def register_ticket_expiration_callback(callback):
        """
        Register a function to be called when a ticket is expired.
        The function should take 3 arguments, (ticket, data, explicit).
        `ticket` is the ticket ID, `data` is a dict of the ticket data,
        and `explicit` is a boolean that indicates whether the ticket
        was explicitly expired (e.g. /logout, ST/PT validation) or
        implicitly expired (e.g. timeout or parent ticket expired).
        """ class ITicketStoreFactory(Interface):

    tag = Attribute('String used to identify the plugin factory.')
    opt_help = Attribute('String description of the plugin.')
    opt_usage = Attribute('String describes how to provide arguments for factory.')

    def generateTicketStore(argstring=""):
        """
        Create an object that implements ITicketStore.
        """ class IServiceManagerAcceptor(Interface):
    
    service_manager = Attribute('None or a reference to the current service manager plugin.') def are_urls_equal(url0, url1):
    p0 = urlparse(url0)
    p1 = urlparse(url1)
    scheme0 = p0.scheme.lower()
    scheme1 = p1.scheme.lower()
    if scheme0 != scheme1:
        return False
    netloc0 = normalize_netloc(scheme0, p0.netloc)
    netloc1 = normalize_netloc(scheme1, p1.netloc)
    if netloc0 != netloc1:
        return False
    if p0.path != p1.path:
        return False
    if p0.params != p1.params:
        return False
    if p0.fragment != p1.fragment:
        return False
    qs0 = set(parse_qsl(p0.query))
    qs1 = set(parse_qsl(p1.query))
    if qs0 != qs1:
        return False
    return True
import datetime
import random
import string
import sys
import uuid
import txcas.settings
import treq
from textwrap import dedent
from xml.sax.saxutils import escape as xml_escape
from txcas.exceptions import (
    CASError, InvalidTicket, InvalidService,
    NotSSOService, InvalidTicketSpec)
from txcas.http import (
    createNonVerifyingHTTPClient, createVerifyingHTTPClient)
from txcas.interface import (
    ITicketStore, ITicketStoreFactory, IServiceManagerAcceptor)
from txcas.urls import are_urls_equal
from twisted.internet import defer
from twisted.plugin import IPlugin
from twisted.python import log
from twisted.web.http_headers import Headers
from zope.interface import implements
            from twisted.internet import reactor

    def _generate(self, prefix):
        r = list(prefix)
        size = self.ticket_size
        while len(r) < size:
            r.append(random.choice(self.charset))
        return ''.join(r)

    def _mkTicket(self, prefix, data, timeout):
        """
        Create a ticket prefixed with C{prefix}

        The ticket will expire after my class' C{lifespan} seconds.

        @param prefix: String prefix for the token.
        @param data: Data associated with this ticket (which will be returned
            when L{_useTicket} is called).
        @param timeout: The time in seconds the ticket lasts.
        """
        ticket = self._generate(prefix)
        self._tickets[ticket] = data
        self.debug("Added ticket '%s' with data: %s" % (ticket, str(data)))
        if prefix == 'TGC-':
            dc = self.reactor.callLater(timeout, self.expireTGT, ticket)
        else:
            dc = self.reactor.callLater(timeout, self.expireTicket, ticket)
        self._delays[ticket] = (dc, timeout)
        return defer.succeed(ticket)

    def expireTicket(self, val):
        """
        This function should only be called when a ticket is expired via
        a timeout or indirectly (e.g. TGT expires so derived PGTs are expired).
        """
        try:
            data = self._tickets[val]
            del self._tickets[val]
            del self._delays[val]
            self._expire_callback(val, data, False)
        except KeyError:
            pass
        self.debug("Expired ticket '%s'." % val)

    def _useTicket(self, ticket, _consume=True):
        """
        Consume a ticket, producing the data that was associated with the ticket
        when it was created.

        @raise InvalidTicket: If the ticket doesn't exist or is no longer valid.
        """
        try:
            val = self._tickets[ticket]
            if _consume:
                del self._tickets[ticket]
                self._expire_callback(ticket, val, True)
                self.debug("Consumed ticket '%s'." % ticket)
            else:
                dc, timeout = self._delays[ticket]
                dc.reset(timeout)
            return defer.succeed(val)
        except KeyError:
            return defer.fail(InvalidTicket())
        except Exception as ex:
            log.err(ex)
            return defer.fail(InvalidTicket())

    def _informTGTOfService(self, st, service, tgt):
        """
        Record in the TGT that a service has requested an ST.
        """
        try:
            data = self._tickets[tgt]
        except KeyError:
            return defer.fail(InvalidTicket())
        services = data.setdefault('services', {})
        services[service] = st
        self.debug("Added service '%s' to TGT '%s' with ST '%s'." % (service, tgt, st))
        return st
        
    def _informTGTOfPGT(self, pgt, tgt):
        """
        Record in the TGT that a service has requested an ST.
        """
        if not pgt.startswith("PGT-"):
            return defer.fail(InvalidTicket("PGT '%s' is not valid." % pgt))
        if not tgt.startswith("TGC-"):
            return defer.fail(InvalidTicket("TGT '%s' is not valid." % tgt))
        try:
            data = self._tickets[tgt]
        except KeyError:
            return defer.fail(InvalidTicket())
        pgts = data.setdefault('pgts', set([]))
        pgts.add(pgt)
        self.debug("Added PGT '%s' to TGT '%s'." % (pgt, tgt))
        return pgt

    def mkLoginTicket(self, service):
        """
        Create a login ticket.
        """
        d = self._validService(service)
        def cb(_):
            return self._mkTicket('LT-', {
                'service': service,
            }, timeout=self.lt_lifespan)
        return d.addCallback(cb)

    def useLoginTicket(self, ticket, service):
        """
        Use a login ticket.
        """
        if not ticket.startswith("LT-"):
            return defer.fail(InvalidTicket("Login ticket '%s' is malformed." % ticket))

        def doit(_):
            d = self._useTicket(ticket)

            def cb(data):
                recorded_service = data['service']
                if not are_urls_equal(recorded_service, service):

                    return defer.fail(InvalidService(