/*
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jasig.cas.login;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * Common testcases for /serviceValidate and /proxyValidate.
 * @version $Revision$ $Date$
 * @since 3.0
 */
public abstract class AbstractCas2ValidateCompatibilityTests extends AbstractCompatibilityTests {

	/**
	 * The name of the compatibility test configuration property the value of which
	 * will be the URL of a proxy ticket receptor to which the CAS server under test
	 * can send proxy granting tickets.  E.g., an instance of the Java CAS Client
	 * ProxyTicketReceptor servlet deployed behind SSL with an SSL cert trusted by the
	 * CAS server under test.  We use this to test specifying a proxy callback URL
	 * and thereby obtaining a pgtiou.
	 */
	public static final String PROXY_RECEPTOR_URL_PROPERTY = "pgtreceptor.url";
	
    public AbstractCas2ValidateCompatibilityTests() throws IOException {
        super();
    }

    public AbstractCas2ValidateCompatibilityTests(String name) throws IOException {
        super(name);
    }
    
    /**
     * Returns /serviceValidate in the case of /serviceValidate, 
     * and /proxyValidate in the case of /proxyValidate.
     * Concrete subclasses implement this method to configure the common
     * tests defined here.
     * @return
     */
    protected abstract String getValidationPath();
    
    protected final String getProxyCallbackUrl() {
    	return getProperties().getProperty(PROXY_RECEPTOR_URL_PROPERTY);
    }
    
    public void testNoParameters() {
        beginAt(getValidationPath());
        assertTextPresent("cas:authenticationFailure");
        
        // TODO: actually test the validation response XML.
    }
    
    public void testBadServiceTicket() throws UnsupportedEncodingException {
        final String service = getServiceUrl();
        beginAt(getValidationPath() + "?service=" + URLEncoder.encode(service, "UTF-8") + "&ticket=test");
        
        assertTextPresent("cas:authenticationFailure");
        
        // TODO: do more to test that the response is actually XML, etc. etc.
    }
    
    /**
     * Test validation of a valid service ticket and that service tickets are
     * not multiply validatable.
     * @throws IOException
     */
    public void testProperCredentialsAndServiceTicket() throws IOException {
        final String service = getServiceUrl();
        String encodedService = URLEncoder.encode(service, "UTF-8");
        beginAt("/login?service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it
        
        beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket);
        
        assertTextPresent("cas:authenticationSuccess");
        
        // this assertion may be too strict.  How does whitespace work here?
        assertTextPresent("<cas:user>" + getUsername() + "</cas:user>");
        
        // TODO: do more to test that the response is actually XML, etc. etc.
        
        // let's validate it again and ensure that we cannot again validate
        // the ticket
        
        beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket);
        assertTextPresent("cas:authenticationFailure");
        
