VIEW_LOGIN = 0 VIEW_LOGIN_SUCCESS = 1 VIEW_LOGOUT = 2 VIEW_INVALID_SERVICE = 3 VIEW_ERROR_5XX = 4 VIEW_NOT_FOUND = 5 class CASError(Exception):
    pass class InvalidTicket(CASError):
    pass class InvalidService(CASError):
    pass class CookieAuthFailed(CASError):
    pass class NotSSOService(CASError):
    pass class NotHTTPSError(CASError):
    pass class InvalidProxyCallback(CASError):
    pass class ViewNotImplementedError(CASError):
    pass class BadRequestError(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 ICASUser(Interface):

    username = Attribute('String username')
    attribs = Attribute('List of (attribute, value) tuples.') 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 ICASAuthWhen(Interface):

    auth_when = Attribute("One of 'cred_acceptor', 'cred_requestor'.") def http_status_filter(response, allowed, ex, msg=None, include_resp_text=True):
    """
    Checks the response status and determines if it is in one of the
    allowed ranges.  If not, it raises `ex()`.

    `ex` is a callable that results in an Exception to be raised,
        (typically an exception class).
    `allowed` is a sequence of (start, end) valid status ranges.
    """
    code = response.code
    in_range = False
    for start_range, end_range in allowed:
        if code >= start_range and code <= end_range:
            in_range = True
            break
    if not in_range:

        def raise_error(body, ex):
            ex_msg = []
            if msg is not None:
                ex_msg.append(msg)
            if include_resp_text:
                ex_msg.append(body)
            text = '\n'.join(ex_msg)
            if text != "":
                raise ex(text)
            else:
                raise ex()

        # Need to still deliver the response body or Twisted make
        # hang.
        d = treq.content(response)
        d.addCallback(raise_error, ex)
        return d
    return response
import cgi
import string
import sys
import urlparse
import treq
import twisted.web.http
import werkzeug.exceptions
from textwrap import dedent
from urllib import urlencode
from xml.sax.saxutils import escape as xml_escape
from txcas.constants import VIEW_LOGIN, VIEW_LOGIN_SUCCESS, VIEW_LOGOUT, \
                        VIEW_INVALID_SERVICE, VIEW_ERROR_5XX, VIEW_NOT_FOUND
from txcas.exceptions import CASError, InvalidTicket, InvalidService, \
                        CookieAuthFailed, NotSSOService, NotHTTPSError, \
                        InvalidProxyCallback, ViewNotImplementedError, \
                        BadRequestError, InvalidTicketSpec
from txcas.http import (
    createNonVerifyingHTTPClient, createVerifyingHTTPClient)
from txcas.interface import ICASUser, ITicketStore, ICASAuthWhen
from txcas.utils import http_status_filter
from klein import Klein
from twisted.cred.portal import Portal, IRealm
from twisted.cred.credentials import UsernamePassword, IUsernamePassword
from twisted.cred.error import Unauthorized, UnauthorizedLogin, \
                            UnhandledCredentials
from twisted.internet import defer
from twisted.python import log
from twisted.web.static import File
from zope.interface import implements
            from twisted.internet import reactor
        def _sendTicketAndIou(pgt_info, pgturl, httpClientFactory):
            pgt = pgt_info['pgt']
            iou = pgt_info['iou']

            def iou_cb(resp_text, pgtiou):
                """
                Return the iou parameter.
                """
                return pgtiou
                
            q = {'pgtId': pgt, 'pgtIou': iou}
            log_cas_event("Sending pgtId and pgtIou to client.", [
                ('pgturl', pgturl),
                ('pgtIou', iou),
                ('pgtId', pgt),])
            httpClient = httpClientFactory(self.reactor)
            d = httpClient.get(pgturl, params=q, timeout=30)
            d.addCallback(http_status_filter, [(200, 200)], InvalidProxyCallback)
            d.addCallback(treq.content)
            d.addCallback(iou_cb, iou)
            return d
            
        def _package_result(iou, data):
            data['iou'] = iou
            return data
        
        if self.validate_pgturl:
            httpClientFactory = createVerifyingHTTPClient
            p = urlparse.urlparse(pgturl)
            if p.scheme.lower() != "https":
                raise NotHTTPSError("The pgtUrl '%s' is not HTTPS.")
        else:
            httpClientFactory = createNonVerifyingHTTPClient
        httpClient = httpClientFactory(self.reactor)
        d = httpClient.get(pgturl)
        d.addCallback(http_status_filter, [(200, 200)], InvalidProxyCallback)
        d.addCallback(treq.content)
        d.addCallback(_mkPGT)
        d.addCallback(_sendTicketAndIou, pgturl, httpClientFactory)
        d.addCallback(_package_result, data)
        return d

    @app.route('/proxy', methods=['GET'])
    def proxy_GET(self, request):
        log_http_event(request)
        try:
            pgt = get_single_param(request, 'pgt')
            targetService = get_single_param(request, 'targetService')
        except BadRequestError as ex:
            log.err(ex)
            request.setResponseCode(400)
            return dedent("""\
                <cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
                    <cas:proxyFailure code="INVALID_REQUEST">
                        Error requesting proxy ticket.       
                        &apos;pgt&apos; and &apos;targetService&apos; parameters are both required exactly once.
                    </cas:proxyFailure>
                </cas:serviceResponse>
                """)
        # Validate the PGT and get the PT

        def successResult(ticket, targetService, pgt, request):
            log_cas_event("Issued proxy ticket", [
                ('client_ip', request.getClientIP()),
                ('ticket', ticket),
                ('targetService', targetService),
                ('PGT', pgt),])
            return dedent("""\
                <cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
                    <cas:proxySuccess>
                        <cas:proxyTicket>%(ticket)s</cas:proxyTicket>
                    </cas:proxySuccess>
                </cas:serviceResponse>
                """) % {'ticket': xml_escape(ticket)}

        def failureResult(err, targetService, pgt, request):
            log_cas_event("Failed to issue proxy ticket", [
                ('client_ip', request.getClientIP()),
                ('targetService', targetService),
                ('PGT', pgt),])
            if not err.check(InvalidTicket, InvalidService):
                log.err(err)
            code = "INTERNAL_ERROR"
            msg = "An internal error occured."
            if err.check(InvalidTicket):
                code = "BAD_PGT"
                msg = "PGT '%s' is invalid." % pgt
            elif err.check(InvalidService):
                code = "INVALID_SERVICE"
                msg = "Target service is not authorized."
            return dedent("""\
                <cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
                    <cas:proxyFailure code="%(code)s">
                        %(msg)s
                    </cas:proxyFailure>
                </cas:serviceResponse>
                """) % {
                    'code': xml_escape(code),
                    'msg': xml_escape(msg),
                }

        d = defer.maybeDeferred(self.ticket_store.mkProxyTicket, targetService, pgt)
        d.addCallback(successResult, targetService, pgt, request)
        d.addErrback(failureResult, targetService, pgt, request)
        return d

    @app.route('/static/', methods=['GET'], branch=True)
    def static_GET(self, request):
        static = self._static
        if static is None:
            log.msg('[ERROR] type="not_found" client_ip="%s" uri="%s"' % (
                        request.getClientIP(), request.uri))
            return self._get_page_view(VIEW_NOT_FOUND, request)
        else:
            return File(static)

    @app.handle_errors(werkzeug.exceptions.NotFound)
    def error_handler(self, request, failure):
        log.msg('[ERROR] type="not_found" client_ip="%s" uri="%s"' % (
                    request.getClientIP(), request.uri))

        return self._get_page_view(VIEW_NOT_FOUND, request)