diff --git a/.gitignore b/.gitignore
index 62dfa3777eb..53b1783eaff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ appserver/packager/pkg_conf.py
target/
*~
*.iml
+*nbactions.xml
.vscode/
.idea
gfbuild.log
@@ -22,4 +23,4 @@ nb-configuration.xml
/nucleus/payara-modules/nucleus-microprofile/config-service/nbproject/
appserver/extras/arquillian-containers/payara-common/dependency-reduced-pom.xml
/nucleus/payara-modules/service-exemplar/nbproject/
-/nucleus/admin/server-mgmt/nbproject/
\ No newline at end of file
+/nucleus/admin/server-mgmt/nbproject/
diff --git a/appserver/extras/embedded/all/pom.xml b/appserver/extras/embedded/all/pom.xml
index 3cb5d750a4f..8a0c30a5737 100644
--- a/appserver/extras/embedded/all/pom.xml
+++ b/appserver/extras/embedded/all/pom.xml
@@ -437,6 +437,13 @@
zip
true
+
+
+ org.glassfish.main.packager
+ yubikey-authentication
+ ${project.version}
+ zip
+
org.glassfish.main.packager
cdi-auth-roles
diff --git a/appserver/extras/embedded/web/pom.xml b/appserver/extras/embedded/web/pom.xml
index 8ca242fa150..d44beb7e8a5 100644
--- a/appserver/extras/embedded/web/pom.xml
+++ b/appserver/extras/embedded/web/pom.xml
@@ -381,6 +381,13 @@
zip
true
+
+
+ org.glassfish.main.packager
+ yubikey-authentication
+ ${project.version}
+ zip
+
org.glassfish.main.packager
diff --git a/appserver/featuresets/payara-web/pom.xml b/appserver/featuresets/payara-web/pom.xml
index 70bbb8a6a5d..934f27a10be 100644
--- a/appserver/featuresets/payara-web/pom.xml
+++ b/appserver/featuresets/payara-web/pom.xml
@@ -275,6 +275,13 @@
${project.version}
zip
+
+
+ org.glassfish.main.packager
+ yubikey-authentication
+ ${project.version}
+ zip
+
diff --git a/appserver/featuresets/payara/pom.xml b/appserver/featuresets/payara/pom.xml
index a934049e245..cbc3eb32bc1 100644
--- a/appserver/featuresets/payara/pom.xml
+++ b/appserver/featuresets/payara/pom.xml
@@ -40,7 +40,7 @@
holder.
-->
-
+
4.0.0
diff --git a/appserver/packager/external/pom.xml b/appserver/packager/external/pom.xml
index 78b7c0ec4b2..d76218539eb 100644
--- a/appserver/packager/external/pom.xml
+++ b/appserver/packager/external/pom.xml
@@ -40,7 +40,7 @@
holder.
-->
-
+
+
+ 4.0.0
+
+ org.glassfish.main.packager
+ packages
+ 5.182-SNAPSHOT
+
+ yubikey-authentication
+ Yubikey Authentication Package
+ distribution-fragment
+ This pom describes how to assemble the Payara Yubikey Authentication package
+
+
+ ${project.build.directory}/dependency
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ process-step1
+
+
+ process-step2
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ process-step3
+
+
+
+
+
+
+
+ ips
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+ process-step4
+
+
+
+
+ org.glassfish.build
+ glassfishbuild-maven-plugin
+
+
+ process-step5
+
+ exec
+
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-resources
+
+
+
+
+
+
+
+
+
+
+ com.yubico
+ yubico-validation-client2
+ ${yubico-validation-client2.version}
+
+
+ fish.payara.security
+ yubikey-authentication
+ ${project.version}
+
+
+
diff --git a/appserver/packager/yubikey-authentication/src/main/assembly/yubikey-authentication.xml b/appserver/packager/yubikey-authentication/src/main/assembly/yubikey-authentication.xml
new file mode 100644
index 00000000000..0f60a5f19c3
--- /dev/null
+++ b/appserver/packager/yubikey-authentication/src/main/assembly/yubikey-authentication.xml
@@ -0,0 +1,64 @@
+
+
+
+ stage-package
+
+ dir
+
+
+ false
+
+
+ ${temp.dir}/nucleus
+ ${install.dir.name}/glassfish
+
+
+ ${temp.dir}
+
+ nucleus/**
+ pkg_proto.py
+
+ ${install.dir.name}
+
+
+
diff --git a/appserver/packager/yubikey-authentication/src/main/resources/pkg_proto.py b/appserver/packager/yubikey-authentication/src/main/resources/pkg_proto.py
new file mode 100644
index 00000000000..3816a993539
--- /dev/null
+++ b/appserver/packager/yubikey-authentication/src/main/resources/pkg_proto.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+# Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+#
+# The contents of this file are subject to the terms of either the GNU
+# General Public License Version 2 only ("GPL") or the Common Development
+# and Distribution License("CDDL") (collectively, the "License"). You
+# may not use this file except in compliance with the License. You can
+# obtain a copy of the License at
+# https://github.com/payara/Payara/blob/master/LICENSE.txt
+# See the License for the specific
+# language governing permissions and limitations under the License.
+#
+# When distributing the software, include this License Header Notice in each
+# file and include the License file at glassfish/legal/LICENSE.txt.
+#
+# GPL Classpath Exception:
+# The Payara Foundation designates this particular file as subject to the "Classpath"
+# exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+# file that accompanied this code.
+#
+# Modifications:
+# If applicable, add the following below the License Header, with the fields
+# enclosed by brackets [] replaced by your own identifying information:
+# "Portions Copyright [year] [name of copyright owner]"
+#
+# Contributor(s):
+# If you wish your version of this file to be governed by only the CDDL or
+# only the GPL Version 2, indicate your decision by adding "[Contributor]
+# elects to include this software in this distribution under the [CDDL or GPL
+# Version 2] license." If you don't indicate a single choice of license, a
+# recipient has the option to distribute your version of this file under
+# either the CDDL, the GPL Version 2 or to extend the choice of license to
+# its licensees as provided above. However, if you add GPL Version 2 code
+# and therefore, elected the GPL Version 2 license, then the option applies
+# only if the new code is made subject to such option by the copyright
+# holder.
+
+
+import imp
+
+conf = imp.load_source("pkg_conf", "../pkg_conf.py")
+
+pkg = {
+ "name" : "yubikey-authentication",
+ "version" : conf.glassfish_version,
+ "attributes" : {
+ "pkg.summary" : "yubikey-authentication Integration",
+ "pkg.description" : "Payara yubikey-authentication modules",
+ "info.classification" : "OSGi Service Platform Release 4",
+ },
+ "dirtrees" : { "glassfish/modules" : {},
+ },
+ "licenses" : {
+ "../../../../ApacheLicense.txt" : {"license" : "ApacheV2"},
+ }
+ }
+
diff --git a/appserver/payara-appserver-modules/pom.xml b/appserver/payara-appserver-modules/pom.xml
index f84d7057ec4..0ade7dd922b 100644
--- a/appserver/payara-appserver-modules/pom.xml
+++ b/appserver/payara-appserver-modules/pom.xml
@@ -72,8 +72,8 @@
payara-rest-endpoints
rest-monitoring
cdi-auth-roles
+ yubikey-authentication
-
jdk8
@@ -87,3 +87,4 @@
+
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/pom.xml b/appserver/payara-appserver-modules/yubikey-authentication/pom.xml
new file mode 100644
index 00000000000..4392d850f8c
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/pom.xml
@@ -0,0 +1,135 @@
+
+
+
+
+ 4.0.0
+
+ org.glassfish.main
+ payara-appserver-modules
+ 5.182-SNAPSHOT
+
+
+ fish.payara.security
+ yubikey-authentication
+
+ Yubikey Authentication
+ glassfish-jar
+ Yubikey Authentication
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ bundle-manifest
+ process-classes
+
+ manifest
+
+
+
+
+ fish.payara.security.annotations,
+ fish.payara.security.authentication.twoIdentityStore,
+ fish.payara.security.identitystores
+
+
+
+
+
+
+
+
+
+
+
+ org.glassfish.main.admin
+ config-api
+ ${project.version}
+ provided
+
+
+ javax
+ javaee-api
+ 8.0
+ provided
+
+
+ com.yubico
+ yubico-validation-client2
+ ${yubico-validation-client2.version}
+ provided
+
+
+ org.glassfish.soteria
+ javax.security.enterprise
+ provided
+
+
+ fish.payara.api
+ payara-api
+ ${project.version}
+ provided
+
+
+ fish.payara.payara-modules
+ requesttracing-core
+ ${project.version}
+ provided
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ ${microprofile-config.version}
+ provided
+
+
+ fish.payara.nucleus.microprofile.config
+ microprofile-config-service
+ ${project.version}
+ provided
+
+
+
\ No newline at end of file
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/annotations/TwoIdentityStoreAuthenticationMechanismDefinition.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/annotations/TwoIdentityStoreAuthenticationMechanismDefinition.java
new file mode 100644
index 00000000000..d69e5146470
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/annotations/TwoIdentityStoreAuthenticationMechanismDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package fish.payara.security.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.enterprise.util.Nonbinding;
+import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
+
+/**
+ * Used to define a Two IdentityStore Authentication Mechanism with an annotation and provide configuration options
+ * @author Mark Wareham
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface TwoIdentityStoreAuthenticationMechanismDefinition {
+
+ @Nonbinding
+ LoginToContinue loginToContinue();
+
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/annotations/YubikeyIdentityStoreDefinition.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/annotations/YubikeyIdentityStoreDefinition.java
new file mode 100644
index 00000000000..0e35b7b7757
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/annotations/YubikeyIdentityStoreDefinition.java
@@ -0,0 +1,87 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package fish.payara.security.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Used to define a Yubico Identity store with an annotation and provide configuration options.
+ * Supports connecting to the Yubico's cloud validation service. You must provide an API
+ * client ID and key for this service
+ * You can obtain one directly from Yubico at https://upgrade.yubico.com/getapikey/
+ *
+ * These can be passed in as EL #{var}
, payara config ${ENV=VARNAME}
or raw.
+ * Microprofile config of
+ * payara.security.yubikey.apikey
+ * payara.security.yubikey.clientid
+ * payara.security.yubikey.identitystore.priority
+ *
+ * will override the properties within the annotation.
+ *
+ * Default priority is 100.
+ *
+ * @author Mark Wareham
+ *
+ * @see fish.payara.security.identitystoresYubikeyIdentityStore
+ */
+
+@Retention(value=RetentionPolicy.RUNTIME)
+@Target(value=ElementType.TYPE)
+public @interface YubikeyIdentityStoreDefinition {
+
+ String YUBICO_API_KEY= "payara.security.yubikey.apikey";
+ String YUBICO_API_CLIENT_ID= "payara.security.yubikey.clientid";
+ String YUBIKEY_ID_STORE_PRIORITY= "payara.security.yubikey.identitystore.priority";
+
+ int yubikeyAPIClientID() default 0; //default is needed to allow yubikeyAPIClientIDExpression alone.
+
+ String yubikeyAPIKey();
+
+ int priority() default 100;
+
+ String priorityExpression() default "";
+
+ String yubikeyAPIClientIDExpression() default "";
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/CDIExtension.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/CDIExtension.java
new file mode 100644
index 00000000000..4e898ec3e89
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/CDIExtension.java
@@ -0,0 +1,164 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package fish.payara.security.authentication.twoIdentityStore;
+
+import static org.glassfish.soteria.cdi.CdiUtils.getAnnotation;
+
+import fish.payara.security.identitystores.YubikeyIdentityStore;
+import fish.payara.security.annotations.YubikeyIdentityStoreDefinition;
+import fish.payara.security.identitystores.YubicoAPIImpl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.CDI;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.ProcessBean;
+import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
+import javax.security.enterprise.identitystore.IdentityStore;
+import org.glassfish.soteria.cdi.CdiProducer;
+import org.glassfish.soteria.cdi.LoginToContinueAnnotationLiteral;
+import fish.payara.security.annotations.TwoIdentityStoreAuthenticationMechanismDefinition;
+import fish.payara.security.identitystores.YubikeyIdentityStoreDefinitionAnnotationLiteral;
+
+/**
+ * CDI Extension class. Uses Dynamic producers to add {@link TwoIdentityStoreAuthenticationMechanism},
+ * {@link YubikeyIdentityStore} if annotations containing their corresponding *Definition are found.
+ *
+ * @author Mark Wareham
+ */
+public class CDIExtension implements Extension {
+
+ private static final Logger LOG = Logger.getLogger(CDIExtension.class.getName());
+ private List> identityStoreBeans = new ArrayList<>();
+ private Bean authenticationMechanismBean;
+ private static final String MODULE_PREFIX = "2FA Module Extension ";
+
+ public void register(@Observes BeforeBeanDiscovery beforeBean, BeanManager beanManager) {
+ beforeBean.addAnnotatedType(beanManager.createAnnotatedType(TwoIdentityStoreAuthenticationMechanism.class),
+ MODULE_PREFIX + TwoIdentityStoreAuthenticationMechanism.class.getName());
+ beforeBean.addAnnotatedType(beanManager.createAnnotatedType(TwoIdentityStoreAuthenticationMechanismState.class),
+ MODULE_PREFIX + TwoIdentityStoreAuthenticationMechanismState.class.getName());
+ beforeBean.addAnnotatedType(beanManager.createAnnotatedType(YubicoAPIImpl.class),
+ MODULE_PREFIX + YubicoAPIImpl.class.getName());
+ beforeBean.addAnnotatedType(beanManager.createAnnotatedType(YubikeyIdentityStore.class),
+ MODULE_PREFIX + YubikeyIdentityStore.class.getName());
+
+ }
+
+ public void processBean(@Observes ProcessBean eventIn, BeanManager beanManager) {
+
+ ProcessBean event = eventIn; // JDK8 u60 workaround
+
+ //create the bean being proccessed.
+ Class> beanClass = event.getBean().getBeanClass();
+
+ //get the identity store from the annotation (if it exists)
+ Optional optionalYubikeyIdentityStore = getAnnotation(beanManager,
+ event.getAnnotated(), YubikeyIdentityStoreDefinition.class);
+
+ optionalYubikeyIdentityStore.ifPresent(yubikeyIdentityStoreDefinition -> {
+ logActivatedIdentityStore(YubikeyIdentityStoreDefinition.class, beanClass);
+ identityStoreBeans.add(new CdiProducer()
+ .scope(ApplicationScoped.class)
+ .beanClass(IdentityStore.class)
+ .types(Object.class, IdentityStore.class)
+ .addToId(YubikeyIdentityStoreDefinition.class)
+ .create(e -> CDI.current().select(YubikeyIdentityStore.class)
+ .get()
+ .init(YubikeyIdentityStoreDefinitionAnnotationLiteral.eval(yubikeyIdentityStoreDefinition))
+ )
+ );
+ });
+
+ Optional optionalOneTimePasswordMechanism
+ = getAnnotation(beanManager, event.getAnnotated(), TwoIdentityStoreAuthenticationMechanismDefinition.class);
+ optionalOneTimePasswordMechanism.ifPresent(oneTimePasswordAuthenticationMechanismDefinition -> {
+
+ logActivatedAuthenticationMechanism(TwoIdentityStoreAuthenticationMechanismDefinition.class, beanClass);
+
+ authenticationMechanismBean = new CdiProducer()
+ .scope(ApplicationScoped.class)
+ .beanClass(HttpAuthenticationMechanism.class)
+ .types(Object.class, HttpAuthenticationMechanism.class)
+ .addToId(TwoIdentityStoreAuthenticationMechanismDefinition.class)
+ .create(e -> {
+ return CDI.current()
+ .select(TwoIdentityStoreAuthenticationMechanism.class)
+ .get()
+ .loginToContinue(LoginToContinueAnnotationLiteral.eval(
+ oneTimePasswordAuthenticationMechanismDefinition.loginToContinue()));
+ });
+ });
+ }
+
+ public void afterBean(final @Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
+
+ if (!identityStoreBeans.isEmpty()) {
+ for (Bean identityStoreBean : identityStoreBeans) {
+ afterBeanDiscovery.addBean(identityStoreBean);
+ }
+ }
+
+ if (authenticationMechanismBean != null) {
+ afterBeanDiscovery.addBean(authenticationMechanismBean);
+ }
+
+ }
+
+ private void logActivatedIdentityStore(Class> identityStoreClass, Class> beanClass) {
+ LOG.log(Level.INFO, "Activating {0} identity store from {1} class",
+ new String[]{identityStoreClass.getName(), beanClass.getName()});
+ }
+
+ private void logActivatedAuthenticationMechanism(Class> authenticationMechanismClass, Class> beanClass) {
+ LOG.log(Level.INFO, "Activating {0} authentication mechanism from {1} class",
+ new String[]{authenticationMechanismClass.getName(), beanClass.getName()});
+ }
+
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/TwoIdentityStoreAuthenticationMechanism.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/TwoIdentityStoreAuthenticationMechanism.java
new file mode 100644
index 00000000000..89fbbfc37d1
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/TwoIdentityStoreAuthenticationMechanism.java
@@ -0,0 +1,135 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package fish.payara.security.authentication.twoIdentityStore;
+
+import javax.enterprise.inject.Typed;
+import javax.enterprise.inject.spi.CDI;
+import javax.inject.Inject;
+import javax.security.enterprise.AuthenticationException;
+import javax.security.enterprise.AuthenticationStatus;
+import javax.security.enterprise.authentication.mechanism.http.AutoApplySession;
+import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
+import javax.security.enterprise.authentication.mechanism.http.HttpMessageContext;
+import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.IdentityStoreHandler;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.glassfish.soteria.mechanisms.LoginToContinueHolder;
+
+/**
+ * Authentication mechanism that ensures two successful authentications. This can be with any two identity stores.
+ *
+ * @author Mark Wareham
+ *
+ * @see fish.payara.security.annotations.TwoIdentityStoreAuthenticationMechanismDefinition
+ */
+@AutoApplySession
+@LoginToContinue
+@Typed(TwoIdentityStoreAuthenticationMechanism.class)
+public class TwoIdentityStoreAuthenticationMechanism implements HttpAuthenticationMechanism,LoginToContinueHolder {
+
+ private LoginToContinue loginToContinue;
+
+ @Inject
+ private TwoIdentityStoreAuthenticationMechanismState state;
+
+ @Override
+ public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext
+ httpMessageContext) throws AuthenticationException {
+
+ if (!hasCredential(httpMessageContext)) {
+ return httpMessageContext.doNothing();
+ }
+ IdentityStoreHandler identityStoreHandler = CDI.current().select(IdentityStoreHandler.class).get();
+ CredentialValidationResult currentRoundValidationResult = identityStoreHandler.validate(
+ httpMessageContext.getAuthParameters().getCredential());
+
+ //first ID Store
+ if (!state.isFirstIDStoreBeenAttempted()) {
+ state.setFirstValidationResult(currentRoundValidationResult);
+ return httpMessageContext.doNothing();
+ }
+
+ //second ID Store
+ CredentialValidationResult finalResult = collateResult(state.getFirstValidationResult(), currentRoundValidationResult);
+ this.state.clean();
+ return httpMessageContext.notifyContainerAboutLogin(finalResult);
+
+ }
+
+ private static boolean hasCredential(HttpMessageContext httpMessageContext) {
+ return httpMessageContext.getAuthParameters().getCredential() != null;
+ }
+
+ public TwoIdentityStoreAuthenticationMechanism loginToContinue(LoginToContinue loginToContinue) {
+ setLoginToContinue(loginToContinue);
+ return this;
+ }
+
+ @Override
+ public void cleanSubject(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) {
+ httpMessageContext.cleanClientSubject();
+ this.state.clean();
+ }
+
+ private CredentialValidationResult collateResult(
+ CredentialValidationResult firstValidationResult,
+ CredentialValidationResult secondValidationResult) {
+
+ if (firstValidationResult.getStatus() == CredentialValidationResult.Status.VALID
+ && secondValidationResult.getStatus() == CredentialValidationResult.Status.VALID) {
+ return firstValidationResult;
+ } else if (secondValidationResult.getStatus() != CredentialValidationResult.Status.VALID) {
+ return secondValidationResult;
+ } else {
+ return firstValidationResult;
+ }
+ }
+
+ @Override
+ public LoginToContinue getLoginToContinue() {
+ return loginToContinue;
+ }
+
+ public void setLoginToContinue(LoginToContinue loginToContinue) {
+ this.loginToContinue = loginToContinue;
+ }
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/TwoIdentityStoreAuthenticationMechanismState.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/TwoIdentityStoreAuthenticationMechanismState.java
new file mode 100644
index 00000000000..20a99e97b83
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/authentication/twoIdentityStore/TwoIdentityStoreAuthenticationMechanismState.java
@@ -0,0 +1,109 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ *
+ */
+
+package fish.payara.security.authentication.twoIdentityStore;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import javax.enterprise.context.SessionScoped;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+
+/**
+ * Maintains the state of the TwoIdentityStoreAuthenticationMechanism per session
+ *
+ * @author Mark Wareham
+ */
+
+@SessionScoped
+public class TwoIdentityStoreAuthenticationMechanismState implements Serializable {
+
+ private CredentialValidationResult firstValidationResult;
+ private boolean firstIDStoreBeenAttempted;
+
+ CredentialValidationResult getFirstValidationResult() {
+ return firstValidationResult;
+ }
+
+ void setFirstValidationResult(CredentialValidationResult firstValidationResult) {
+ this.firstValidationResult = firstValidationResult;
+ this.setFirstIDStoreBeenAttempted(true);
+ }
+
+ boolean isFirstIDStoreBeenAttempted() {
+ return firstIDStoreBeenAttempted;
+ }
+
+ void setFirstIDStoreBeenAttempted(boolean firstIDStoreBeenAttempted) {
+ this.firstIDStoreBeenAttempted = firstIDStoreBeenAttempted;
+ }
+
+ void clean() {
+ firstValidationResult = null;
+ firstIDStoreBeenAttempted = false;
+ }
+
+ private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
+ firstIDStoreBeenAttempted = aInputStream.readBoolean();
+ if(firstIDStoreBeenAttempted){
+ String statusString = aInputStream.readUTF();
+ String callerName = aInputStream.readUTF();
+ if(statusString.equals(CredentialValidationResult.Status.VALID.name())){
+ firstValidationResult = new CredentialValidationResult(callerName);
+ }else if(statusString.equals(CredentialValidationResult.Status.INVALID.name())){
+ firstValidationResult = CredentialValidationResult.INVALID_RESULT;
+ }else{
+ firstValidationResult = CredentialValidationResult.NOT_VALIDATED_RESULT;
+ }
+ }
+
+ }
+
+ private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
+ aOutputStream.writeBoolean(firstIDStoreBeenAttempted);
+ if(firstIDStoreBeenAttempted){
+ aOutputStream.writeUTF(firstValidationResult.getStatus().name());
+ aOutputStream.writeUTF(firstValidationResult.getCallerUniqueId());
+ }
+ }
+
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/ConfigRetriever.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/ConfigRetriever.java
new file mode 100644
index 00000000000..f03e0560668
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/ConfigRetriever.java
@@ -0,0 +1,121 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ *
+ */
+package fish.payara.security.identitystores;
+
+import org.glassfish.soteria.cdi.AnnotationELPProcessor;
+import java.util.Optional;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.glassfish.config.support.TranslatedConfigView;
+
+/**
+ * Class to retrieve value from config whether Microprofile, Payara/glassfish, EL, or a raw value. In that order.
+ * @author Mark Wareham
+ */
+public class ConfigRetriever {
+
+ private static final Config CONFIG = ConfigProvider.getConfig();
+
+ private ConfigRetriever(){}//class should not be instanciated.
+
+ /**
+ * Grabs the config value regardless of where it's stored.
+ * Should a microprofile config exist, that will be returned as top priority.
+ * Otherwise returns the value translated from one of
+ * - payara/glassfish config ${}
+ * - EL #{}
+ * - raw. Returns the value given if none of the above exist.
+ * @param expression the expression you are looking for
+ * @param microProfileConfigKey a microprofile key.
+ * @return a config value.
+ */
+ public static String resolveConfigAttribute(String expression, String microProfileConfigKey) {
+
+ Optional microProfileConfigValue = CONFIG.getOptionalValue(microProfileConfigKey, String.class);
+ if (microProfileConfigValue.isPresent()) {
+ return microProfileConfigValue.get();
+ }
+ if(isPayaraConfigFormat(expression)) {
+ String translatedValue = (String)TranslatedConfigView.getTranslatedValue(expression);
+ if(translatedValue.equals(expression) && isELImmediateFormat(expression)) {
+ return AnnotationELPProcessor.evalImmediate(expression);
+ }
+ return translatedValue;
+ }
+ if(isELDeferredFormat(expression)) {
+ return AnnotationELPProcessor.evalELExpression(expression);
+ }
+ return expression;
+ }
+
+ /**
+ * Grabs the config value regardless of where it's stored. Accounts for overriding expression parameters
+ * Should a microprofile config exist, that will be returned as top priority.
+ * Otherwise returns the value translated from one of
+ * - payara/glassfish config ${}
+ * - EL #{}
+ * - raw. Returns the value given if none of the above exist.
+ * @param attribute the attribute value to fall back on if there's no value expression param
+ * @param microProfileConfigKey a microprofile key.
+ * @param expression the expression you are looking for
+ * @return a config value.
+ */
+ public static String resolveConfigAttribute(String attribute, String expression, String microProfileConfigKey){
+ if(expression==null || expression.isEmpty()){
+ return resolveConfigAttribute(attribute, microProfileConfigKey);
+ }else{
+ return resolveConfigAttribute(expression, microProfileConfigKey);
+ }
+ }
+
+ private static boolean isPayaraConfigFormat(String attribute) {
+ return isELImmediateFormat(attribute);//payara format is the same as EL format.
+ }
+
+ private static boolean isELImmediateFormat(String attribute) {
+ return attribute!=null && attribute.startsWith("${") && attribute.endsWith("}");
+ }
+
+ private static boolean isELDeferredFormat(String attribute) {
+ return attribute!=null && attribute.startsWith("#{") && attribute.endsWith("}");
+ }
+
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubicoAPI.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubicoAPI.java
new file mode 100644
index 00000000000..c67ce3088d4
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubicoAPI.java
@@ -0,0 +1,57 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ *
+ */
+package fish.payara.security.identitystores;
+
+import com.yubico.client.v2.VerificationResponse;
+import com.yubico.client.v2.exceptions.YubicoValidationFailure;
+import com.yubico.client.v2.exceptions.YubicoVerificationException;
+
+/**
+ *
+ * @author Mark Wareham
+ */
+public interface YubicoAPI {
+
+ public void init(int apiClientID, String apiClientKey);
+ public VerificationResponse verify(String otp) throws YubicoVerificationException, YubicoValidationFailure;
+ public Integer getClientId();
+ public String[] getWsapiUrls();
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubicoAPIImpl.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubicoAPIImpl.java
new file mode 100644
index 00000000000..191e509c98b
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubicoAPIImpl.java
@@ -0,0 +1,108 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ *
+ */
+package fish.payara.security.identitystores;
+
+import com.yubico.client.v2.VerificationResponse;
+import com.yubico.client.v2.YubicoClient;
+import com.yubico.client.v2.exceptions.YubicoValidationFailure;
+import com.yubico.client.v2.exceptions.YubicoVerificationException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Alternative;
+
+/**
+ *
+ * @author Mark Wareham
+ */
+@ApplicationScoped
+@Alternative
+@Priority(1000)
+public class YubicoAPIImpl implements YubicoAPI {
+
+ private static final Logger LOG = Logger.getLogger(YubicoAPIImpl.class.getName());
+
+ private YubicoClient yubicoClient;
+
+ @Override
+ public void init(int apiClientID, String apiClientKey) {
+ if(apiClientID==0){
+ LOG.log(Level.WARNING, "A Yubico clientID has not been set");
+ }else{
+ LOG.log(Level.INFO, "Set up YubicoClient with clientID of {0}", apiClientID);
+ }
+ yubicoClient = YubicoClient.getClient(apiClientID, apiClientKey);
+ }
+
+ @Override
+ public VerificationResponse verify(String otp) throws YubicoVerificationException, YubicoValidationFailure {
+ performInitialisedCheck();
+ return yubicoClient.verify(otp);
+ }
+
+ @Override
+ public Integer getClientId() {
+ performInitialisedCheck();
+ return yubicoClient.getClientId();
+ }
+
+ @Override
+ public String[] getWsapiUrls() {
+ performInitialisedCheck();
+ return yubicoClient.getWsapiUrls();
+ }
+
+ private void performInitialisedCheck() {
+ if(yubicoClient==null){
+ throw new NotInitialisedException();
+ }
+ }
+
+ /**
+ * To represent a call on a YubicoAPIImpl that has not yet been initialised.
+ */
+ public static class NotInitialisedException extends RuntimeException {
+ public NotInitialisedException() {
+ //default constructor
+ }
+ }
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyCredential.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyCredential.java
new file mode 100644
index 00000000000..d28db8d8d6a
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyCredential.java
@@ -0,0 +1,81 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ *
+ */
+
+package fish.payara.security.identitystores;
+
+import javax.security.enterprise.credential.Credential;
+
+/**
+ * Class representing a Yubikey One Time Password (OTP)
+ *
+ * @author Mark Wareham
+*/
+public class YubikeyCredential implements Credential {
+
+ public static final int PUBLIC_ID_LENGTH = 12;
+ private String oneTimePasswordString;
+
+ public YubikeyCredential(String oneTimePasswordString) {
+ this.oneTimePasswordString = oneTimePasswordString;
+ }
+
+ public String getOneTimePasswordString() {
+ return oneTimePasswordString;
+ }
+
+ public void clearCredential (){
+ oneTimePasswordString=null;
+ }
+
+ public String getPublicID(){
+ String fullKey = getOneTimePasswordString();
+ if(fullKey==null){
+ return null;
+ }
+ if (fullKey.trim().isEmpty()){
+ return "";
+ }
+ if(fullKey.length()<12){
+ return fullKey;
+ }
+ return fullKey.substring(0, PUBLIC_ID_LENGTH);
+ }
+}
\ No newline at end of file
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyIdentityStore.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyIdentityStore.java
new file mode 100644
index 00000000000..e602868c3fd
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyIdentityStore.java
@@ -0,0 +1,172 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package fish.payara.security.identitystores;
+
+import static javax.security.enterprise.identitystore.IdentityStore.ValidationType.VALIDATE;
+import static fish.payara.security.identitystores.ConfigRetriever.resolveConfigAttribute;
+
+import fish.payara.security.annotations.YubikeyIdentityStoreDefinition;
+import com.yubico.client.v2.ResponseStatus;
+import com.yubico.client.v2.YubicoClient;
+import com.yubico.client.v2.exceptions.YubicoValidationFailure;
+import com.yubico.client.v2.exceptions.YubicoVerificationException;
+import fish.payara.notification.requesttracing.EventType;
+import fish.payara.notification.requesttracing.RequestTraceSpan;
+import fish.payara.nucleus.requesttracing.RequestTracingService;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.enterprise.inject.Typed;
+import javax.inject.Inject;
+import javax.security.enterprise.credential.Credential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.IdentityStore;
+import org.glassfish.internal.api.Globals;
+
+/**
+ * A Yubikey identity store. Supports connecting to the Yubico's cloud validation service. You must provide an API
+ * client ID and key for this service in the {@link YubikeyIdentityStoreDefinition}
+ * You can obtain one directly from Yubico at https://upgrade.yubico.com/getapikey/
+ *
+ * @author Mark Wareham
+ */
+@Typed(YubikeyIdentityStore.class)
+public class YubikeyIdentityStore implements IdentityStore {
+
+
+ private static final Logger LOG = Logger.getLogger(YubikeyIdentityStore.class.getName());
+
+ @Inject
+ private YubicoAPI yubicoAPI;
+
+ private RequestTracingService requestTracing;
+ private int priority; //priority is handled as immediate, not deferred
+
+ public YubikeyIdentityStore init (YubikeyIdentityStoreDefinition definition){
+ try {
+ this.requestTracing = Globals.get(RequestTracingService.class);
+ } catch (NullPointerException e) {
+ LOG.log(Level.INFO, "Error retrieving Request Tracing service "
+ + "during initialisation of Yubikey Identity Store - NullPointerException");
+ }
+ priority = definition.priority();
+ yubicoAPI.init(definition.yubikeyAPIClientID(), definition.yubikeyAPIKey());
+ return this;
+ }
+
+ @Override
+ public CredentialValidationResult validate(Credential credential) {
+
+ if (!(credential instanceof YubikeyCredential)) {
+ return CredentialValidationResult.NOT_VALIDATED_RESULT;
+ }
+ YubikeyCredential yubikeyCredential = ((YubikeyCredential) credential);
+ try {
+ String oneTimePassword = yubikeyCredential.getOneTimePasswordString();
+
+ if (!YubicoClient.isValidOTPFormat(oneTimePassword)) {
+ return CredentialValidationResult.INVALID_RESULT;
+ }
+ RequestTraceSpan span = beginTrace(yubikeyCredential);
+ ResponseStatus responseStatus = yubicoAPI.verify(oneTimePassword).getStatus();
+ doTrace(span, responseStatus);
+ LOG.log(Level.FINE, "Yubico server reported {0}", responseStatus.name());
+
+ switch (responseStatus) {
+ case BAD_OTP:
+ case REPLAYED_OTP:
+ case BAD_SIGNATURE:
+ case NO_SUCH_CLIENT:
+ return CredentialValidationResult.INVALID_RESULT;
+ case MISSING_PARAMETER:
+ case OPERATION_NOT_ALLOWED:
+ case BACKEND_ERROR:
+ case NOT_ENOUGH_ANSWERS:
+ case REPLAYED_REQUEST:
+ LOG.log(Level.WARNING, "Yubico reported {0}",
+ responseStatus.name());
+ return CredentialValidationResult.NOT_VALIDATED_RESULT;
+ case OK:
+ break;//carry on.
+ default:
+ LOG.log(Level.SEVERE, "Unknown/new yubico return status");
+ }
+
+ return new CredentialValidationResult(yubikeyCredential.getPublicID());
+
+ } catch (YubicoVerificationException | YubicoValidationFailure ex) {
+ LOG.log(Level.SEVERE, null, ex);
+ return CredentialValidationResult.NOT_VALIDATED_RESULT;
+ }
+ }
+
+ @Override
+ public Set validationTypes() {
+ return EnumSet.of(VALIDATE);
+ //This IdentityStore does not provide groups.
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ private RequestTraceSpan beginTrace(YubikeyCredential yubikeyCredential) {
+ if (requestTracing==null || !requestTracing.isRequestTracingEnabled()) {
+ return null;
+ }
+
+ RequestTraceSpan span = new RequestTraceSpan(EventType.REQUEST_EVENT, "verifyYubikeyCloudServiceRequest");
+ span.addSpanTag("API Client ID", ""+yubicoAPI.getClientId());
+ span.addSpanTag("Yubikey public ID", yubikeyCredential.getPublicID());
+ span.addSpanTag("Yubico validation URLs", Arrays.toString(yubicoAPI.getWsapiUrls()));
+ return span;
+ }
+
+ private void doTrace(RequestTraceSpan span, ResponseStatus responseStatus) {
+ if(span !=null){
+ span.addSpanTag("Yubico response status", responseStatus.name());
+ requestTracing.traceSpan(span);
+ }
+ }
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyIdentityStoreDefinitionAnnotationLiteral.java b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyIdentityStoreDefinitionAnnotationLiteral.java
new file mode 100644
index 00000000000..3d94f5cc84d
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/java/fish/payara/security/identitystores/YubikeyIdentityStoreDefinitionAnnotationLiteral.java
@@ -0,0 +1,117 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package fish.payara.security.identitystores;
+
+import fish.payara.security.annotations.YubikeyIdentityStoreDefinition;
+import javax.enterprise.util.AnnotationLiteral;
+
+import static fish.payara.security.identitystores.ConfigRetriever.resolveConfigAttribute;
+
+/**
+ * A literal representation of the {@link fish.payara.security.annotations.YubikeyIdentityStoreDefinitionDefinition}
+ *
+ * @author Mark Wareham
+ */
+@SuppressWarnings("AnnotationAsSuperInterface")
+public class YubikeyIdentityStoreDefinitionAnnotationLiteral extends AnnotationLiteral
+ implements YubikeyIdentityStoreDefinition {
+
+ private final int yubikeyAPIClientID;
+ private final String yubikeyAPIKey;
+ private final int priority;
+ private final String priorityExpression;
+ private final String yubikeyAPIClientIDExpression;
+
+ public YubikeyIdentityStoreDefinitionAnnotationLiteral(int yubikeyAPIClientID, String yubikeyAPIKey, int priority,
+ String priorityExpression, String yubikeyAPIClientIDExpression) {
+ this.yubikeyAPIClientID = yubikeyAPIClientID;
+ this.yubikeyAPIKey = yubikeyAPIKey;
+ this.priority = priority;
+ this.priorityExpression = priorityExpression;
+ this.yubikeyAPIClientIDExpression = yubikeyAPIClientIDExpression;
+ }
+
+ public static YubikeyIdentityStoreDefinition eval(YubikeyIdentityStoreDefinition yubikeyIdentityStoreDefinition) {
+ YubikeyIdentityStoreDefinitionAnnotationLiteral out = new YubikeyIdentityStoreDefinitionAnnotationLiteral(
+ Integer.parseInt(
+ resolveConfigAttribute(yubikeyIdentityStoreDefinition.yubikeyAPIClientID()+"",
+ yubikeyIdentityStoreDefinition.yubikeyAPIClientIDExpression(), YUBICO_API_CLIENT_ID)),
+
+ resolveConfigAttribute(yubikeyIdentityStoreDefinition.yubikeyAPIKey(), YUBICO_API_KEY),
+ Integer.parseInt(resolveConfigAttribute(yubikeyIdentityStoreDefinition.priority()+"",
+ yubikeyIdentityStoreDefinition.priorityExpression(), YUBIKEY_ID_STORE_PRIORITY)),
+ yubikeyIdentityStoreDefinition.priorityExpression(),
+ yubikeyIdentityStoreDefinition.yubikeyAPIClientIDExpression()
+ );
+ return out;
+ }
+
+ @Override
+ public int yubikeyAPIClientID() {
+ return yubikeyAPIClientID;
+ }
+
+ @Override
+ public String yubikeyAPIKey() {
+ return yubikeyAPIKey;
+ }
+
+ @Override
+ public int priority(){
+ return priority;
+ }
+
+ @Override
+ public String priorityExpression(){
+ return priorityExpression;
+ }
+
+ @Override
+ public String yubikeyAPIClientIDExpression() {
+ return yubikeyAPIClientIDExpression;
+ }
+
+ @Override
+ public String toString() {
+ return "YubikeyIdentityStoreDefinitionAnnotationLiteral{" + "yubikeyAPIClientID=" + yubikeyAPIClientID + ", yubikeyAPIKey=" + yubikeyAPIKey + ", priority=" + priority + ", priorityExpression=" + priorityExpression + ", yubikeyAPIClientIDExpression=" + yubikeyAPIClientIDExpression + '}';
+ }
+
+}
diff --git a/appserver/payara-appserver-modules/yubikey-authentication/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/appserver/payara-appserver-modules/yubikey-authentication/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 00000000000..b4160fec265
--- /dev/null
+++ b/appserver/payara-appserver-modules/yubikey-authentication/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+fish.payara.security.authentication.twoIdentityStore.CDIExtension
diff --git a/appserver/pom.xml b/appserver/pom.xml
index 94857b9b2ed..a32beaf2a4e 100644
--- a/appserver/pom.xml
+++ b/appserver/pom.xml
@@ -222,6 +222,8 @@
1.2.payara-p1
2.3
1.0-1
+
+ 3.0.2.payara-p1
@@ -687,12 +689,12 @@
javax.security.enterprise-api
${javax.security.enterprise-api.version}
-
+
org.glassfish.soteria
javax.security.enterprise
${javax.security.enterprise.version}
-
+
javax.security.jacc