package org.vaadin.leif.persona;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import javax.net.ssl.HttpsURLConnection;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.vaadin.annotations.JavaScript;
import com.vaadin.server.AbstractJavaScriptExtension;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.Resource;
import com.vaadin.shared.JavaScriptExtensionState;
import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.ui.JavaScriptFunction;
import com.vaadin.ui.UI;
/**
*
*/
@JavaScript({ "https://login.persona.org/include.orig.js", "personaConnector.js" })
public class Persona extends AbstractJavaScriptExtension {
private LinkedHashSet<PersonaListener> listeners = new LinkedHashSet<PersonaListener>();
private final String audience;
public static class PersonaState extends JavaScriptExtensionState {
public String backgroundColor;
public String privacyPolicy;
public String returnTo;
public String siteLogo;
public String siteName;
public String termsOfService;
public String domId = "persona" + new Random().nextInt();
}
public interface PersonaRpc extends ServerRpc {
public void onlogin(String assertion);
public void onlogout();
public void oncancel();
}
@Override
public PersonaState getState() {
return (PersonaState) super.getState();
}
public Persona(UI ui, String audience) {
this.audience = audience;
callFunction("watch");
registerRpc(new PersonaRpc() {
@Override
public void onlogout() {
fireLogoutEvent();
}
@Override
public void onlogin(String assertion) {
checkAssertion(assertion);
}
@Override
public void oncancel() {
fireCancelEvent();
}
});
addFunction("error", new JavaScriptFunction() {
@Override
public void call(JSONArray arguments) throws JSONException {
String message = null;
if (arguments.length() > 0) {
message = arguments.getString(0).toString();
}
fireError(new PersonaErrorEvent(Persona.this, message));
}
});
extend(ui);
}
public Resource getLoginResource() {
return new ExternalResource("javascript:window['" + getState().domId
+ "'].request()");
}
public Resource getLogoutResource() {
return new ExternalResource("javascript:window['" + getState().domId
+ "'].logout()");
}
protected void checkAssertion(String assertion) {
try {
JSONObject result = fetchVerification(assertion);
System.out.println(result);
String status = result.getString("status");
if (status.equals("okay")) {
PersonaLoginEvent event = new PersonaLoginEvent(this,
result.getString("email"),
result.getString("audience"), new Date(
result.getLong("expires")),
result.getString("issuer"));
fireLoginEvent(event);
} else {
fireError(new PersonaErrorEvent(this,
result.getString("reason")));
}
} catch (Exception e) {
fireError(new PersonaErrorEvent(this, e));
}
}
private JSONObject fetchVerification(String assertion) throws Exception {
HttpsURLConnection connection = null;
OutputStreamWriter writer = null;
InputStream inputStream = null;
try {
connection = (HttpsURLConnection) new URL(
"https://verifier.login.persona.org/verify").openConnection();
connection.setDoOutput(true);
writer = new OutputStreamWriter(connection.getOutputStream());
writer.write("assertion=" + URLEncoder.encode(assertion, "UTF-8")
+ "&audience=" + URLEncoder.encode(audience, "UTF-8"));
writer.flush();
writer.close();
if (connection.getResponseCode() != 200) {
System.out
.println(IOUtils.toString(connection.getErrorStream()));
}
inputStream = connection.getInputStream();
String response = IOUtils.toString(inputStream);
return new JSONObject(response);
} finally {
IOUtils.closeQuietly(writer);
IOUtils.closeQuietly(inputStream);
if (connection != null) {
connection.disconnect();
}
}
}
public void addPersonaListener(PersonaListener listener) {
listeners.add(listener);
}
public void removePersonaListener(PersonaListener listener) {
listeners.remove(listener);
}
protected void fireLoginEvent(PersonaLoginEvent event) {
for (PersonaListener l : getListeners()) {
l.onLogin(event);
}
}
protected void fireError(PersonaErrorEvent event) {
for (PersonaListener l : getListeners()) {
l.onError(event);
}
}
protected void fireLogoutEvent() {
PersonaEvent event = new PersonaEvent(this);
for (PersonaListener l : getListeners()) {
l.onLogout(event);
}
}
protected void fireCancelEvent() {
PersonaEvent event = new PersonaEvent(this);
for (PersonaListener l : getListeners()) {
l.onCancel(event);
}
}
private List<PersonaListener> getListeners() {
return new ArrayList<PersonaListener>(listeners);
}
/**
* Absolute path or URL to the web site's privacy policy. If provided, then
* {@link #setTermsOfService(String)} must also be provided. When both
* termsOfService and privacyPolicy are given, the login dialog informs the
* user that, by continuing,
* "you confirm that you accept this site's Terms of Use and Privacy Policy."
* The dialog provides links to the the respective policies. If
* termsOfService is not provided, this parameter has no effect.
*
* @param privacyPolcyUrl
*/
public void setPrivacyPolicy(String privacyPolcyUrl) {
getState().privacyPolicy = privacyPolcyUrl;
}
public String getPrivacyPolicy() {
return getState().privacyPolicy;
}
/**
* Absolute path or URL to the web site's terms of service. If provided,
* then {@link #setPrivacyPolicy(String)} must also be provided. When both
* termsOfService and privacyPolicy are given, the login dialog informs the
* user that, by continuing,
* "you confirm that you accept this site's Terms of Use and Privacy Policy."
* The dialog provides links to the the respective policies. If
* privacyPolicy is not provided, this parameter has no effect.
*
* @param termsOfServiceUrl
*/
public void setTermsOfService(String termsOfServiceUrl) {
getState().termsOfService = termsOfServiceUrl;
}
public String getTermsOfService() {
return getState().termsOfService;
}
/**
* Plain text name of your site to show in the login dialog. Unicode and
* whitespace are allowed, but markup is not.
*
* @param siteName
*/
public void setSiteName(String siteName) {
getState().siteName = siteName;
}
public String getSiteName() {
return getState().siteName;
}
/**
* Absolute path to an image to show in the login dialog. The path must
* begin with '/' and must be available over SSL. Larger images will be
* scaled down to fit within 100x100 pixels.
*
* @param siteLogoUrl
*/
public void setSiteLogo(String siteLogoUrl) {
getState().siteLogo = siteLogoUrl;
}
public String getSiteLogo() {
return getState().siteLogo;
}
/**
* Absolute path to send new users to after they've completed email
* verification for the first time. The path must begin with '/'. This
* parameter only affects users who are certified by Mozilla's fallback
* Identity Provider.
*
*
* @param returnToUrl
*/
public void setReturnTo(String returnToUrl) {
getState().returnTo = returnToUrl;
}
public String getReturnTo() {
return getState().returnTo;
}
public void setBackgroundColor(String backgroundColor) {
getState().backgroundColor = backgroundColor;
}
public String getBackgroundColor() {
return getState().backgroundColor;
}
}