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