This is an automated email from the ASF dual-hosted git repository. harikrishna pushed a commit to branch 2FA in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit d4ae91d16812f9f212568573f31c92c0022ab5ee Author: Harikrishna Patnala <[email protected]> AuthorDate: Tue Aug 23 08:54:55 2022 +0530 2FA Plugin changes --- .../main/java/com/cloud/user/AccountService.java | 16 +++++ .../auth/UserTwoFactorAuthenticator.java | 9 ++- ...ring-core-lifecycle-api-context-inheritable.xml | 4 +- .../src/main/resources/components-example.xml | 6 +- .../management/IntegrationTestConfiguration.java | 2 +- plugins/pom.xml | 10 +++ .../apache/cloudstack/ldap/LdapAuthenticator.java | 2 +- .../cloudstack/ldap/LdapAuthenticatorSpec.groovy | 2 +- .../cloudstack/ldap/LdapAuthenticatorTest.java | 2 +- .../cloudstack}/auth/MD5UserAuthenticator.java | 2 +- .../META-INF/cloudstack/md5/spring-md5-context.xml | 2 +- .../cloudstack}/auth/MD5UserAuthenticatorTest.java | 9 ++- .../server/auth/PBKDF2UserAuthenticator.java | 2 +- .../server/auth/PBKD2UserAuthenticatorTest.java | 2 +- .../auth/PlainTextUserAuthenticator.java | 2 +- .../plaintext/spring-plaintext-context.xml | 2 +- .../cloudstack/saml/SAML2UserAuthenticator.java | 2 +- .../cloudstack/SAML2UserAuthenticatorTest.java | 2 +- .../auth/SHA256SaltedUserAuthenticator.java | 2 +- .../sha256salted/spring-sha256salted-context.xml | 2 +- .../cloudstack}/auth/test/AuthenticatorTest.java | 4 +- .../auth/GoogleUserTwoFactorAuthenticator.java | 32 ---------- .../auth/GoogleUserTwoFactorAuthenticator.java | 71 ++++++++++++++++++++++ .../cloudstack/plaintext/spring-google-context.xml | 2 +- .../auth/StaticPinUserTwoFactorAuthenticator.java | 7 ++- .../static-pin/spring-static-pin-context.xml | 2 +- .../com/cloud/server/ManagementServerImpl.java | 2 +- .../main/java/com/cloud/user/AccountManager.java | 13 ++++ .../java/com/cloud/user/AccountManagerImpl.java | 49 ++++++++++++++- .../apache/cloudstack}/auth/UserAuthenticator.java | 2 +- .../core/spring-server-core-managers-context.xml | 1 + server/src/test/async-job-component.xml | 4 +- .../com/cloud/user/AccountManagerImplTest.java | 4 +- .../com/cloud/user/AccountManagetImplTestBase.java | 2 +- 34 files changed, 204 insertions(+), 73 deletions(-) diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index 863801ec826..200846c1087 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.user; +import java.util.List; import java.util.Map; import org.apache.cloudstack.acl.ControlledEntity; @@ -33,6 +34,7 @@ import com.cloud.network.vpc.VpcOffering; import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; +import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; public interface AccountService { @@ -124,4 +126,18 @@ public interface AccountService { public Map<String, String> getKeys(GetUserKeysCmd cmd); public Map<String, String> getKeys(Long userId); + + /** + * Lists user two-factor authentication provider plugins + * @return list of providers + */ + List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders(); + + /** + * Find user two factor authenticator provider by domain ID + * @param domainId domain id + * @return backup provider + */ + UserTwoFactorAuthenticator getUserTwoFactorAuthenticationProvider(final Long domainId); + } diff --git a/server/src/main/java/com/cloud/server/auth/UserTwoFactorAuthenticator.java b/api/src/main/java/org/apache/cloudstack/auth/UserTwoFactorAuthenticator.java similarity index 74% rename from server/src/main/java/com/cloud/server/auth/UserTwoFactorAuthenticator.java rename to api/src/main/java/org/apache/cloudstack/auth/UserTwoFactorAuthenticator.java index da7d56b178a..f7257a0d18e 100644 --- a/server/src/main/java/com/cloud/server/auth/UserTwoFactorAuthenticator.java +++ b/api/src/main/java/org/apache/cloudstack/auth/UserTwoFactorAuthenticator.java @@ -14,9 +14,14 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package com.cloud.server.auth; +package org.apache.cloudstack.auth; +import com.cloud.exception.CloudAuthenticationException; +import com.cloud.user.UserAccount; import com.cloud.utils.component.Adapter; -public interface UserTwoFactorAuthenticator extends Adapter { +public interface UserTwoFactorAuthenticator extends Adapter { + + void check2FA(String code, UserAccount userAccount) throws CloudAuthenticationException; + } diff --git a/core/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml index 655b7fe6572..5790081a347 100644 --- a/core/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml +++ b/core/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml @@ -32,7 +32,7 @@ <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle"> <property name="registry" ref="userAuthenticatorsRegistry" /> <property name="typeClass" - value="com.cloud.server.auth.UserAuthenticator" /> + value="org.apache.cloudstack.auth.UserAuthenticator" /> </bean> <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle"> @@ -64,7 +64,7 @@ <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle"> <property name="registry" ref="userPasswordEncodersRegistry" /> - <property name="typeClass" value="com.cloud.server.auth.UserAuthenticator" /> + <property name="typeClass" value="org.apache.cloudstack.auth.UserAuthenticator" /> </bean> </beans> diff --git a/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml b/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml index 9d1b1200776..2a8016960f0 100755 --- a/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml +++ b/plugins/network-elements/dns-notifier/src/main/resources/components-example.xml @@ -87,9 +87,9 @@ under the License. <adapters key="com.cloud.network.IpAddrAllocator"> <adapter name="Basic" class="com.cloud.network.ExternalIpAddressAllocator"/> </adapters> - <adapters key="com.cloud.server.auth.UserAuthenticator"> - <!-- <adapter name="SHA256SALT" class="com.cloud.server.auth.SHA256SaltedUserAuthenticator"/> --> - <adapter name="MD5" class="com.cloud.server.auth.MD5UserAuthenticator"/> + <adapters key="org.apache.cloudstack.auth.UserAuthenticator"> + <!-- <adapter name="SHA256SALT" class="org.apache.cloudstack.auth.SHA256SaltedUserAuthenticator"/> --> + <adapter name="MD5" class="org.apache.cloudstack.auth.MD5UserAuthenticator"/> <adapter name="LDAP" class="com.cloud.server.auth.LDAPUserAuthenticator"/> </adapters> <adapters key="com.cloud.ha.Investigator"> diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java index 989d74b6c95..51d3fe3bac0 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java @@ -239,7 +239,7 @@ import com.cloud.server.ManagementServer; import com.cloud.server.ResourceMetaDataService; import com.cloud.server.StatsCollector; import com.cloud.server.TaggedResourceService; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.service.dao.ServiceOfferingDaoImpl; import com.cloud.service.dao.ServiceOfferingDetailsDaoImpl; import com.cloud.storage.DataStoreProviderApiService; diff --git a/plugins/pom.xml b/plugins/pom.xml index 95ad47418d9..08a3253ed4c 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -159,6 +159,16 @@ <artifactId>cloud-framework-config</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>de.taimos</groupId> + <artifactId>totp</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>com.google.zxing</groupId> + <artifactId>javase</artifactId> + <version>3.2.1</version> + </dependency> <dependency> <groupId>org.apache.cloudstack</groupId> <artifactId>cloud-api</artifactId> diff --git a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java index fc6c32f7213..f80dbad6c73 100644 --- a/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java +++ b/plugins/user-authenticators/ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.User; diff --git a/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy b/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy index 37a0aba8adb..e35938fae5d 100644 --- a/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy +++ b/plugins/user-authenticators/ldap/src/test/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy @@ -16,7 +16,7 @@ // under the License. package groovy.org.apache.cloudstack.ldap -import com.cloud.server.auth.UserAuthenticator +import org.apache.cloudstack.auth.UserAuthenticator import com.cloud.user.Account import com.cloud.user.AccountManager import com.cloud.user.User diff --git a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapAuthenticatorTest.java b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapAuthenticatorTest.java index 6852d5e2f3a..604540a9cea 100644 --- a/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapAuthenticatorTest.java +++ b/plugins/user-authenticators/ldap/src/test/java/org/apache/cloudstack/ldap/LdapAuthenticatorTest.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.ldap; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; import com.cloud.user.Account; diff --git a/plugins/user-authenticators/md5/src/main/java/com/cloud/server/auth/MD5UserAuthenticator.java b/plugins/user-authenticators/md5/src/main/java/org/apache/cloudstack/auth/MD5UserAuthenticator.java similarity index 98% rename from plugins/user-authenticators/md5/src/main/java/com/cloud/server/auth/MD5UserAuthenticator.java rename to plugins/user-authenticators/md5/src/main/java/org/apache/cloudstack/auth/MD5UserAuthenticator.java index 8398c6c3fc5..cb4f076760a 100644 --- a/plugins/user-authenticators/md5/src/main/java/com/cloud/server/auth/MD5UserAuthenticator.java +++ b/plugins/user-authenticators/md5/src/main/java/org/apache/cloudstack/auth/MD5UserAuthenticator.java @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.cloud.server.auth; +package org.apache.cloudstack.auth; import java.math.BigInteger; import java.security.MessageDigest; diff --git a/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/spring-md5-context.xml b/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/spring-md5-context.xml index 782b1132718..132f1481bb6 100644 --- a/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/spring-md5-context.xml +++ b/plugins/user-authenticators/md5/src/main/resources/META-INF/cloudstack/md5/spring-md5-context.xml @@ -27,7 +27,7 @@ http://www.springframework.org/schema/context/spring-context.xsd" > - <bean id="MD5UserAuthenticator" class="com.cloud.server.auth.MD5UserAuthenticator"> + <bean id="MD5UserAuthenticator" class="org.apache.cloudstack.auth.MD5UserAuthenticator"> <property name="name" value="MD5"/> </bean> diff --git a/plugins/user-authenticators/md5/src/test/java/com/cloud/server/auth/MD5UserAuthenticatorTest.java b/plugins/user-authenticators/md5/src/test/java/org/apache/cloudstack/auth/MD5UserAuthenticatorTest.java similarity index 87% rename from plugins/user-authenticators/md5/src/test/java/com/cloud/server/auth/MD5UserAuthenticatorTest.java rename to plugins/user-authenticators/md5/src/test/java/org/apache/cloudstack/auth/MD5UserAuthenticatorTest.java index a0189e19720..78af8e532f0 100644 --- a/plugins/user-authenticators/md5/src/test/java/com/cloud/server/auth/MD5UserAuthenticatorTest.java +++ b/plugins/user-authenticators/md5/src/test/java/org/apache/cloudstack/auth/MD5UserAuthenticatorTest.java @@ -17,7 +17,7 @@ * under the License. */ -package com.cloud.server.auth; +package org.apache.cloudstack.auth; import java.lang.reflect.Field; @@ -28,7 +28,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; -import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication; import com.cloud.user.UserAccountVO; import com.cloud.user.dao.UserAccountDao; import com.cloud.utils.Pair; @@ -53,7 +52,7 @@ public class MD5UserAuthenticatorTest { UserAccountVO account = new UserAccountVO(); account.setPassword("5f4dcc3b5aa765d61d8327deb882cf99"); Mockito.when(dao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(account); - Pair<Boolean, ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null); + Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null); Assert.assertTrue(pair.first()); } @@ -66,7 +65,7 @@ public class MD5UserAuthenticatorTest { UserAccountVO account = new UserAccountVO(); account.setPassword("surprise"); Mockito.when(dao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(account); - Pair<Boolean, ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null); + Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null); Assert.assertFalse(pair.first()); } @@ -77,7 +76,7 @@ public class MD5UserAuthenticatorTest { daoField.setAccessible(true); daoField.set(authenticator, dao); Mockito.when(dao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(null); - Pair<Boolean, ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null); + Pair<Boolean, UserAuthenticator.ActionOnFailedAuthentication> pair = authenticator.authenticate("admin", "password", 1l, null); Assert.assertFalse(pair.first()); } } diff --git a/plugins/user-authenticators/pbkdf2/src/main/java/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java b/plugins/user-authenticators/pbkdf2/src/main/java/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java index 733bde734fc..d20f4a4c6c8 100644 --- a/plugins/user-authenticators/pbkdf2/src/main/java/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java +++ b/plugins/user-authenticators/pbkdf2/src/main/java/org/apache/cloudstack/server/auth/PBKDF2UserAuthenticator.java @@ -32,7 +32,7 @@ import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.util.encoders.Base64; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.user.UserAccount; import com.cloud.user.dao.UserAccountDao; import com.cloud.utils.ConstantTimeComparator; diff --git a/plugins/user-authenticators/pbkdf2/src/test/java/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java b/plugins/user-authenticators/pbkdf2/src/test/java/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java index f4014167cff..21d4746abe1 100644 --- a/plugins/user-authenticators/pbkdf2/src/test/java/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java +++ b/plugins/user-authenticators/pbkdf2/src/test/java/org/apache/cloudstack/server/auth/PBKD2UserAuthenticatorTest.java @@ -15,7 +15,7 @@ package org.apache.cloudstack.server.auth; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.user.UserAccountVO; import com.cloud.user.dao.UserAccountDao; import com.cloud.utils.Pair; diff --git a/plugins/user-authenticators/plain-text/src/main/java/com/cloud/server/auth/PlainTextUserAuthenticator.java b/plugins/user-authenticators/plain-text/src/main/java/org/apache/cloudstack/auth/PlainTextUserAuthenticator.java similarity index 98% rename from plugins/user-authenticators/plain-text/src/main/java/com/cloud/server/auth/PlainTextUserAuthenticator.java rename to plugins/user-authenticators/plain-text/src/main/java/org/apache/cloudstack/auth/PlainTextUserAuthenticator.java index 3740d702ca1..ce23010614d 100644 --- a/plugins/user-authenticators/plain-text/src/main/java/com/cloud/server/auth/PlainTextUserAuthenticator.java +++ b/plugins/user-authenticators/plain-text/src/main/java/org/apache/cloudstack/auth/PlainTextUserAuthenticator.java @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.cloud.server.auth; +package org.apache.cloudstack.auth; import java.util.Map; diff --git a/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/spring-plaintext-context.xml b/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/spring-plaintext-context.xml index 674bfc72b04..fccff88c4cb 100644 --- a/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/spring-plaintext-context.xml +++ b/plugins/user-authenticators/plain-text/src/main/resources/META-INF/cloudstack/plaintext/spring-plaintext-context.xml @@ -27,7 +27,7 @@ http://www.springframework.org/schema/context/spring-context.xsd" > - <bean id="PlainTextUserAuthenticator" class="com.cloud.server.auth.PlainTextUserAuthenticator"> + <bean id="PlainTextUserAuthenticator" class="org.apache.cloudstack.auth.PlainTextUserAuthenticator"> <property name="name" value="PLAINTEXT" /> </bean> diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2UserAuthenticator.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2UserAuthenticator.java index a1a8e7a0a73..23eaac16bfe 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2UserAuthenticator.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2UserAuthenticator.java @@ -21,7 +21,7 @@ import javax.inject.Inject; import org.apache.cxf.common.util.StringUtils; import org.apache.log4j.Logger; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.user.User; import com.cloud.user.UserAccount; import com.cloud.user.dao.UserAccountDao; diff --git a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAML2UserAuthenticatorTest.java b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAML2UserAuthenticatorTest.java index c0f61d729df..fedaf00bc2a 100644 --- a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAML2UserAuthenticatorTest.java +++ b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAML2UserAuthenticatorTest.java @@ -32,7 +32,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication; +import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication; import com.cloud.user.UserAccountVO; import com.cloud.user.UserVO; import com.cloud.user.dao.UserAccountDao; diff --git a/plugins/user-authenticators/sha256salted/src/main/java/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java b/plugins/user-authenticators/sha256salted/src/main/java/org/apache/cloudstack/auth/SHA256SaltedUserAuthenticator.java similarity index 99% rename from plugins/user-authenticators/sha256salted/src/main/java/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java rename to plugins/user-authenticators/sha256salted/src/main/java/org/apache/cloudstack/auth/SHA256SaltedUserAuthenticator.java index 0b87bd445fd..a02305e4676 100644 --- a/plugins/user-authenticators/sha256salted/src/main/java/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java +++ b/plugins/user-authenticators/sha256salted/src/main/java/org/apache/cloudstack/auth/SHA256SaltedUserAuthenticator.java @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package com.cloud.server.auth; +package org.apache.cloudstack.auth; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; diff --git a/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/spring-sha256salted-context.xml b/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/spring-sha256salted-context.xml index 53b1a40f0d3..3e29fd9ddba 100644 --- a/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/spring-sha256salted-context.xml +++ b/plugins/user-authenticators/sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/spring-sha256salted-context.xml @@ -27,7 +27,7 @@ http://www.springframework.org/schema/context/spring-context.xsd" > - <bean id="SHA256SaltedUserAuthenticator" class="com.cloud.server.auth.SHA256SaltedUserAuthenticator"> + <bean id="SHA256SaltedUserAuthenticator" class="org.apache.cloudstack.auth.SHA256SaltedUserAuthenticator"> <property name="name" value="SHA256SALT"/> </bean> diff --git a/plugins/user-authenticators/sha256salted/src/test/java/com/cloud/server/auth/test/AuthenticatorTest.java b/plugins/user-authenticators/sha256salted/src/test/java/org/apache/cloudstack/auth/test/AuthenticatorTest.java similarity index 97% rename from plugins/user-authenticators/sha256salted/src/test/java/com/cloud/server/auth/test/AuthenticatorTest.java rename to plugins/user-authenticators/sha256salted/src/test/java/org/apache/cloudstack/auth/test/AuthenticatorTest.java index f770b74cd9e..3450254f486 100644 --- a/plugins/user-authenticators/sha256salted/src/test/java/com/cloud/server/auth/test/AuthenticatorTest.java +++ b/plugins/user-authenticators/sha256salted/src/test/java/org/apache/cloudstack/auth/test/AuthenticatorTest.java @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package com.cloud.server.auth.test; +package org.apache.cloudstack.auth.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -29,6 +29,7 @@ import java.util.Map; import javax.naming.ConfigurationException; +import org.apache.cloudstack.auth.SHA256SaltedUserAuthenticator; import org.bouncycastle.util.encoders.Base64; import org.junit.Before; import org.junit.Test; @@ -37,7 +38,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import com.cloud.server.auth.SHA256SaltedUserAuthenticator; import com.cloud.user.UserAccount; import com.cloud.user.dao.UserAccountDao; diff --git a/plugins/user-two-factor-authenticators/google/src/main/java/com/cloud/server/auth/GoogleUserTwoFactorAuthenticator.java b/plugins/user-two-factor-authenticators/google/src/main/java/com/cloud/server/auth/GoogleUserTwoFactorAuthenticator.java deleted file mode 100644 index 6374afb2106..00000000000 --- a/plugins/user-two-factor-authenticators/google/src/main/java/com/cloud/server/auth/GoogleUserTwoFactorAuthenticator.java +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.cloud.server.auth; - - -import javax.inject.Inject; - -import org.apache.log4j.Logger; - -import com.cloud.user.dao.UserAccountDao; -import com.cloud.utils.component.AdapterBase; - -public class GoogleUserTwoFactorAuthenticator extends AdapterBase implements UserTwoFactorAuthenticator { - public static final Logger s_logger = Logger.getLogger(GoogleUserTwoFactorAuthenticator.class); - - @Inject - private UserAccountDao _userAccountDao; - -} diff --git a/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java b/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java new file mode 100644 index 00000000000..1d771ad62b1 --- /dev/null +++ b/plugins/user-two-factor-authenticators/google/src/main/java/org/apache/cloudstack/auth/GoogleUserTwoFactorAuthenticator.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.apache.cloudstack.auth; + + +import javax.inject.Inject; + +import de.taimos.totp.TOTP; + +import com.cloud.exception.CloudAuthenticationException; +import com.cloud.user.UserAccount; +import org.apache.commons.codec.binary.Base32; +import org.apache.commons.codec.binary.Hex; +import org.apache.log4j.Logger; + +import com.cloud.user.dao.UserAccountDao; +import com.cloud.utils.component.AdapterBase; + +public class GoogleUserTwoFactorAuthenticator extends AdapterBase implements UserTwoFactorAuthenticator { + public static final Logger s_logger = Logger.getLogger(GoogleUserTwoFactorAuthenticator.class); + + @Inject + private UserAccountDao _userAccountDao; + + @Override + public void check2FA(String code, UserAccount userAccount) throws CloudAuthenticationException { + // TODO: in future get userAccount specific 2FA key + String expectedCode = get2FACode(get2FAKey()); + if (expectedCode.equals(code)) { + s_logger.info("2FA matches user's input"); + return; + } + throw new CloudAuthenticationException("two-factor authentication has failed for the user"); + } + + public static String get2FAKey() { + return "7t4gabg72liipmq7n43lt3cw66fel4iz"; + /* + This logic can be replaced on per-user-account basis + where the key is generated to show the user one-time QR code, + and then stored in DB. + For #CCC21 hackathon, we'll take shortcuts ;) + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[20]; + random.nextBytes(bytes); + Base32 base32 = new Base32(); + return base32.encodeToString(bytes); + */ + } + + public static String get2FACode(String secretKey) { + Base32 base32 = new Base32(); + byte[] bytes = base32.decode(secretKey); + String hexKey = Hex.encodeHexString(bytes); + return TOTP.getOTP(hexKey); + } + +} diff --git a/plugins/user-two-factor-authenticators/google/src/main/resources/META-INF/cloudstack/plaintext/spring-google-context.xml b/plugins/user-two-factor-authenticators/google/src/main/resources/META-INF/cloudstack/plaintext/spring-google-context.xml index ac4b731dbae..7c63e6b0226 100644 --- a/plugins/user-two-factor-authenticators/google/src/main/resources/META-INF/cloudstack/plaintext/spring-google-context.xml +++ b/plugins/user-two-factor-authenticators/google/src/main/resources/META-INF/cloudstack/plaintext/spring-google-context.xml @@ -27,7 +27,7 @@ http://www.springframework.org/schema/context/spring-context.xsd" > - <bean id="GoogleUserTwoFactorAuthenticator" class="com.cloud.server.auth.GoogleUserTwoFactorAuthenticator"> + <bean id="GoogleUserTwoFactorAuthenticator" class="org.apache.cloudstack.auth.GoogleUserTwoFactorAuthenticator"> <property name="name" value="GOOGLE" /> </bean> diff --git a/plugins/user-two-factor-authenticators/static-pin/src/main/java/com/cloud/server/auth/StaticPinUserTwoFactorAuthenticator.java b/plugins/user-two-factor-authenticators/static-pin/src/main/java/org/apache/cloudstack/auth/StaticPinUserTwoFactorAuthenticator.java similarity index 82% rename from plugins/user-two-factor-authenticators/static-pin/src/main/java/com/cloud/server/auth/StaticPinUserTwoFactorAuthenticator.java rename to plugins/user-two-factor-authenticators/static-pin/src/main/java/org/apache/cloudstack/auth/StaticPinUserTwoFactorAuthenticator.java index f5dd688a8a8..9e80f8aab1f 100644 --- a/plugins/user-two-factor-authenticators/static-pin/src/main/java/com/cloud/server/auth/StaticPinUserTwoFactorAuthenticator.java +++ b/plugins/user-two-factor-authenticators/static-pin/src/main/java/org/apache/cloudstack/auth/StaticPinUserTwoFactorAuthenticator.java @@ -13,10 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.cloud.server.auth; +package org.apache.cloudstack.auth; import javax.inject.Inject; +import com.cloud.exception.CloudAuthenticationException; +import com.cloud.user.UserAccount; import org.apache.log4j.Logger; import com.cloud.user.dao.UserAccountDao; @@ -28,4 +30,7 @@ public class StaticPinUserTwoFactorAuthenticator extends AdapterBase implements @Inject private UserAccountDao _userAccountDao; + @Override + public void check2FA(String code, UserAccount userAccount) throws CloudAuthenticationException { + } } diff --git a/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/static-pin/spring-static-pin-context.xml b/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/static-pin/spring-static-pin-context.xml index 0dc5cfa6d44..89c1d5a2be2 100644 --- a/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/static-pin/spring-static-pin-context.xml +++ b/plugins/user-two-factor-authenticators/static-pin/src/main/resources/META-INF/cloudstack/static-pin/spring-static-pin-context.xml @@ -27,7 +27,7 @@ http://www.springframework.org/schema/context/spring-context.xsd" > - <bean id="StaticPinUserTwoFactorAuthenticator" class="com.cloud.server.auth.StaticPinUserTwoFactorAuthenticator"> + <bean id="StaticPinUserTwoFactorAuthenticator" class="org.apache.cloudstack.auth.StaticPinUserTwoFactorAuthenticator"> <property name="name" value="StaticPin" /> </bean> diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 9f1b5fe78fa..bdde132eb50 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -706,7 +706,7 @@ import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.projects.ProjectManager; import com.cloud.resource.ResourceManager; import com.cloud.server.ResourceTag.ResourceObjectType; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; diff --git a/server/src/main/java/com/cloud/user/AccountManager.java b/server/src/main/java/com/cloud/user/AccountManager.java index a4913feba81..0c5407c1495 100644 --- a/server/src/main/java/com/cloud/user/AccountManager.java +++ b/server/src/main/java/com/cloud/user/AccountManager.java @@ -187,5 +187,18 @@ public interface AccountManager extends AccountService, Configurable { ConfigKey<Boolean> UseSecretKeyInResponse = new ConfigKey<Boolean>("Advanced", Boolean.class, "use.secret.key.in.response", "false", "This parameter allows the users to enable or disable of showing secret key as a part of response for various APIs. By default it is set to false.", true); + ConfigKey<Boolean> enable2FA = new ConfigKey<Boolean>("Advanced", + Boolean.class, + "enable.two.factor.authentication", + "true", + "Determines whether two factor authentication is enabled or not. This can be done at domain level as well", + true, + ConfigKey.Scope.Domain); + + ConfigKey<String> userTwoFactorAuthenticationProviderPlugin = new ConfigKey<>("Advanced", String.class, + "user.two.factor.authentication.provider.plugin", + "google", + "The user two factor authentication provider plugin. Eg. google, static-pin", true, ConfigKey.Scope.Domain); + boolean moveUser(long id, Long domainId, Account newAccount); } diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 3c1e2618df9..c206c8c1c0b 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -59,6 +59,8 @@ import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.api.command.admin.user.RegisterCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; +import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; +import org.apache.cloudstack.backup.BackupProvider; import org.apache.cloudstack.config.ApiServiceConfiguration; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -139,8 +141,8 @@ import com.cloud.projects.ProjectVO; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; import com.cloud.region.ha.GlobalLoadBalancingRulesService; -import com.cloud.server.auth.UserAuthenticator; -import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication; +import org.apache.cloudstack.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeApiService; import com.cloud.storage.VolumeVO; @@ -315,6 +317,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M private int _cleanupInterval; private List<String> apiNameList; + private static Map<String, UserTwoFactorAuthenticator> userTwoFactorAuthenticationProvidersMap = new HashMap<>(); + + private List<UserTwoFactorAuthenticator> userTwoFactorAuthenticationProviders; + protected AccountManagerImpl() { super(); } @@ -400,6 +406,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M @Override public boolean start() { + + initializeUserTwoFactorAuthenticationProvidersMap(); + if (apiNameList == null) { long startTime = System.nanoTime(); apiNameList = new ArrayList<String>(); @@ -2636,6 +2645,27 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M return keys; } + @Override + public List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders() { + return userTwoFactorAuthenticationProviders; + } + + @Override + public UserTwoFactorAuthenticator getUserTwoFactorAuthenticationProvider(Long domainId) { + final String name = userTwoFactorAuthenticationProviderPlugin.valueIn(domainId); + return getUserTwoFactorAuthenticationProvider(name); + } + + public UserTwoFactorAuthenticator getUserTwoFactorAuthenticationProvider(final String name) { + if (StringUtils.isEmpty(name)) { + throw new CloudRuntimeException("Invalid two factor authentication provider name provided"); + } + if (!userTwoFactorAuthenticationProvidersMap.containsKey(name)) { + throw new CloudRuntimeException("Failed to find two factor authentication provider by the name: " + name); + } + return userTwoFactorAuthenticationProvidersMap.get(name); + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY, eventDescription = "register for the developer API keys") @@ -3100,6 +3130,19 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M @Override public ConfigKey<?>[] getConfigKeys() { - return new ConfigKey<?>[] {UseSecretKeyInResponse}; + return new ConfigKey<?>[] {UseSecretKeyInResponse, enable2FA, userTwoFactorAuthenticationProviderPlugin}; + } + + public void setUserTwoFactorAuthenticationProviders(final List<UserTwoFactorAuthenticator> userTwoFactorAuthenticationProviders) { + this.userTwoFactorAuthenticationProviders = userTwoFactorAuthenticationProviders; } + + private void initializeUserTwoFactorAuthenticationProvidersMap() { + if (userTwoFactorAuthenticationProviders != null) { + for (final UserTwoFactorAuthenticator userTwoFactorAuthenticator : userTwoFactorAuthenticationProviders) { + userTwoFactorAuthenticationProvidersMap.put(userTwoFactorAuthenticator.getName().toLowerCase(), userTwoFactorAuthenticator); + } + } + } + } diff --git a/server/src/main/java/com/cloud/server/auth/UserAuthenticator.java b/server/src/main/java/org/apache/cloudstack/auth/UserAuthenticator.java similarity index 97% rename from server/src/main/java/com/cloud/server/auth/UserAuthenticator.java rename to server/src/main/java/org/apache/cloudstack/auth/UserAuthenticator.java index 895c3c06a61..36d591c3675 100644 --- a/server/src/main/java/com/cloud/server/auth/UserAuthenticator.java +++ b/server/src/main/java/org/apache/cloudstack/auth/UserAuthenticator.java @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package com.cloud.server.auth; +package org.apache.cloudstack.auth; import java.util.Map; diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 5e75388547c..8997e26a1ed 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -50,6 +50,7 @@ <property name="querySelectors" value="#{querySelectorsRegistry.registered}" /> <property name="apiAccessCheckers" value="#{apiAclCheckersRegistry.registered}" /> <property name="services" value="#{apiCommandsRegistry.registered}" /> + <property name="userTwoFactorAuthenticationProviders" value="#{userTwoFactorAuthenticatorsRegistry.registered}" /> </bean> <bean id="passwordPolicies" class="com.cloud.user.PasswordPolicyImpl" /> diff --git a/server/src/test/async-job-component.xml b/server/src/test/async-job-component.xml index 413194c6718..888982ea76b 100644 --- a/server/src/test/async-job-component.xml +++ b/server/src/test/async-job-component.xml @@ -123,8 +123,8 @@ </adapters> - <adapters key="com.cloud.server.auth.UserAuthenticator"> - <adapter name="MD5" class="com.cloud.server.auth.MD5UserAuthenticator" /> + <adapters key="org.apache.cloudstack.auth.UserAuthenticator"> + <adapter name="MD5" class="org.apache.cloudstack.auth.MD5UserAuthenticator" /> </adapters> <adapters key="com.cloud.ha.Investigator"> <adapter name="SimpleInvestigator" class="com.cloud.ha.CheckOnAgentInvestigator" /> diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java index b695e512a6a..4d948dda1c5 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplTest.java @@ -44,8 +44,8 @@ import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.projects.Project; import com.cloud.projects.ProjectAccountVO; -import com.cloud.server.auth.UserAuthenticator; -import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication; +import org.apache.cloudstack.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication; import com.cloud.user.Account.State; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; diff --git a/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java b/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java index e26b390c203..d0a70faea5e 100644 --- a/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java +++ b/server/src/test/java/com/cloud/user/AccountManagetImplTestBase.java @@ -60,7 +60,7 @@ import com.cloud.network.vpn.Site2SiteVpnManager; import com.cloud.projects.ProjectManager; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; -import com.cloud.server.auth.UserAuthenticator; +import org.apache.cloudstack.auth.UserAuthenticator; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.VolumeApiService; import com.cloud.storage.dao.SnapshotDao;
