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 static final long serialVersionUID = 1L;
066
067    private String var; // tag attribute
068
069    private int scope; // tag attribute
070
071    private String casLogin, casValidate, service; // from children
072
073    private List<String> acceptedProxies; // from children
074
075    private HttpServletRequest request;
076
077    private HttpServletResponse response;
078
079    // *********************************************************************
080    // Tag logic
081
082    @Override
083    public int doStartTag() throws JspException {
084        // retrieve and save the request and response objects
085        request = (HttpServletRequest) pageContext.getRequest();
086        response = (HttpServletResponse) pageContext.getResponse();
087
088        // reset invocation-specific state
089        casLogin = null;
090        casValidate = null;
091        try {
092            service = Util.getService(request,
093                    pageContext.getServletContext().getInitParameter("edu.yale.its.tp.cas.serverName"));
094        } catch (ServletException ex) {
095            throw new JspException(ex);
096        }
097        acceptedProxies = new ArrayList<>();
098        return EVAL_BODY_INCLUDE;
099    }
100
101    @Override
102    public int doEndTag() throws JspTagException {
103        try {
104            // if our attribute's already present, don't do anything
105            if (pageContext.getAttribute(var, scope) != null)
106                return EVAL_PAGE;
107
108            // otherwise, we need to authenticate via CAS
109            String ticket = request.getParameter("ticket");
110
111            // no ticket? redirect...
112            if (ticket == null || ticket.equals("")) {
113                if (casLogin == null)
114                    throw new JspTagException("for pages that expect to be called without 'ticket' parameter, "
115                            + "cas:auth must have a cas:loginUrl subtag");
116                response.sendRedirect(casLogin + "?service=" + service);
117                return SKIP_PAGE;
118            }
119
120            // Yay, ticket! Validate it.
121            String netid = getAuthenticatedNetid(ticket);
122            if (netid == null)
123                throw new JspTagException("Unexpected CAS authentication error");
124
125            // Store the authenticate user in the id/scope attribute
126            pageContext.setAttribute(var, netid, scope);
127
128            return EVAL_PAGE;
129
130        } catch (IOException ex) {
131            throw new JspTagException(ex.getMessage());
132        } catch (SAXException ex) {
133            throw new JspTagException(ex.getMessage());
134        } catch (ParserConfigurationException ex) {
135            throw new JspTagException(ex.getMessage());
136        }
137    }
138
139    // *********************************************************************
140    // Attribute accessors
141
142    public void setVar(String var) {
143        this.var = var;
144    }
145
146    public void setScope(String scope) {
147        if (scope.equals("page"))
148            this.scope = PageContext.PAGE_SCOPE;
149        else if (scope.equals("request"))
150            this.scope = PageContext.REQUEST_SCOPE;
151        else if (scope.equals("session"))
152            this.scope = PageContext.SESSION_SCOPE;
153        else if (scope.equals("application"))
154            this.scope = PageContext.APPLICATION_SCOPE;
155        else
156            throw new IllegalArgumentException("invalid scope");
157    }
158
159    // *********************************************************************
160    // Accessors for child tags
161
162    public void setCasLogin(String url) {
163        casLogin = url;
164    }
165
166    public void setCasValidate(String url) {
167        casValidate = url;
168    }
169
170    public void addAuthorizedProxy(String proxyId) {
171        acceptedProxies.add(proxyId);
172    }
173
174    public void setService(String service) {
175        this.service = service;
176    }
177
178    // *********************************************************************
179    // Constructor and lifecycle management
180
181    public AuthTag() {
182        super();
183        init();
184    }
185
186    // Releases any resources we may have (or inherit)
187    @Override
188    public void release() {
189        super.release();
190        init();
191    }
192
193    // clears any internal state we might have
194    private void init() {
195        var = null;
196        scope = PageContext.PAGE_SCOPE;
197        casLogin = null;
198        casValidate = null;
199        acceptedProxies = null;
200    }
201
202    // *********************************************************************
203    // Utility methods
204
205    private String getAuthenticatedNetid(String ticket) throws ParserConfigurationException, SAXException, IOException,
206            JspTagException {
207        ProxyTicketValidator pv = new ProxyTicketValidator();
208        pv.setCasValidateUrl(casValidate);
209        pv.setServiceTicket(ticket);
210        pv.setService(service);
211        pv.validate();
212        if (!pv.isAuthenticationSuccesful())
213            throw new JspTagException("CAS authentication error: " + pv.getErrorCode());
214        if (pv.getProxyList().size() != 0) {
215            // ticket was proxied
216            if (acceptedProxies.size() == 0)
217                throw new JspTagException("this page does not accept proxied tickets");
218            else if (!acceptedProxies.contains(pv.getProxyList().get(0)))
219                throw new JspTagException("unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'");
220        }
221        return pv.getUser();
222    }
223}