/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

package pt.webdetails.browserid.spring;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpHost;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.json.JSONException;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.ui.AbstractProcessingFilter;
import org.springframework.util.Assert;

import pt.webdetails.browserid.BrowserIdResponse;
import pt.webdetails.browserid.BrowserIdVerifier;

/**
 * Spring security filter for BrowserID authentication.
 */
public class BrowserIdProcessingFilter extends AbstractProcessingFilter {

  private static final String DEFAULT_FILTER_PROCESS_URL = "/j_spring_security_check";
  private static final String DEFAULT_ASSERTION_PARAMETER = "assertion";
//  private static final String DEFAULT_AUDIENCE_PARAMETER = "audience";
  
  private String verificationServiceUrl; 
  private String assertionParameterName = DEFAULT_ASSERTION_PARAMETER;
//  private String audienceParameterName = DEFAULT_AUDIENCE_PARAMETER;
  private int order;
  
  public String getAssertionParameterName() {
    return assertionParameterName;
  }

  /**
   * 
   * @param assertionParameterName 
   */
  public void setAssertionParameterName(String assertionParameterName) {
    this.assertionParameterName = assertionParameterName;
  }

//  public String getAudienceParameterName() {
//    return audienceParameterName;
//  }
//
//  public void setAudienceParameterName(String audienceParameterName) {
//    this.audienceParameterName = audienceParameterName;
//  }

  public String getVerificationServiceUrl() {
    return verificationServiceUrl;
  }

  public void setVerificationServiceUrl(String verificationServiceUrl) {
    this.verificationServiceUrl = verificationServiceUrl;
  }

  
  @Override
  public int getOrder() {
    return order;
  }

  public void setOrder(int order) {
    this.order = order;
  }

  /**
   * 
   */
  @Override
  public Authentication attemptAuthentication(HttpServletRequest request) throws AuthenticationException {
    String browserIdAssertion = request.getParameter(getAssertionParameterName());
//    String assertionAudience = request.getParameter(getAudienceParameterName());
    
    if(browserIdAssertion != null) {
     
      BrowserIdVerifier verifier = new BrowserIdVerifier(getVerificationServiceUrl());
      BrowserIdResponse response = null;
      
      String audience  = request.getRequestURL().toString();
      try {
        URL url = new URL(audience);
        audience = url.getHost();
      } catch (MalformedURLException e) {
        throw new BrowserIdAuthenticationException("Malformed request URL", e);
      }
      
//      Assert.hasLength("Unable to determine hostname",audience);
//      if(!StringUtils.equals(audience, assertionAudience)){
//        logger.error("Server and client-side audience don't match");
//      }
      
      try {
        response = verifier.verify(browserIdAssertion, audience);
      } catch (HttpException e) {
        throw new BrowserIdAuthenticationException("Error calling verify service [" + verifier.getVerifyUrl() + "]", e);
      } catch (IOException e) {
        throw new BrowserIdAuthenticationException("Error calling verify service [" + verifier.getVerifyUrl() + "]", e);
      } catch (JSONException e){
        throw new BrowserIdAuthenticationException("Could not parse response from verify service [" + verifier.getVerifyUrl() + "]", e);
      }

      if(response != null){
        if(response.getStatus() == BrowserIdResponse.Status.OK){
          BrowserIdAuthenticationToken token = new BrowserIdAuthenticationToken(response, browserIdAssertion);
          //send to provider to get authorities
          return getAuthenticationManager().authenticate(token);
        }
        else {
          throw new BrowserIdAuthenticationException("BrowserID verification failed, reason: " + response.getReason());
        }
      }
      else throw new BrowserIdAuthenticationException("Verification yielded null response");
    }
    //may not be a BrowserID authentication
    return null;
  }

  @Override
  public String getDefaultFilterProcessesUrl() {
    return DEFAULT_FILTER_PROCESS_URL;
  }
  
  
  @Override
  public void afterPropertiesSet() throws Exception {
    super.afterPropertiesSet();
    //request parameters
    Assert.hasLength(getAssertionParameterName(), "assertionParameterName cannot be empty.");
//    Assert.hasLength(getAudienceParameterName(), "audienceParameterName cannot be empty.");
    
    //check URL
    Assert.hasLength(getVerificationServiceUrl());
    try{
      HttpHost host = new HttpHost(new URI(getVerificationServiceUrl(), false));
      Assert.isTrue(host.getProtocol().isSecure(), "verificationServiceUrl does not use a secure protocol");
    } catch (URIException e){
      throw new IllegalArgumentException("verificationServiceUrl is not a valid URI",e);
    }
  }
  
  @Override
  protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {
    if (authResult instanceof BrowserIdAuthenticationToken) {
      logger.debug(((BrowserIdAuthenticationToken) authResult));
      
    }

  }
}

@@ -607,32 +607,76 @@
 t.URIException;%0A
+import org.apache.commons.lang.StringUtils;%0A
 import org.json.
@@ -1313,81 +1313,8 @@
 n%22;%0A
-//  private static final String DEFAULT_AUDIENCE_PARAMETER = %22audience%22;%0A
   %0A 
@@ -1425,18 +1425,16 @@
 AMETER;%0A
-//
   privat
@@ -1446,59 +1446,18 @@
 ing 
-audienceParameterName = DEFAULT_AUDIENCE_PARAMETER;
+hostname;%0A
 %0A  p
@@ -1757,237 +1757,8 @@
  %7D%0A%0A
-//  public String getAudienceParameterName() %7B%0A//    return audienceParameterName;%0A//  %7D%0A//%0A//  public void setAudienceParameterName(String audienceParameterName) %7B%0A//    this.audienceParameterName = audienceParameterName;%0A//  %7D%0A
 %0A  p
@@ -1975,20 +1975,163 @@
 rl;%0A  %7D%0A
+  %0A  %0A  public String getHostname() %7B%0A    return hostname;%0A  %7D%0A%0A  public void setHostname(String hostname) %7B%0A    this.hostname = hostname;%0A  %7D
 %0A  %0A
+%0A
   @Overr
@@ -2791,24 +2791,105 @@
 toString();%0A
+      String referer = request.getHeader(%22Referer%22);%0A      //strip to host names%0A
       try %7B%0A
@@ -2900,17 +2900,25 @@
     URL 
-u
+audienceU
 rl = new
@@ -2952,17 +2952,108 @@
 ience = 
-u
+audienceUrl.getHost();%0A        URL refererUrl = new URL(referer);%0A        referer = refererU
 rl.getHo
@@ -3156,33 +3156,212 @@
 on(%22
-Malformed request URL%22, e
+Request contains malformed URL%22, e);%0A      %7D%0A      %0A      if(!StringUtils.equals(audience, audience) %7C%7C StringUtils.isEmpty(referer))%7B%0A        throw new BrowserIdAuthenticationException(%22Referer mismatch%22
 );%0A 

Check Referer before verification