001/*
002 *  (C) Copyright 2000-2003 Yale University. All rights reserved.
003 *
004 *  THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED
005 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
006 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY
007 *  DISCLAIMED. IN NO EVENT SHALL YALE UNIVERSITY OR ITS EMPLOYEES BE
008 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
009 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED, THE COSTS OF
010 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR
011 *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
012 *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
013 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
014 *  SOFTWARE, EVEN IF ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH
015 *  DAMAGE.
016 *
017 *  Redistribution and use of this software in source or binary forms,
018 *  with or without modification, are permitted, provided that the
019 *  following conditions are met:
020 *
021 *  1. Any redistribution must include the above copyright notice and
022 *  disclaimer and this list of conditions in any related documentation
023 *  and, if feasible, in the redistributed software.
024 *
025 *  2. Any redistribution must include the acknowledgment, "This product
026 *  includes software developed by Yale University," in any related
027 *  documentation and, if feasible, in the redistributed software.
028 *
029 *  3. The names "Yale" and "Yale University" must not be used to endorse
030 *  or promote products derived from this software.
031 */
032
033package edu.yale.its.tp.cas.client.taglib;
034
035import java.io.*;
036import java.util.*;
037import javax.servlet.*;
038import javax.servlet.http.*;
039import javax.servlet.jsp.*;
040import javax.servlet.jsp.tagext.*;
041import edu.yale.its.tp.cas.client.*;
042import javax.xml.parsers.ParserConfigurationException;
043import org.xml.sax.SAXException;
044
045/**
046 * <p>
047 * Authentication tag for use with the Yale Central Authentication Service.
048 * </p>
049 * <p>
050 * Typical usage involves placing the tag at the top of the page. The tag checks to determine if the attribute
051 * referenced by id/scope exists; if it does, the tag has no runtime effect. If the attribute does not exist, however, a
052 * CAS authentication is necessary: if no ticket is present, we redirect to CAS, and if a ticket is present, we validate
053 * it. Upon successful CAS authentication (either by a pre-existing attribute or through CAS directly), we store the
054 * NetID in the attribute referenced by id/scope.
055 * </p>
056 *
057 * @author Shawn Bayern
058 * @author Drew Mazurek
059 */
060public class AuthTag extends TagSupport {
061
062    // *********************************************************************
063    // Internal state
064
065    private String var; // tag attribute
066
067    private int scope; // tag attribute
068
069    private String casLogin, casValidate, service; // from children
070
071    private List acceptedProxies; // from children
072
073    private HttpServletRequest request;
074
075    private HttpServletResponse response;
076
077    // *********************************************************************
078    // Tag logic
079
080    public int doStartTag() throws JspException {
081        // retrieve and save the request and response objects
082        request = (HttpServletRequest) pageContext.getRequest();
083        response = (HttpServletResponse) pageContext.getResponse();
084
085        // reset invocation-specific state
086        casLogin = null;
087        casValidate = null;
088        try {
089            service = Util.getService(request,
090                    (String) pageContext.getServletContext().getInitParameter("edu.yale.its.tp.cas.serverName"));
091        } catch (ServletException ex) {
092            throw new JspException(ex);
093        }
094        acceptedProxies = new ArrayList();
095        return EVAL_BODY_INCLUDE;
096    }
097
098    public int doEndTag() throws JspTagException {
099        try {
100            // if our attribute's already present, don't do anything
101            if (pageContext.getAttribute(var, scope) != null)
102                return EVAL_PAGE;
103
104            // otherwise, we need to authenticate via CAS
105            String ticket = request.getParameter("ticket");
106
107            // no ticket? redirect...
108            if (ticket == null || ticket.equals("")) {
109                if (casLogin == null)
110                    throw new JspTagException("for pages that expect to be called without 'ticket' parameter, "
111                            + "cas:auth must have a cas:loginUrl subtag");
112                response.sendRedirect(casLogin + "?service=" + service);
113                return SKIP_PAGE;
114            }
115
116            // Yay, ticket! Validate it.
117            String netid = getAuthenticatedNetid(ticket);
118            if (netid == null)
119                throw new JspTagException("Unexpected CAS authentication error");
120
121            // Store the authenticate user in the id/scope attribute
122            pageContext.setAttribute(var, netid, scope);
123
124            return EVAL_PAGE;
125
126        } catch (IOException ex) {
127            throw new JspTagException(ex.getMessage());
128        } catch (SAXException ex) {
129            throw new JspTagException(ex.getMessage());
130        } catch (ParserConfigurationException ex) {
131            throw new JspTagException(ex.getMessage());
132        }
133    }
134
135    // *********************************************************************
136    // Attribute accessors
137
138    public void setVar(String var) {
139        this.var = var;
140    }
141
142    public void setScope(String scope) {
143        if (scope.equals("page"))
144            this.scope = PageContext.PAGE_SCOPE;
145        else if (scope.equals("request"))
146            this.scope = PageContext.REQUEST_SCOPE;
147        else if (scope.equals("session"))
148            this.scope = PageContext.SESSION_SCOPE;
149        else if (scope.equals("application"))
150            this.scope = PageContext.APPLICATION_SCOPE;
151        else
152            throw new IllegalArgumentException("invalid scope");
153    }
154
155    // *********************************************************************
156    // Accessors for child tags
157
158    public void setCasLogin(String url) {
159        casLogin = url;
160    }
161
162    public void setCasValidate(String url) {
163        casValidate = url;
164    }
165
166    public void addAuthorizedProxy(String proxyId) {
167        acceptedProxies.add(proxyId);
168    }
169
170    public void setService(String service) {
171        this.service = service;
172    }
173
174    // *********************************************************************
175    // Constructor and lifecycle management
176
177    public AuthTag() {
178        super();
179        init();
180    }
181
182    // Releases any resources we may have (or inherit)
183    public void release() {
184        super.release();
185        init();
186    }
187
188    // clears any internal state we might have
189    private void init() {
190        var = null;
191        scope = PageContext.PAGE_SCOPE;
192        casLogin = null;
193        casValidate = null;
194        acceptedProxies = null;
195    }
196
197    // *********************************************************************
198    // Utility methods
199
200    private String getAuthenticatedNetid(String ticket) throws ParserConfigurationException, SAXException, IOException,
201            JspTagException {
202        ProxyTicketValidator pv = new ProxyTicketValidator();
203        pv.setCasValidateUrl(casValidate);
204        pv.setServiceTicket(ticket);
205        pv.setService(service);
206        pv.validate();
207        if (!pv.isAuthenticationSuccesful())
208            throw new JspTagException("CAS authentication error: " + pv.getErrorCode());
209        if (pv.getProxyList().size() != 0) {
210            // ticket was proxied
211            if (acceptedProxies.size() == 0)
212                throw new JspTagException("this page does not accept proxied tickets");
213            else if (!acceptedProxies.contains(pv.getProxyList().get(0)))
214                throw new JspTagException("unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'");
215        }
216        return pv.getUser();
217    }
218}