        // TODO: do more to test that the response is actually XML, etc. etc.
        
    }
    
    /**
     * Test that renew=true, when specified both at login and ticket validation, 
     * validation succeeds.
     * @throws IOException
     */
    public void testRenew() throws IOException {
        final String service = getServiceUrl();
        String encodedService = URLEncoder.encode(service, "UTF-8");
        beginAt("/login?renew=true&service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it
        
        beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket);
        
        assertTextPresent("cas:authenticationSuccess");
    }
    
    /**
     * Test a couple renew=true logins...
     * @throws IOException
     */
    public void testMultipleRenew() throws IOException {
        final String service = getServiceUrl();
        String encodedService = URLEncoder.encode(service, "UTF-8");
        
        beginAt("/login?renew=true&service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it
        
        beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket + "&renew=true");
        
        assertTextPresent("cas:authenticationSuccess");
        
        // now let's do it again
        
        beginAt("/login?renew=true&service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it
        
        beginAt(getValidationPath() + "?service=" + encodedService + "&" + "ticket=" + serviceTicket + "&renew=true");
        
        assertTextPresent("<cas:authenticationSuccess>");
        
    }
    
    /**
     * Test that renew=true, when specified only at ticket validation, 
     * validation succeeds if username, password were presented at login even
     * though renew wasn't set then.
     * @throws IOException
     */
    public void testAccidentalRenew() throws IOException {
        final String service = getServiceUrl();
        String encodedService = URLEncoder.encode(service, "UTF-8");
        beginAt("/login?service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it
        
        beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket);
        
        assertTextPresent("<cas:authenticationSuccess>");
        assertTextPresent("<cas:user>" + getUsername() + "</cas:user>");
    }
    
    /**
     * Test that renew at ticket validation blocks validation of a ticket
     * vended via SSO.
     * @throws IOException
     */
    public void testRenewBlocksSsoValidation() throws IOException {
    	
    	// initial authentication
        final String firstService = getServiceUrl();
        final String encodedFirstService = URLEncoder.encode(firstService, "UTF-8");
        beginAt("/login?service=" + encodedFirstService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        String firstServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // that established SSO.  Now let's get another ticket via SSO
        
        final String secondService= "http://www.uportal.org/";
        final String encodedSecondService = URLEncoder.encode(secondService, "UTF-8");
        
        beginAt("/login?service=" + encodedSecondService);
        
        // read the service ticket
        
        String secondServiceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // let's validate the second (non-renew) ticket.
        
        beginAt(getValidationPath() + "?renew=true&service=" + encodedSecondService + "&ticket=" + secondServiceTicket);
        
        assertTextPresent("cas:authenticationFailure");
        
        // TODO: test the authentication failure response in more detail
        
        assertTextNotPresent("<cas:user>");
        
        // however, we can validate the first ticket with renew=true.
        
        beginAt(getValidationPath() + "?renew=true&service=" + encodedFirstService + "&ticket=" + firstServiceTicket);
        
        assertTextPresent("cas:authenticationSuccess");
        assertTextPresent("<cas:user>" + getUsername() + "</cas:user>");
        // TODO: assert more about the response
        
    }
    
    /**
     * Test best-effort ticket validation when a specified proxy callback handler
     * doesn't really exist.
     * @throws IOException
     */
    public void testBrokenProxyCallbackUrl() throws IOException {
    	
        final String service = getServiceUrl();
        String encodedService = URLEncoder.encode(service, "UTF-8");
        beginAt("/login?service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it, specifying a bogus pgt callback
        
        String encodedProxyCallbackUrl = URLEncoder.encode("https://secure.its.yale.edu/cas/noexist", "UTF-8");
        
        beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket + "&pgtUrl=" + encodedProxyCallbackUrl);
        
        assertTextPresent("<cas:authenticationSuccess>");
        assertTextPresent("<cas:user>" + getUsername() + "</cas:user>");
        
        // no pgtiou because failure in sending pgt to specified receptor URL.
        assertTextNotPresent("<cas:pgtiou>");
    	
    }
    
    public void testPgtAcquisition() throws IOException {
    	
        final String service = getServiceUrl();
        String encodedService = URLEncoder.encode(service, "UTF-8");
        beginAt("/login?service=" + encodedService);
        setFormElement("username", getUsername());
        setFormElement("password", getGoodPassword());
        submit();
        
        // read the service ticket
        
        String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
        
        // great, now we have a ticket
        
        // let's validate it, specifying a bogus pgt callback
        
        String encodedProxyCallbackUrl = URLEncoder.encode(getProxyCallbackUrl(), "UTF-8");
        
        beginAt(getValidationPath() + "?renew=true&service=" + encodedService + "&" + "ticket=" + serviceTicket + "&pgtUrl=" + encodedProxyCallbackUrl);
        
        assertTextPresent("<cas:authenticationSuccess>");
        assertTextPresent("<cas:user>" + getUsername() + "</cas:user>");
        // pgtiou because success in sending pgt
        assertTextPresent("<cas:proxyGrantingTicket>");
    	
    }
    
    /**
     * Test for JIRA issue CAS-224.
     * 1. /cas/login?service=foo&renew=true
     * 2. log in
     * 3. /serviceValidate?ticket=[ticket]&service=foo&renew=true 
     * 
     * Issue was that validation fails whereas it should succeed.
     * 
     * This testcase is almost certainly redundant, but it's explicitly here
     * to cover this issue.
     * @throws IOException
     */
    public void test224() throws IOException {
    	
    	 final String service = getServiceUrl();
         String encodedService = URLEncoder.encode(service, "UTF-8");
         
         beginAt("/login?service=" + encodedService + "&renew=true");
         setFormElement("username", getUsername());
         setFormElement("password", getGoodPassword());
         submit();
         
         // read the service ticket
         
         String serviceTicket = LoginHelper.serviceTicketFromResponse(getDialog().getResponse());
         
         // great, now we have a ticket
         
         // let's validate it
         assertNotNull(serviceTicket);
         
         beginAt(getValidationPath() + "?ticket=" + serviceTicket + "&service=" + encodedService + "&renew=true");
         
         assertTextPresent("cas:authenticationSuccess");
         
    }
    
}
