diff options
35 files changed, 1977 insertions, 0 deletions
diff --git a/java/vaadin-u2f/build.xml b/java/vaadin-u2f/build.xml new file mode 100644 index 0000000..bcac04b --- /dev/null +++ b/java/vaadin-u2f/build.xml @@ -0,0 +1,286 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns:ivy="antlib:org.apache.ivy.ant" + name="My Vaadin Hello World" + basedir="." + default="compile.all"> + + <target name="configure"> + <property file="${basedir}/src/main/resources/Application.properties"/> + + <property name="main.dir" value="${basedir}/src/main" /> + <property name="run.dir" value="${basedir}/src/run" /> + <property name="build.dir" value="${basedir}/build"/> + <property name="dist.dir" value="${basedir}/dist"/> + + <property name="src.dir" value="${main.dir}/java" /> + <property name="web.dir" value="${main.dir}/WebContent"/> + <property name="resources.dir" value="${main.dir}/resources"/> + <property name="classes.dir" value="${build.dir}/WEB-INF/classes"/> + <property name="lib.dir" value="${build.dir}/WEB-INF/lib"/> + + <property name="war.name" value="${app.name}-${app.version}.war"/> + <property name="jar.name" value="${app.name}-${app.version}.jar"/> + <property name="run.name" value="ch.asynk.Main"/> + + <property name="compile.debug" value="true"/> + <property name="compile.deprecation" value="false"/> + <property name="compile.optimize" value="true"/> + + <property file="${basedir}/tomcat.properties"/> + <path id="cp.tomcat"> + <fileset dir="${catalina.home}/bin"> + <include name="*.jar"/> + </fileset> + <fileset dir="${catalina.home}/lib"> + <include name="*.jar"/> + </fileset> + </path> + <taskdef classpathref="cp.tomcat" resource="org/apache/catalina/ant/catalina.tasks" /> + <path id="cp.build"> + <dirset dir="${classes.dir}" /> + </path> + </target> + + + <target name="resolve" description="retrieve dependencies with Ivy"> + <ivy:resolve file="ivy.xml" log="download-only"/> + <ivy:cachepath pathid="ivy.deps.server-side" conf="server-side" /> + <ivy:cachepath pathid="ivy.deps.themes" conf="themes" /> + <ivy:cachepath pathid="ivy.deps.widgetsets" conf="widgetsets" /> + <ivy:cachepath pathid="ivy.deps.run" conf="run" /> + <ivy:cachefileset setid="ivy.deps.run.fileset" conf="run" /> + <ivy:cachefileset setid="ivy.deps.server-side.fileset" conf="server-side"/> + </target> + + <target name="ivy-report" depends="configure,resolve"> + <ivy:report conf="server-side" organisation="ch.asynk" module="${app.name}"/> + </target> + + <target name="clean" depends="configure" description="clean project"> + <delete dir="${build.dir}" /> + <delete dir="${dist.dir}" /> + <delete file="build.log" /> + <delete file="test.log" /> + <delete file="ivy-report.css" /> + <delete> + <fileset dir="${basedir}" includes="ch.asynk-${app.name}-*"/> + <fileset dir="${basedir}" includes="**/*.cache"/> + </delete> + </target> + + <target name="ctags" depends="configure"> + <exec executable="ctags" failonerror="false"> + <arg value="-R"/> + <arg value="--language-force=java"/> + <arg value="-f.tags"/> + <arg value="${src.dir}"/> + </exec> + </target> + + <target name="-compile.theme" > + <property name="theme.dir" value="${build.dir}/VAADIN/themes/${theme}" /> + <delete dir="${theme.dir}"/> + <mkdir dir="${theme.dir}"/> + <java classname="com.vaadin.sass.SassCompiler" failonerror="yes" fork="true"> + <classpath refid="ivy.deps.themes"/> + <jvmarg value="-Djava.awt.headless=true"/> + <arg value="-ignore-warnings:true"/> + <arg value="-compress:true"/> + <arg value="-minify:true"/> + <arg value="${web.dir}/VAADIN/themes/${theme}/styles.scss"/> + <arg value="${theme.dir}/styles.css"/> + </java> + </target> + + <target name="compile.themes" description="compile application's themes" depends="configure,resolve"> + <antcall target="-compile.theme" inheritRefs="true"> + <param name="theme" value="mytheme"/> + </antcall> + </target> + + <target name="-compile.widgetset"> + <property name="widgetset.dir" value="${build.dir}/VAADIN/widgetsets/${widgetset}" /> + <delete dir="${widgetset.dir}"/> + <mkdir dir="${widgetset.dir}"/> + <java classname="com.google.gwt.dev.Compiler" failonerror="yes" fork="true"> + <classpath refid="ivy.deps.widgetsets"/> + <classpath> + <dirset dir="${web.dir}/VAADIN/widgetsets"/> + </classpath> + <jvmarg value="-Djava.awt.headless=true"/> + <arg value="-war"/> + <arg value="${build.dir}/VAADIN/widgetsets"/> + <arg value="${widgetset}"/> + <arg value="-logLevel"/> + <arg value="ERROR"/> + <arg value="-strict"/> + <arg value="-optimize"/> + <arg value="9"/> + </java> + </target> + + <target name="compile.widgetsets" description="compile application's widgetsets" depends="configure,resolve"> + <antcall target="-compile.widgetset" inheritRefs="true"> + <param name="widgetset" value="mywidgetset"/> + </antcall> + </target> + + <target name="compile.app" description="compile server side components" depends="configure,resolve,ctags"> + <record name="build.log" loglevel="verbose" action="start" /> + <mkdir dir="${classes.dir}" /> + <javac srcdir="${main.dir}" + destdir="${classes.dir}" + debug="${compile.debug}" + deprecation="${compile.deprecation}" + optimize="${compile.optimize}" + includeantruntime="false"> + <classpath refid="cp.tomcat"/> + <classpath refid="ivy.deps.server-side"/> + <compilerarg value="-Xlint:all"/> + <compilerarg value="-Xlint:-path"/> + <compilerarg value="-Xlint:-processing"/> + <compilerarg value="-Xmaxerrs"/> + <compilerarg value="10"/> + </javac> + <copy todir="${classes.dir}"> + <fileset dir="${src.dir}"> + <include name="**/*.js"/> + </fileset> + </copy> + </target> + + <target name="compile.all" description="compile everything" depends="compile.app,compile.themes,compile.widgetsets"/> + + <target name="war" description="build war package" depends="configure,resolve"> + <mkdir dir="${lib.dir}"/> + <copy todir="${lib.dir}" flatten="true"> + <fileset refid="ivy.deps.server-side.fileset"/> + </copy> + <delete> + <fileset dir="${lib.dir}"> + <include name="**/*-sources.jar"/> + <include name="**/*-javadoc.jar"/> + </fileset> + </delete> + + <delete file="${dist.dir}/${war.name}"/> + <war destfile="${dist.dir}/${war.name}" + webxml="${web.dir}/WEB-INF/web.xml"> + <zipfileset dir="${resources.dir}" prefix="WEB-INF/classes"/> + <classes dir="${classes.dir}" includes="**" /> + <zipfileset dir="${build.dir}/VAADIN/themes" prefix="VAADIN/themes"/> + <zipfileset dir="${build.dir}/VAADIN/widgetsets" prefix="VAADIN/widgetsets"/> + <fileset dir="${web.dir}"> + <patternset> + <include name="*.html" /> + <include name="VAADIN/widgetsets/**/*" /> + <include name="VAADIN/themes/**/fonts/**/*" /> + <include name="VAADIN/themes/**/icons/**/*" /> + <include name="VAADIN/themes/**/images/**/*" /> + <include name="WEB-INF/lib/*" /> + <include name="WEB-INF/*.xml" /> + </patternset> + </fileset> + <zipfileset dir="${lib.dir}" prefix="WEB-INF/lib"/> + </war> + </target> + + <target name="compile.run" depends="configure,resolve"> + <mkdir dir="${build.dir}/run" /> + <javac srcdir="${run.dir}" + destdir="${build.dir}/run" + debug="${compile.debug}" + deprecation="${compile.deprecation}" + optimize="${compile.optimize}" + includeantruntime="false"> + <classpath refid="cp.build"/> + <classpath refid="ivy.deps.run"/> + <compilerarg value="-Xlint:all"/> + <compilerarg value="-Xlint:-path"/> + <compilerarg value="-Xlint:-processing"/> + <compilerarg value="-Xmaxerrs"/> + <compilerarg value="10"/> + </javac> + </target> + + <target name="run" description="run jetty in source tree" depends="configure,resolve"> + <java classname="${run.name}" fork="true"> + <classpath> + <dirset dir="${build.dir}/run" /> + <dirset dir="${resources.dir}" /> + <dirset dir="${run.dir}/resources" /> + <dirset dir="${build.dir}" /> + </classpath> + <classpath refid="cp.build"/> + <classpath refid="ivy.deps.run"/> + <jvmarg value="-DTEST=1"/> + </java> + </target> + + <target name="-jar.deps.check" unless="-jar.deps.exists"> + <available property="-jar.deps.exists" file="dist/deps.jar"/> + </target> + + <target name="-jar.deps" unless="-jar.deps.exists" depends="configure,resolve"> + <jar jarfile="dist/deps.jar"> + <zipgroupfileset refid="ivy.deps.run.fileset"/> + </jar> + </target> + + <target name="jar" description="build runnable jar" depends="-jar.deps.check,-jar.deps"> + <delete file="${dist.dir}/${jar.name}"/> + <jar jarfile="${dist.dir}/${jar.name}"> + <manifest> + <attribute name="Main-Class" value="${run.name}"/> + <attribute name="Class-Path" value="."/> + </manifest> + <fileset dir="${resources.dir}"/> + <fileset dir="${run.dir}/resources"/> + <fileset dir="${classes.dir}"/> + <fileset dir="${build.dir}/run"/> + <zipfileset dir="${build.dir}/VAADIN/themes" prefix="VAADIN/themes"/> + <zipfileset dir="${build.dir}/VAADIN/widgetsets" prefix="VAADIN/widgetsets"/> + <fileset dir="${web.dir}"> + <patternset> + <include name="WEB-INF/*.xml" /> + <include name="WEB-INF/lib/*" /> + <include name="*.html" /> + <include name="VAADIN/widgetsets/**/*" /> + <include name="VAADIN/themes/**/fonts/**/*" /> + <include name="VAADIN/themes/**/icons/**/*" /> + <include name="VAADIN/themes/**/images/**/*" /> + </patternset> + </fileset> + <zipfileset src="dist/deps.jar" excludes="META-INF/*"/> + </jar> + <echo message="$ java -DWEBROOT=/tmp -DTEST=1 -jar ${jar.name}"/> + </target> + + <target name="run.jar" description="run jetty from application jar" depends="configure"> + <java jar="${dist.dir}/${jar.name}" fork="true"> + <jvmarg value="-DTEST=1"/> + </java> + </target> + + <!-- tomcat specific targets --> + + <target name="remove" depends="configure" description="remove application on servlet container"> + <undeploy url="${manager.url}" + username="${manager.username}" + password="${manager.password}" + path="${app.path}" + failonerror="false"/> + </target> + + + <target name="install" depends="war,remove" description="install application to servlet container"> + <copy todir="/tmp" file="${dist.dir}/${war.name}"/> + <deploy url="${manager.url}" + username="${manager.username}" + password="${manager.password}" + path="${app.path}" + localWar="file://tmp/${war.name}"/> + <delete file="/tmp/${war.name}"/> + </target> + +</project> diff --git a/java/vaadin-u2f/ivy.xml b/java/vaadin-u2f/ivy.xml new file mode 100644 index 0000000..aabd97a --- /dev/null +++ b/java/vaadin-u2f/ivy.xml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<!DOCTYPE ivy-module [ +<!ENTITY vaadin.version "8.1.5"> +]> +<ivy-module version="2.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd"> + + <info organisation="ch.asynk" module="VaadinHelloWorld" /> + + <configurations> + <conf name="server-side" description="only needed for server side classes compilation"/> + <conf name="themes" extends="server-side" description="needed for themes compilation"/> + <conf name="widgetsets" extends="server-side" description="needed for widgetsets compilation"/> + <conf name="run" extends="server-side" description="to run locally using jetty"/> + </configurations> + + <dependencies defaultconf="server-side" defaultconfmapping="server-side->default"> + <dependency org="com.vaadin" name="vaadin-server" rev="&vaadin.version;"/> + <dependency org="com.vaadin" name="vaadin-themes" rev="&vaadin.version;"/> + <dependency org="com.vaadin" name="vaadin-client-compiled" rev="&vaadin.version;"/> + <dependency org="com.vaadin" name="vaadin-client-compiler" rev="&vaadin.version;" conf="widgetsets->default"/> + <dependency org="ch.qos.logback" name="logback-classic" rev="1.2.1"/> + <dependency org="org.codehaus.janino" name="janino" rev="3.0.6"/> + <dependency org="com.yahoo.platform.yui" name="yuicompressor" rev="2.4.8" conf="themes->default"/> + <dependency org="org.eclipse.jetty" name="jetty-webapp" rev="9.4.2.v20170220" conf="run->default"/> + <dependency org="com.yubico" name="u2flib-server-core" rev="0.16.0"/> + </dependencies> +</ivy-module> diff --git a/java/vaadin-u2f/ivysettings.xml b/java/vaadin-u2f/ivysettings.xml new file mode 100644 index 0000000..86ff132 --- /dev/null +++ b/java/vaadin-u2f/ivysettings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ivysettings> + <settings defaultResolver="default" /> + <!-- <caches defaultCacheDir="${basedir}/lib" /> --> + <!-- <caches default="mine" defaultCacheDir="${basedir}/ivy"> --> + <!-- <cache name="mine" basedir="${basedir}/ivy" /> --> + <!-- </caches> --> + <resolvers> + <chain name="default"> + <ibiblio name="maven2" m2compatible="true"/> + <ibiblio name="maven" m2compatible="true" usepoms="false"/> + <ibiblio name="vaadin-addons" usepoms="true" m2compatible="true" + root="http://maven.vaadin.com/vaadin-addons" /> + <!-- <ibiblio name="vaadin-prereleases" usepoms="true" m2compatible="true" --> + <!-- root="http://maven.vaadin.com/vaadin-prereleases" /> --> + <filesystem name="local"> + <ivy pattern="${user.home}/ivy-local/[revision]/[module]/ivy-[revision].xml"/> + <artifact pattern="${user.home}/ivy-local/[revision]/[module]/[module]-[revision](-[classifier]).[ext]"/> + </filesystem> + </chain> + </resolvers> +</ivysettings> diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/help.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/help.png Binary files differnew file mode 100644 index 0000000..e91d95a --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/help.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/home.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/home.png Binary files differnew file mode 100644 index 0000000..7362322 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/home.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.png Binary files differnew file mode 100644 index 0000000..7e1709f --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_down.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.png Binary files differnew file mode 100644 index 0000000..cc442d5 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/thumb_up.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.png Binary files differnew file mode 100644 index 0000000..52d0761 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-128.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.png Binary files differnew file mode 100644 index 0000000..bd5594a --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-24.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.png Binary files differnew file mode 100644 index 0000000..78a15c4 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-32.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.png Binary files differnew file mode 100644 index 0000000..c89c931 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-64.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.png Binary files differnew file mode 100644 index 0000000..7823ff2 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-lock.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.png Binary files differnew file mode 100644 index 0000000..3641de1 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo-48.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.png b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.png Binary files differnew file mode 100644 index 0000000..a18234b --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/icons/u2f-logo.png diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/layouts/About.html b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/layouts/About.html new file mode 100644 index 0000000..7020e1d --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/layouts/About.html @@ -0,0 +1,10 @@ +<div style='margin:10px'> +<h1>HelloWorld 0.0.0</h1> + +<p>powered by <ul> +<li><a target="_blank" href="https://vaadin.com">vaadin</a> java framework for building modern web applications</li> +<li><a target="_blank" href="http://debian.org">debian</a> a free operating system (OS) for your computer.</li> +</ul></p> +<p>written by <a target="_blank" href="mailto:jeremy@asynk.ch">Jérémy Zurcher</a> +as a part of <a target="_blank" href="https://github.com/jeremyz/share">share</a></p>. +</div> diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/mytheme.scss b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/mytheme.scss new file mode 100644 index 0000000..ad29293 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/mytheme.scss @@ -0,0 +1,17 @@ + +@import "../valo/valo.scss"; + +@mixin mytheme { + @include valo; + .v-app { background-color: #e6e6e9; } + .mybg { background-color: #e6e6e6; } + .v-verticallayout-margin-top { padding-top: $v-layout-margin-top; } + .v-verticallayout-margin-right { padding-right: $v-layout-margin-top; } + .v-menubar { + .v-menubar-menuitem-menuRight { + position: absolute; + right: 10px; + top: 0; + } + } +} diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/styles.scss b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/styles.scss new file mode 100644 index 0000000..447ce98 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/themes/mytheme/styles.scss @@ -0,0 +1,4 @@ +@import "mytheme.scss"; +.mytheme { + @include mytheme; +} diff --git a/java/vaadin-u2f/src/main/WebContent/VAADIN/widgetsets/mywidgetset.gwt.xml b/java/vaadin-u2f/src/main/WebContent/VAADIN/widgetsets/mywidgetset.gwt.xml new file mode 100644 index 0000000..3c11cb9 --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/VAADIN/widgetsets/mywidgetset.gwt.xml @@ -0,0 +1,3 @@ +<module> + <inherits name="com.vaadin.DefaultWidgetSet" /> +</module> diff --git a/java/vaadin-u2f/src/main/WebContent/WEB-INF/web.xml b/java/vaadin-u2f/src/main/WebContent/WEB-INF/web.xml new file mode 100644 index 0000000..6e03c3d --- /dev/null +++ b/java/vaadin-u2f/src/main/WebContent/WEB-INF/web.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<web-app id="HelloWorld" version="3.0" + xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> + + <display-name>Hello World Vaadin</display-name> + <description> + Hello World in Vaadin + </description> + + <context-param> + <description>Vaadin production mode</description> + <param-name>productionMode</param-name> + <param-value>false</param-value> + </context-param> + + <session-config> + <session-timeout>60</session-timeout> + <!-- <tracking-mode>COOKIE</tracking-mode> --> + </session-config> + + + <!-- <welcome-file-list> --> + <!-- <welcome-file>index.html</welcome-file> --> + <!-- <welcome-file>index.htm</welcome-file> --> + <!-- <welcome-file>index.jsp</welcome-file> --> + <!-- <welcome-file>default.html</welcome-file> --> + <!-- <welcome-file>default.htm</welcome-file> --> + <!-- <welcome-file>default.jsp</welcome-file> --> + <!-- </welcome-file-list> --> +</web-app> diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/Daddy.java b/java/vaadin-u2f/src/main/java/ch/asynk/Daddy.java new file mode 100644 index 0000000..3f279fd --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/Daddy.java @@ -0,0 +1,25 @@ +package ch.asynk; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Daddy +{ + private static final long serialVersionUID = 1L; + + public static final String SESSION_STATUS = "SESSION_STATUS"; + + private static final Logger logger; + + static { + logger = LoggerFactory.getLogger(Daddy.class); + warn("beware Daddy is up"); + } + + public static void trace(String msg) { logger.trace(msg); } + public static void debug(String msg) { logger.debug(msg); } + public static void warn(String msg) { logger.warn(msg); } + public static void info(String msg) { logger.info(msg); } + public static void error(String msg) { logger.error(msg); } + public static void error(String msg, Exception e) { logger.error(String.format("%s : %s", msg, e.getMessage())); } +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/HelloWorldServlet.java b/java/vaadin-u2f/src/main/java/ch/asynk/HelloWorldServlet.java new file mode 100644 index 0000000..d0e8374 --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/HelloWorldServlet.java @@ -0,0 +1,38 @@ +package ch.asynk; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.ServletException; + +import com.vaadin.annotations.VaadinServletConfiguration; +import com.vaadin.server.ServiceException; +import com.vaadin.server.SessionInitEvent; +import com.vaadin.server.SessionInitListener; +import com.vaadin.server.SessionDestroyEvent; +import com.vaadin.server.SessionDestroyListener; +import com.vaadin.server.VaadinServlet; + +@WebServlet(value = "/*", asyncSupported = true) +@VaadinServletConfiguration(productionMode = false, ui = ch.asynk.HelloWorldUI.class, closeIdleSessions = true) +public class HelloWorldServlet extends VaadinServlet implements SessionInitListener, SessionDestroyListener +{ + private static final long serialVersionUID = 511085337415583793L; + @Override + protected void servletInitialized() throws ServletException { + super.servletInitialized(); + getService().addSessionInitListener(this); + getService().addSessionDestroyListener(this); + } + + @Override + public void sessionInit(SessionInitEvent event) throws ServiceException + { + event.getSession().setLocale(new java.util.Locale("fr", "CH")); + event.getSession().setAttribute(Daddy.SESSION_STATUS, "unknown"); + System.err.println("sessionInit"); + } + + @Override + public void sessionDestroy(SessionDestroyEvent event) { + System.err.println("sessionDestroy"); + } +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/HelloWorldUI.java b/java/vaadin-u2f/src/main/java/ch/asynk/HelloWorldUI.java new file mode 100644 index 0000000..1f293df --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/HelloWorldUI.java @@ -0,0 +1,135 @@ +package ch.asynk; + +import com.vaadin.annotations.PreserveOnRefresh; +import com.vaadin.annotations.Theme; +import com.vaadin.annotations.Title; +import com.vaadin.annotations.Widgetset; +import com.vaadin.navigator.Navigator; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Button; +import com.vaadin.ui.Component; +import com.vaadin.ui.CustomLayout; +import com.vaadin.ui.HorizontalSplitPanel; +import com.vaadin.ui.Label; +import com.vaadin.ui.MenuBar; +import com.vaadin.ui.MenuBar.Command; +import com.vaadin.ui.MenuBar.MenuItem; +import com.vaadin.ui.Notification; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +import ch.asynk.ui.Icons; +import ch.asynk.ui.ViewMain; + +@PreserveOnRefresh +@Theme("mytheme") +@Title("Hello!!") +@Widgetset("mywidgetset") +public class HelloWorldUI extends UI +{ + private static final long serialVersionUID = 1L; + + private static final String HOME = "Home"; + private static final String ABOUT = "About"; + + private Navigator navigator; + + @Override + protected void init(VaadinRequest request) + { + System.err.println("UI init()"); + final VerticalLayout vl = new VerticalLayout(); + vl.setSizeFull(); + vl.setMargin(true); + setContent(vl); + + final MenuBar menu = createMenuBar(); + + final HorizontalSplitPanel hsplit = new HorizontalSplitPanel(); + hsplit.setSplitPosition(80f); + hsplit.addComponent(createLeftPanel()); + hsplit.addComponent(createRightPanel()); + + vl.addComponents(menu, hsplit); + vl.setExpandRatio(menu, 0); + vl.setExpandRatio(hsplit, 1); + + navigateToHome(); + } + + private Component createLeftPanel() + { + final VerticalLayout vl = new VerticalLayout(); + vl.addStyleName("margin-top"); + vl.addStyleName("margin-right"); + navigator = new Navigator(this, vl); + navigator.addView(HOME, ViewMain.class); + return vl; + } + + private Component createRightPanel() + { + final VerticalLayout vl = new VerticalLayout(); + vl.setMargin(true); + vl.addStyleName("mybg"); + // vl.addStyleName("margin-top"); + // vl.addStyleName("margin-right"); + vl.addComponent(new Label("Hello World using mytheme")); + Button btn = new Button("Push Me!", Icons.home); + btn.addClickListener(new Button.ClickListener() { + private static final long serialVersionUID = 1L; + @Override + public void buttonClick(Button.ClickEvent event) { + Notification.show("Pushed!"); + Daddy.trace("trace"); + Daddy.debug("debug"); + Daddy.info("info"); + Daddy.warn("warn"); + Daddy.error("error"); + } + }); + vl.addComponent(btn); + return vl; + } + + private MenuBar createMenuBar() + { + Command menuCommand = new Command() { + private static final long serialVersionUID = 1L; + @Override + public void menuSelected(MenuItem selectedItem) { + String itemText = selectedItem.getText(); + if (itemText.equals(HOME)) { + navigateToHome(); + } else if (itemText.equals(ABOUT)) { + showAbout(); + } else { + Daddy.error("unhandeled MenuItem : " + itemText); + } + } + }; + + final MenuBar menu = new MenuBar(); + menu.addItem(HOME, Icons.home, menuCommand); + menu.addItem(ABOUT, Icons.help, menuCommand); + MenuBar.MenuItem login = menu.addItem((String) UI.getCurrent().getSession().getAttribute(Daddy.SESSION_STATUS), menuCommand); + login.setStyleName("menuRight"); + menu.setWidth("100%"); + + return menu; + } + + public void navigateToHome() { navigator.navigateTo(HOME); } + + private void showAbout() + { + final Window about = new Window(ABOUT); + about.setContent( new CustomLayout(ABOUT) ); + about.setHeight("50%"); + about.setWidth("600px"); + about.center(); + about.setModal(true); + UI.getCurrent().addWindow(about); + } +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fConnector.java b/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fConnector.java new file mode 100644 index 0000000..24b406d --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fConnector.java @@ -0,0 +1,164 @@ +package ch.asynk.security; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.annotations.JavaScript; +import com.vaadin.server.AbstractJavaScriptExtension; +import com.vaadin.ui.UI; +import com.vaadin.ui.JavaScriptFunction; +import com.yubico.u2f.U2F; +import com.yubico.u2f.data.DeviceRegistration; +import com.yubico.u2f.data.messages.AuthenticateRequestData; +import com.yubico.u2f.data.messages.AuthenticateResponse; +import com.yubico.u2f.data.messages.RegisterRequestData; +import com.yubico.u2f.data.messages.RegisterResponse; +import com.yubico.u2f.exceptions.DeviceCompromisedException; +import com.yubico.u2f.exceptions.NoEligibleDevicesException; +import elemental.json.JsonArray; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@JavaScript({"u2f-api.js", "u2f_connector.js"}) +public class U2fConnector extends AbstractJavaScriptExtension +{ + private static final long serialVersionUID = 1L; + + private static final Logger logger = LoggerFactory.getLogger(U2fConnector.class); + + // appId MUST match the URL - https://developers.yubico.com/U2F/App_ID.html + private final String appId = "https://localhost:8666"; + + private final U2F u2f = new U2F(); + // private final U2F u2f = U2F.withoutAppIdValidation(); + + // https://developers.yubico.com/U2F/Libraries/Client_error_codes.html + private static String U2F_ERRORS[] = {"","OTHER_ERROR","BAD_REQUEST","CONFIGURATION_UNSUPPORTED","DEVICE_INELIGIBLE","TIMEOUT","DEVICE_COMPROMISED"}; + public String getErrorMsg(int i) { return U2F_ERRORS[i]; } + + private final U2fService u2fService = new U2fServiceImpl(); + + class Request + { + String userId; + String data; + U2fListener listener; + public Request(String userId, String data, U2fListener listener) + { + this.userId = userId; + this.data = data; + this.listener = listener; + } + } + + private final Map<String, Request> requests = new HashMap<>(); + + public enum U2fAction { + REGISTRATION_PENDING, + REGISTRATION_SUCCESS, + REGISTRATION_FAILURE, + AUTHENTICATION_PENDING, + AUTHENTICATION_FAILURE, + AUTHENTICATION_SUCCESS + }; + + public interface U2fListener + { + public void u2fCallback(U2fAction action, String msg); + } + + public U2fConnector() { + extend(UI.getCurrent()); + addFunction("onRegisterResponse", new JavaScriptFunction() { + private static final long serialVersionUID = 1L; + @Override + public void call(final JsonArray arguments) { + onRegisterResponse(arguments); + } + }); + addFunction("onAuthenticateResponse", new JavaScriptFunction() { + private static final long serialVersionUID = 1L; + @Override + public void call(final JsonArray arguments) { + onAuthenticateResponse(arguments); + } + }); + } + + public void sendRegisterRequest(final String userId, final U2fListener listener) + { + final RegisterRequestData registerRequestData = u2f.startRegistration(appId, u2fService.getDeviceRegistrations(userId)); + final String registerRequestDataJson = registerRequestData.toJson(); + final String registerRequestDataId = registerRequestData.getRequestId(); + requests.put(registerRequestDataId, new Request(userId, registerRequestDataJson, listener)); + callFunction("register", registerRequestDataJson); + listener.u2fCallback(U2fAction.REGISTRATION_PENDING, null); + logger.debug(String.format("register[%s] : %s", userId, registerRequestDataJson)); + } + + public void onRegisterResponse(JsonArray arguments) + { + if (arguments.get(0) instanceof elemental.json.impl.JreJsonNumber) { + int errorCode = (int) arguments.getNumber(0); + // FIXME why does it not work ?? + // final RegisterRequestData registerRequestData = RegisterRequestData.fromJson(arguments.getString(1)); + final String registerRequestDataJson = arguments.getObject(1).toString(); + final RegisterRequestData registerRequestData = RegisterRequestData.fromJson(registerRequestDataJson); + final Request request = requests.remove(registerRequestData.getRequestId()); + request.listener.u2fCallback(U2fAction.REGISTRATION_FAILURE, getErrorMsg(errorCode)); + logger.debug(String.format("failure[%d] : %s", errorCode, registerRequestDataJson)); + } else { + final String registerResponseJson = arguments.getString(0); + final RegisterResponse registerResponse = RegisterResponse.fromJson(registerResponseJson); + final Request request = requests.remove(registerResponse.getRequestId()); + final RegisterRequestData registerRequestData = RegisterRequestData.fromJson(request.data); + u2fService.addDeviceRegistration(request.userId, u2f.finishRegistration(registerRequestData, registerResponse)); + request.listener.u2fCallback(U2fAction.REGISTRATION_SUCCESS, null); + logger.debug(String.format("success : %s", registerResponseJson)); + } + } + + public void startAuthentication(final String userId, final U2fListener listener) + { + try { + final AuthenticateRequestData authenticateRequestData = u2f.startAuthentication(appId, u2fService.getDeviceRegistrations(userId)); + final String authenticateRequestDataJson = authenticateRequestData.toJson(); + final String authenticateRequestDataId = authenticateRequestData.getRequestId(); + requests.put(authenticateRequestDataId, new Request(userId, authenticateRequestDataJson, listener)); + callFunction("authenticate", authenticateRequestDataJson, userId); + listener.u2fCallback(U2fAction.AUTHENTICATION_PENDING, null); + logger.debug(String.format("authenticate[%s] : %s", userId, authenticateRequestDataJson)); + } catch (NoEligibleDevicesException e) { + listener.u2fCallback(U2fAction.AUTHENTICATION_FAILURE, getErrorMsg(4)); + } + } + + public void onAuthenticateResponse(JsonArray arguments) + { + if (arguments.get(0) instanceof elemental.json.impl.JreJsonNumber) { + int errorCode = (int) arguments.getNumber(0); + // FIXME why does it not work ?? + // final AuthenticateRequestData authenticateRequestData = AuthenticateRequestData.fromJson(arguments.getString(1)); + final String authenticateRequestDataJson = arguments.getObject(1).toString(); + final AuthenticateRequestData authenticateRequestData = AuthenticateRequestData.fromJson(authenticateRequestDataJson); + final Request request = requests.remove(authenticateRequestData.getRequestId()); + request.listener.u2fCallback(U2fAction.AUTHENTICATION_FAILURE, getErrorMsg(errorCode)); + logger.debug(String.format("failure[%d] : %s", errorCode, authenticateRequestDataJson)); + } else { + final String authenticateResponseJson = arguments.getString(0); + final AuthenticateResponse authenticateResponse = AuthenticateResponse.fromJson(authenticateResponseJson); + final Request request = requests.remove(authenticateResponse.getRequestId()); + final AuthenticateRequestData authenticateRequestData = AuthenticateRequestData.fromJson(request.data); + DeviceRegistration registration = null; + try { + registration = u2f.finishAuthentication(authenticateRequestData, authenticateResponse, u2fService.getDeviceRegistrations(request.userId)); + } catch (final DeviceCompromisedException e) { + request.listener.u2fCallback(U2fAction.AUTHENTICATION_FAILURE, getErrorMsg(6)); + logger.error(String.format("device compromised: %s", request.data)); + } + request.listener.u2fCallback(U2fAction.AUTHENTICATION_SUCCESS, null); + logger.debug(String.format("success : %s", authenticateResponseJson)); + } + } +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fService.java b/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fService.java new file mode 100644 index 0000000..9bce94e --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fService.java @@ -0,0 +1,12 @@ +package ch.asynk.security; + +import java.util.List; + +import com.yubico.u2f.data.DeviceRegistration; + +public interface U2fService +{ + public boolean hasDeviceRegistrations(final String userId); + public List<DeviceRegistration> getDeviceRegistrations(final String userId); + public void addDeviceRegistration(final String userId, final DeviceRegistration deviceRegistration); +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fServiceImpl.java b/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fServiceImpl.java new file mode 100644 index 0000000..f8e785f --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/security/U2fServiceImpl.java @@ -0,0 +1,43 @@ +package ch.asynk.security; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.yubico.u2f.data.DeviceRegistration; + +public class U2fServiceImpl implements U2fService +{ + private HashMap<String, List<DeviceRegistration>> store; + + public U2fServiceImpl() + { + store = new HashMap<String, List<DeviceRegistration>>(); + } + + public boolean hasDeviceRegistrations(final String userId) + { + return store.containsKey(userId); + } + + public List<DeviceRegistration> getDeviceRegistrations(final String userId) + { + return get(userId); + } + + public void addDeviceRegistration(final String userId, final DeviceRegistration deviceRegistration) + { + List<DeviceRegistration> registrations = get(userId); + registrations.add(deviceRegistration); + store.put(userId, registrations); + } + + private List<DeviceRegistration> get(final String userId) + { + List<DeviceRegistration> registrations = store.get(userId); + if (registrations == null) + registrations = new ArrayList<DeviceRegistration>(); + return registrations; + } +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/security/u2f-api.js b/java/vaadin-u2f/src/main/java/ch/asynk/security/u2f-api.js new file mode 100644 index 0000000..9244d14 --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/security/u2f-api.js @@ -0,0 +1,748 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. + u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array<u2f.Transport>} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array<u2f.SignRequest>} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array<u2f.SignRequest>} signRequests + * @param {Array<u2f.RegisterRequest>} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } +}; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); +}; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse)) + * |function((u2f.Error|u2f.SignResponse)))>} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.<u2f.Response>} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array<u2f.RegisteredKey>} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array<u2f.RegisteredKey>} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array<u2f.RegisterRequest>} registerRequests + * @param {Array<u2f.RegisteredKey>} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array<u2f.RegisterRequest>} registerRequests + * @param {Array<u2f.RegisteredKey>} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/security/u2f_connector.js b/java/vaadin-u2f/src/main/java/ch/asynk/security/u2f_connector.js new file mode 100644 index 0000000..8723cb3 --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/security/u2f_connector.js @@ -0,0 +1,30 @@ +window.ch_asynk_security_U2fConnector = function() { + + var connector = this; + + this.register = function(requestJson) { + var request = JSON.parse(requestJson); + // alert(JSON.stringify(request)); + setTimeout(function() { + u2f.register(request.registerRequests, request.authenticateRequests, function(data) { + if(data.errorCode) + connector.onRegisterResponse(data.errorCode, request); + else + connector.onRegisterResponse(JSON.stringify(data)); + }); + }, 500); + } + + this.authenticate = function(requestJson) { + var request = JSON.parse(requestJson); + //alert(JSON.stringify(request)); + setTimeout(function() { + u2f.sign(request.authenticateRequests, function(data) { + if(data.errorCode) + connector.onAuthenticateResponse(data.errorCode, request); + else + connector.onAuthenticateResponse(JSON.stringify(data)); + }); + }, 500); + } +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/ui/Icons.java b/java/vaadin-u2f/src/main/java/ch/asynk/ui/Icons.java new file mode 100644 index 0000000..3cc96bc --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/ui/Icons.java @@ -0,0 +1,18 @@ +package ch.asynk.ui; + +import com.vaadin.server.ThemeResource; + +public class Icons +{ + public static final ThemeResource help = new ThemeResource("icons/help.png"); + public static final ThemeResource home = new ThemeResource("icons/home.png"); + public static final ThemeResource u2f128 = new ThemeResource("icons/u2f-128.png"); + public static final ThemeResource u2f64 = new ThemeResource("icons/u2f-64.png"); + public static final ThemeResource u2f32 = new ThemeResource("icons/u2f-32.png"); + public static final ThemeResource u2f24 = new ThemeResource("icons/u2f-24.png"); + public static final ThemeResource u2flogo = new ThemeResource("icons/u2f-logo.png"); + public static final ThemeResource u2flogo48 = new ThemeResource("icons/u2f-logo-48.png"); + public static final ThemeResource u2flock = new ThemeResource("icons/u2f-lock.png"); + public static final ThemeResource thumbUp = new ThemeResource("icons/thumb_up.png"); + public static final ThemeResource thumbDown = new ThemeResource("icons/thumb_down.png"); +} diff --git a/java/vaadin-u2f/src/main/java/ch/asynk/ui/ViewMain.java b/java/vaadin-u2f/src/main/java/ch/asynk/ui/ViewMain.java new file mode 100644 index 0000000..8fae5f0 --- /dev/null +++ b/java/vaadin-u2f/src/main/java/ch/asynk/ui/ViewMain.java @@ -0,0 +1,176 @@ +package ch.asynk.ui; + +import com.vaadin.data.validator.RegexpValidator; +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.server.Page; +import com.vaadin.data.Binder; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.Component; +import com.vaadin.ui.Image; +import com.vaadin.ui.Label; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Panel; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; +import com.vaadin.ui.GridLayout; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +import ch.asynk.security.U2fConnector; + +class Login +{ + private String login; + public String getLogin() { return login; } + public void setLogin(final String login) { this.login = login; } +} + +public class ViewMain extends TabSheet implements View, U2fConnector.U2fListener +{ + private static final long serialVersionUID = 1L; + + private static final String U2F_TITLE = "FIDO U2F (Universal 2nd factor)"; + + private final Window u2fWindow; + private final U2fConnector u2fConnector; + + private enum Action { U2F_REGISTER, U2F_AUTHENTICATE } + + public ViewMain() + { + u2fWindow = u2fWindow(); + u2fConnector = new U2fConnector(); + addStyleName("framed"); + addTab(u2fComponent("register", Action.U2F_REGISTER), "Register", Icons.u2flogo48); + addTab(u2fComponent("authenticate", Action.U2F_AUTHENTICATE), "Authenticate", Icons.u2f32); + setSelectedTab(2); + } + + @Override + public void enter(ViewChangeEvent event) { + System.err.println(this.getClass().getName()+"::enter"); + } + + private VerticalLayout content(String s) + { + VerticalLayout ly = new VerticalLayout(); + ly.addComponent(new Label(" Hi, this is " + s)); + return ly; + } + + private Component u2fComponent(final String title, Action action) + { + final Login login = new Login(); + final TextField tf = new TextField(); + final Button bt = new Button(title.toLowerCase()); + final Binder<Login> binder = new Binder<>(); + binder.addValueChangeListener(evt -> bt.setEnabled(binder.isValid()) ); + binder.forField(tf) + .asRequired("Login required") + .withValidator(s -> s.length() >= 6, "at least 6 characters") + .withValidator(s -> s.length() <= 20, "at most 20 characters") + .withValidator(new RegexpValidator("invalid username", "^(?=.{6,20}$)[a-zA-Z][a-zA-Z0-9#@.]+[a-zA-Z]$")) + .bind(Login::getLogin, Login::setLogin); + + bt.setEnabled(false); + bt.addClickListener(e -> { + if (binder.writeBeanIfValid(login)) { + tf.setValue(""); + tryAction(action, login); + } + }); + + Panel panel = new Panel(title); + panel.setWidth("450px"); + final GridLayout ly = new GridLayout(2,2); + ly.setMargin(true); + ly.setSpacing(true); + panel.setContent(ly); + ly.addComponent(new Image(null, Icons.u2flock), 0, 0, 0, 1); + ly.addComponent(tf, 1, 0); + ly.addComponent(bt,1,1); + ly.setComponentAlignment(bt, Alignment.MIDDLE_CENTER); + + VerticalLayout vl = new VerticalLayout(); + vl.setMargin(true); + vl.setSizeFull(); + vl.addComponent(panel); + vl.setComponentAlignment(panel, Alignment.MIDDLE_CENTER); + return vl; + } + + private void tryAction(Action action, Login login) + { + final String userId = login.getLogin(); + login.setLogin(null); + try { + if (action == Action.U2F_REGISTER) + u2fConnector.sendRegisterRequest(userId, this); + else if (action == Action.U2F_AUTHENTICATE) + u2fConnector.startAuthentication(userId, this); + } catch (com.yubico.u2f.exceptions.U2fBadConfigurationException ex) { + Notification.show(U2F_TITLE, ex.getMessage(), Notification.Type.ERROR_MESSAGE); + } + } + + public void u2fCallback(U2fConnector.U2fAction action, String msg) + { + Notification n = null; + switch (action) { + case REGISTRATION_PENDING: + case AUTHENTICATION_PENDING: + UI.getCurrent().addWindow(u2fWindow); + break; + case REGISTRATION_SUCCESS: + msg = "Your token has been successfully registred."; + break; + case REGISTRATION_FAILURE: + msg = "Registration failed with error : " + msg; + break; + case AUTHENTICATION_SUCCESS: + msg = "You have been successfully authenticated."; + break; + case AUTHENTICATION_FAILURE: + msg = "Authentication failed with error : " + msg; + break; + } + switch (action) { + case REGISTRATION_SUCCESS: + case AUTHENTICATION_SUCCESS: + n = new Notification(U2F_TITLE, msg, Notification.Type.HUMANIZED_MESSAGE); + n.setIcon(Icons.thumbUp); + break; + case REGISTRATION_FAILURE: + case AUTHENTICATION_FAILURE: + n = new Notification(U2F_TITLE, msg, Notification.Type.ERROR_MESSAGE); + n.setIcon(Icons.thumbDown); + break; + } + if (n != null) { + n.setDelayMsec(5000); + u2fWindow.close(); + n.show(Page.getCurrent()); + } + } + + private Window u2fWindow() + { + Window w = new Window(U2F_TITLE); + final VerticalLayout vy = new VerticalLayout(); + vy.setMargin(true); + vy.setSpacing(true); + vy.addComponent(new Label("Insert your u2f token and click on it.")); + vy.addComponent(new Image(null, Icons.u2flock)); + w.setModal(true); + w.setClosable(false); + w.setContent(vy); + w.setResizable(false); + w.setWidth("280px"); + w.setHeight("220px"); + w.center(); + return w; + } +} diff --git a/java/vaadin-u2f/src/main/resources/Application.properties b/java/vaadin-u2f/src/main/resources/Application.properties new file mode 100644 index 0000000..512362b --- /dev/null +++ b/java/vaadin-u2f/src/main/resources/Application.properties @@ -0,0 +1,5 @@ +app.name=VaadinHelloWorld +app.version=0.0.1 +app.path=/hello +app.env=DEV +app.logfile=/var/log/tomcat7/HelloWorld.log diff --git a/java/vaadin-u2f/src/main/resources/logback-test.xml b/java/vaadin-u2f/src/main/resources/logback-test.xml new file mode 100644 index 0000000..a8fcf4b --- /dev/null +++ b/java/vaadin-u2f/src/main/resources/logback-test.xml @@ -0,0 +1,41 @@ +<configuration debug="true"> + + <contextName>HelloWorld</contextName> + <property resource="Application.properties" /> + <property name="LOG_LEVEL" value="INFO" /> + <property name="LOG_REF" value="FILE" /> + <property name="LOG_FILE" value="${app.logfile}" /> + <property name="LOG_PATTERN" value="%contextName %date{ISO8601} [%-5level %thread] [%file:%line] - %msg%n" /> + + <if condition='property("app.env").contains("DEV")'> + <then> + <property name="LOG_LEVEL" value="DEBUG" /> + </then> + </if> + + <if condition='isDefined("TEST")'> + <then> + <property name="LOG_FILE" value="/tmp/${app.name}.log" /> + <property name="LOG_LEVEL" value="DEBUG" /> + <property name="LOG_REF" value="STDOUT" /> + </then> + </if> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${LOG_PATTERN}</pattern> + </encoder> + </appender> + + <appender name="FILE" class="ch.qos.logback.core.FileAppender"> + <file>${LOG_FILE}</file> + <encoder> + <pattern>${LOG_PATTERN}</pattern> + </encoder> + </appender> + + <root level="${LOG_LEVEL}"> + <appender-ref ref="${LOG_REF}" /> + </root> + +</configuration> diff --git a/java/vaadin-u2f/src/run/java/ch/asynk/Main.java b/java/vaadin-u2f/src/run/java/ch/asynk/Main.java new file mode 100644 index 0000000..f6dfc4e --- /dev/null +++ b/java/vaadin-u2f/src/run/java/ch/asynk/Main.java @@ -0,0 +1,130 @@ +package ch.asynk; + +import com.vaadin.server.DefaultUIProvider; +import com.vaadin.server.UIProvider; +import com.vaadin.server.VaadinServlet; +import com.vaadin.ui.UI; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.webapp.WebAppContext; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +// https://github.com/eclipse/jetty.project/blob/jetty-9.3.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java + +abstract class VaadinJettyServer extends Server +{ + public VaadinJettyServer(int port, final String webappDirectory, final String contextPath) + { + super(); + configure(port, contextPath); + buildWebapp(webappDirectory, contextPath); + } + + abstract protected void configure(int port, final String contextPath); + + protected void buildWebapp(final String webappDirectory, final String contextPath) + { + + WebAppContext context = new WebAppContext(webappDirectory, contextPath); + + context.addServlet(buildVaadinServlet(new HelloWorldServlet(), HelloWorldUI.class), "/*"); + + setHandler(context); + } + + protected void say(int port, boolean https, final String contextPath) + { + System.out.println(String.format("%s://localhost:%d%s", (https ? "https" : "http"), port, contextPath)); + } + + private ServletHolder buildVaadinServlet(VaadinServlet servlet, Class<? extends UI> uiClass) + { + ServletHolder servletHolder = new ServletHolder(servlet); + servletHolder.setInitParameter(VaadinServlet.SERVLET_PARAMETER_UI_PROVIDER, DefaultUIProvider.class.getName()); + if (uiClass != null) servletHolder.setInitParameter("UI", uiClass.getName()); + return servletHolder; + } + + protected Connector httpConnector(int port) + { + ServerConnector connector = new ServerConnector(this); + connector.setPort(port); + return connector; + } + + protected Connector httpsConnector(int port) + { + HttpConfiguration httpConf = new HttpConfiguration(); + httpConf.addCustomizer(new SecureRequestCustomizer()); + + SslContextFactory sslContextFactory = new SslContextFactory(); + // keytool -keystore src/run/resources/keystore.jks -genkey -alias asynk.ch -keyalg RSA -keysize 2048 + sslContextFactory.setKeyStorePath(VaadinJettyServer.class.getResource("/keystore.jks").toExternalForm()); + sslContextFactory.setKeyStorePassword("123456"); + sslContextFactory.setKeyManagerPassword("123456"); + ServerConnector connector = new ServerConnector(this, + new SslConnectionFactory(sslContextFactory, "http/1.1"), + new HttpConnectionFactory(httpConf)); + connector.setPort(port); + return connector; + } +} + +class HttpVaadinJettyServer extends VaadinJettyServer +{ + public HttpVaadinJettyServer(int port, final String webappDirectory, final String contextPath) { super(port, webappDirectory, contextPath); } + + @Override + protected void configure(int port, final String contextPath) + { + say(port, false, contextPath); + addConnector(httpConnector(port)); + } +} + +class HttpsVaadinJettyServer extends VaadinJettyServer +{ + public HttpsVaadinJettyServer(int port, final String webappDirectory, final String contextPath) { super(port, webappDirectory, contextPath); } + + @Override + protected void configure(int port, final String contextPath) + { + say(port, true, contextPath); + addConnector(httpsConnector(port)); + } +} +class HttpHttpsVaadinJettyServer extends HttpsVaadinJettyServer +{ + public HttpHttpsVaadinJettyServer(int port, final String webappDirectory, final String contextPath) { super(port, webappDirectory, contextPath); } + + @Override + protected void configure(int port, final String contextPath) + { + say(port, false, contextPath); + say(port + 1, true, contextPath); + this.setConnectors(new Connector[] { httpConnector(port), httpsConnector(port + 1) }); + } +} + +public class Main +{ + public static void main(String[] args) throws Exception + { + int port = 8666; + String webRoot = System.getProperty("WEBROOT"); + if (webRoot == null) webRoot = "./src/main/WebContent"; + + new HttpsVaadinJettyServer(port, webRoot, "/test").start(); + } +} diff --git a/java/vaadin-u2f/src/run/resources/jetty-logging.properties b/java/vaadin-u2f/src/run/resources/jetty-logging.properties new file mode 100644 index 0000000..71495db --- /dev/null +++ b/java/vaadin-u2f/src/run/resources/jetty-logging.properties @@ -0,0 +1,6 @@ +# Configure for System.err output +org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog +# Configure StdErrLog to log all jetty namespace at default of WARN or above +org.eclipse.jetty.LEVEL=WARN +# Configure StdErrLog to log websocket specific namespace at DEBUG or above +org.eclipse.jetty.websocket.LEVEL=INFO diff --git a/java/vaadin-u2f/src/run/resources/keystore.jks b/java/vaadin-u2f/src/run/resources/keystore.jks Binary files differnew file mode 100644 index 0000000..333fa20 --- /dev/null +++ b/java/vaadin-u2f/src/run/resources/keystore.jks diff --git a/java/vaadin-u2f/tomcat.properties b/java/vaadin-u2f/tomcat.properties new file mode 100644 index 0000000..7ec1b73 --- /dev/null +++ b/java/vaadin-u2f/tomcat.properties @@ -0,0 +1,4 @@ +manager.username=manager +manager.password=h46bh2j0 +manager.url=http://127.0.0.1:8080/manager/text +catalina.home=/usr/share/tomcat7 |