[java-idp-plugin-totp] branch master updated: Working and tested seed source and validator.
Scott Cantor
cantor.2 at osu.edu
Mon Jul 13 21:28:50 UTC 2020
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-idp-plugin-totp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-totp.git;a=commit;h=6c7a8207854e6e91662d3120809c8c802ac7a992
The following commit(s) were added to refs/heads/master by this push:
new 6c7a820 Working and tested seed source and validator.
6c7a820 is described below
commit 6c7a8207854e6e91662d3120809c8c802ac7a992
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jul 13 17:28:53 2020 -0400
Working and tested seed source and validator.
---
pom.xml | 7 +
totp-impl/.checkstyle | 6 +-
totp-impl/pom.xml | 64 +++++++-
.../idp/plugin/totp/TOTPDescription.java | 82 ++++++++++
.../idp/plugin/totp/context/TOTPContext.java | 58 -------
.../totp/impl/AbstractTOTPCredentialValidator.java | 164 +++++++++++++++++++
.../totp/impl/AttributeResolverSeedSource.java | 2 +-
.../GoogleAuthenticatorCredentialValidator.java | 102 ++++++++++++
.../shibboleth/idp/plugin/totp/package-info.java | 22 +++
.../0.0.1/version.details.properties | 3 +
.../versions.properties | 1 +
...utilities.java.support.plugin.PluginDescription | 1 +
.../net/shibboleth/idp/plugin/totp/PluginTest.java | 61 +++++++
.../totp/impl/AttributeResolverSeedSourceTest.java | 156 ++++++++++++++++++
...GoogleAuthenticatorCredentialValidatorTest.java | 180 +++++++++++++++++++++
15 files changed, 843 insertions(+), 66 deletions(-)
diff --git a/pom.xml b/pom.xml
index d1a3386..a28c086 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-tests-bom</artifactId>
+ <version>${idp.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
diff --git a/totp-impl/.checkstyle b/totp-impl/.checkstyle
index 7c63424..c9c3e58 100644
--- a/totp-impl/.checkstyle
+++ b/totp-impl/.checkstyle
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
-<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
+<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
<local-check-config name="Shibboleth Checkstyle" location="/java-idp-plugin-totp/checkstyle.xml" type="project" description="">
<additional-data name="protect-config-file" value="false"/>
</local-check-config>
- <fileset name="all" enabled="true" check-config-name="Shibboleth Checkstyle" local="true">
- <file-match-pattern match-pattern="." include-pattern="true"/>
+ <fileset name="main" enabled="true" check-config-name="Shibboleth Checkstyle" local="true">
+ <file-match-pattern match-pattern="src/main/java/.*\.java$" include-pattern="true"/>
</fileset>
</fileset-config>
diff --git a/totp-impl/pom.xml b/totp-impl/pom.xml
index 0a339a4..3c1a9c6 100644
--- a/totp-impl/pom.xml
+++ b/totp-impl/pom.xml
@@ -30,13 +30,13 @@
<!-- provided dependencies -->
<dependency>
- <groupId>net.shibboleth.utilities</groupId>
- <artifactId>java-support</artifactId>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>net.shibboleth.ext</groupId>
- <artifactId>spring-extensions</artifactId>
+ <groupId>net.shibboleth.utilities</groupId>
+ <artifactId>java-support</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -61,6 +61,62 @@
</dependency>
<!-- test dependencies -->
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-attribute-resolver-api</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-attribute-resolver-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-authn-impl</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-installer</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-profile-api</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-core</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.utilities</groupId>
+ <artifactId>java-support</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.ext</groupId>
+ <artifactId>spring-extensions</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-test</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/TOTPDescription.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/TOTPDescription.java
new file mode 100644
index 0000000..9de5ea5
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/TOTPDescription.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.totp;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.springframework.core.io.ClassPathResource;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.plugin.AbstractPluginDescription;
+
+/**
+ * Details about the TOTP login plugin.
+ */
+public class TOTPDescription extends AbstractPluginDescription {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NotEmpty public String getPluginId() {
+ return "net.shibboleth.idp.plugin.totp";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NonnullElements public List<URL> getUpdateURLs() throws IOException {
+ return Collections.singletonList(new ClassPathResource("META-INF/plugins/").getURL());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NonnullElements public List<Pair<URL, Path>> getExternalFilePathsToCopy() throws IOException {
+ return Collections.emptyList();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull @NonnullElements public List<Path> getFilePathsToCopy() {
+ return List.of(Path.of("conf", "testfile.xml"), Path.of("doc", "nashorn-plugin"));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMajorVersion() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getMinorVersion() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getPatchVersion() {
+ return 1;
+ }
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/context/TOTPContext.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/context/TOTPContext.java
index 6a151d5..ccc8fd6 100644
--- a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/context/TOTPContext.java
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/context/TOTPContext.java
@@ -30,32 +30,12 @@ import com.google.common.base.Strings;
import net.shibboleth.utilities.java.support.annotation.constraint.Live;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.logic.Constraint;
/**
* Context class for state of a TOTP validation.
*/
public class TOTPContext extends BaseContext {
- /** Transaction status. */
- public enum AuthState {
- /** Success. */
- OK,
- /** Failure. */
- ERROR,
- /** Unable to locate a token registration. */
- MISSING_SEED,
- /** No tokens registered. */
- REGISTER,
- /** Unable to validate. */
- CANT_VALIDATE,
- /** Unknown. */
- UNKNOWN,
- }
-
- /** State of transaction. */
- @Nonnull private AuthState state;
-
/** The subject identifier with respect to the token "back-end". */
@Nullable @NotEmpty private String username;
@@ -70,33 +50,9 @@ public class TOTPContext extends BaseContext {
/** Constructor. */
public TOTPContext() {
- state = AuthState.UNKNOWN;
tokenSeeds = new ArrayList<>();
}
- /**
- * Get the state of the transaction.
- *
- * @return transaction state
- */
- @Nonnull public AuthState getState() {
- return state;
- }
-
-
- /**
- * Set the state of the transaction.
- *
- * @param astate new state
- *
- * @return this context
- */
- @Nonnull public TOTPContext setState(@Nonnull final AuthState astate) {
- state = Constraint.isNotNull(astate, "AuthState cannot be null");
-
- return this;
- }
-
/**
* Get the username.
*
@@ -154,18 +110,4 @@ public class TOTPContext extends BaseContext {
return tokenSeeds;
}
- /*
- //A counter for the numbers of times the user has failed auth in this context
- private int failedAuth = 0;
-
-
- public void failedAttempt() {
- failedAuth++;
- }
-
- public int getFailedAttempts(){
- return failedAuth;
- }
- */
-
}
\ No newline at end of file
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AbstractTOTPCredentialValidator.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AbstractTOTPCredentialValidator.java
new file mode 100644
index 0000000..e338552
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AbstractTOTPCredentialValidator.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.totp.impl;
+
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.AbstractCredentialValidator;
+import net.shibboleth.idp.authn.AbstractUsernamePasswordCredentialValidator;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.CredentialValidator;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.idp.plugin.totp.principal.TOTPPrincipal;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * An abstract {@link CredentialValidator} that checks for a {@link TOTPContext} and delegates
+ * to subclasses to produce a result.
+ */
+public abstract class AbstractTOTPCredentialValidator extends AbstractCredentialValidator {
+
+ /** Default prefix for metrics. */
+ @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.totp";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractUsernamePasswordCredentialValidator.class);
+
+ /** Lookup strategy for TOTP context. */
+ @Nonnull private Function<AuthenticationContext,TOTPContext> totpContextLookupStrategy;
+
+ /** A regular expression to apply for acceptance testing. */
+ @Nullable private Pattern matchExpression;
+
+ /** Constructor. */
+ public AbstractTOTPCredentialValidator() {
+ totpContextLookupStrategy = new ChildContextLookup<>(TOTPContext.class);
+ }
+
+ /**
+ * Set the lookup strategy to locate the {@link TOTPContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTOTPContextLookupStrategy(
+ @Nonnull final Function<AuthenticationContext,TOTPContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ totpContextLookupStrategy = Constraint.isNotNull(strategy, "TOTPContextLookupStrategy cannot be null");
+ }
+
+ /**
+ * Set a matching expression to apply to the username for acceptance.
+ *
+ * @param expression a matching expression
+ */
+ public void setMatchExpression(@Nullable final Pattern expression) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ matchExpression = expression;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+
+ final TOTPContext totpContext = totpContextLookupStrategy.apply(authenticationContext);
+ if (totpContext == null) {
+ log.info("{} No TOTPContext available", getLogPrefix());
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
+ AuthnEventIds.NO_CREDENTIALS);
+ }
+ throw new LoginException(AuthnEventIds.NO_CREDENTIALS);
+ } else if (totpContext.getUsername() == null) {
+ log.info("{} No username available within TOTPContext", getLogPrefix());
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
+ AuthnEventIds.NO_CREDENTIALS);
+ }
+ throw new LoginException(AuthnEventIds.NO_CREDENTIALS);
+ } else if (totpContext.getTokenCode() == null || totpContext.getTokenSeeds().isEmpty()) {
+ log.info("{} No seeds or tokencode available within TOTPContext", getLogPrefix());
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
+ AuthnEventIds.INVALID_CREDENTIALS);
+ }
+ throw new LoginException(AuthnEventIds.INVALID_CREDENTIALS);
+ }
+
+ if (matchExpression != null && !matchExpression.matcher(totpContext.getUsername()).matches()) {
+ log.debug("{} Username '{}' did not match expression", getLogPrefix(), totpContext.getUsername());
+ return null;
+ }
+
+ return doValidate(profileRequestContext, authenticationContext, totpContext, warningHandler, errorHandler);
+ }
+
+ /**
+ * Override method for subclasses to use to perform the actual TOTP validation.
+ *
+ * @param profileRequestContext profile request context
+ * @param authenticationContext authentication context
+ * @param totpContext the TOTP context to validate
+ * @param warningHandler optional warning handler interface
+ * @param errorHandler optional error handler interface
+ *
+ * @return the validated result, or null if inapplicable
+ *
+ * @throws Exception if an error occurs
+ */
+ @Nullable protected abstract Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final TOTPContext totpContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception;
+
+ /**
+ * Decorate the subject with "standard" content from the validation.
+ *
+ * @param subject the subject being returned
+ * @param totpContext the TOTP context being validated
+ *
+ * @return the decorated subject
+ */
+ @Nonnull protected Subject populateSubject(@Nonnull final Subject subject,
+ @Nonnull final TOTPContext totpContext) {
+ subject.getPrincipals().add(new TOTPPrincipal(totpContext.getUsername()));
+
+ return super.populateSubject(subject);
+ }
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSource.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSource.java
index 94ca139..c0f4c2e 100644
--- a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSource.java
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSource.java
@@ -126,7 +126,7 @@ public class AttributeResolverSeedSource extends AbstractSeedSource {
log.debug("Resolved {} seed(s) for '{}'", seeds.size(), totp.getUsername());
} else {
- log.warn("Unable to locate TOTPContext and username");
+ log.warn("Unable to locate TOTPContext with username set");
}
}
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/GoogleAuthenticatorCredentialValidator.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/GoogleAuthenticatorCredentialValidator.java
new file mode 100644
index 0000000..02651ee
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/GoogleAuthenticatorCredentialValidator.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.totp.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.warrenstrange.googleauth.GoogleAuthenticator;
+
+/**
+ * A TOTP validator using an implementation based on Google Authenticator code.
+ */
+ at ThreadSafeAfterInit
+public class GoogleAuthenticatorCredentialValidator extends AbstractTOTPCredentialValidator {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(GoogleAuthenticatorCredentialValidator.class);
+
+ /** Google Authenticator. **/
+ @NonnullAfterInit private GoogleAuthenticator gAuth;
+
+ /**
+ * Set the {@link GoogleAuthenticator} to use.
+ *
+ * @param authenticator implementation to use
+ */
+ public void setGoogleAuthenticator(@Nonnull final GoogleAuthenticator authenticator) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ gAuth = Constraint.isNotNull(authenticator, "GoogleAuthenticator cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (gAuth == null) {
+ log.info("{} Installing default GoogleAuthenticator", getLogPrefix());
+ gAuth = new GoogleAuthenticator();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext,
+ @Nonnull final TOTPContext totpContext,
+ @Nullable final WarningHandler warningHandler,
+ @Nullable final ErrorHandler errorHandler) throws Exception {
+
+ log.debug("{} Attempting to authenticate token code for '{}' ", getLogPrefix(), totpContext.getUsername());
+
+ try {
+ if (totpContext.getTokenSeeds().stream().anyMatch(
+ seed -> gAuth.authorize(seed, totpContext.getTokenCode()))) {
+ log.info("{} Login by '{}' succeeded", getLogPrefix(), totpContext.getUsername());
+ return populateSubject(new Subject(), totpContext);
+ }
+
+ throw new LoginException(AuthnEventIds.INVALID_CREDENTIALS);
+ } catch (final Exception e) {
+ log.info("{} Login by '{}' failed", getLogPrefix(), totpContext.getUsername());
+ if (errorHandler != null) {
+ errorHandler.handleError(profileRequestContext, authenticationContext, e,
+ AuthnEventIds.INVALID_CREDENTIALS);
+ }
+ throw e;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/package-info.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/package-info.java
new file mode 100644
index 0000000..6856442
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.
+ */
+
+/**
+ * Top level TOTP classes.
+ */
+
+package net.shibboleth.idp.plugin.totp;
\ No newline at end of file
diff --git a/totp-impl/src/main/resources/META-INF/plugins/net.shibboleth.idp.plugin.totp/0.0.1/version.details.properties b/totp-impl/src/main/resources/META-INF/plugins/net.shibboleth.idp.plugin.totp/0.0.1/version.details.properties
new file mode 100644
index 0000000..ccce079
--- /dev/null
+++ b/totp-impl/src/main/resources/META-INF/plugins/net.shibboleth.idp.plugin.totp/0.0.1/version.details.properties
@@ -0,0 +1,3 @@
+idp.version.max=5.0.0
+idp.version.min=4.1.0
+support.level = 2
diff --git a/totp-impl/src/main/resources/META-INF/plugins/net.shibboleth.idp.plugin.totp/versions.properties b/totp-impl/src/main/resources/META-INF/plugins/net.shibboleth.idp.plugin.totp/versions.properties
new file mode 100644
index 0000000..65fbb9d
--- /dev/null
+++ b/totp-impl/src/main/resources/META-INF/plugins/net.shibboleth.idp.plugin.totp/versions.properties
@@ -0,0 +1 @@
+net.shibboleth.idp.plugin.totp.versions=0.0.1
\ No newline at end of file
diff --git a/totp-impl/src/main/resources/META-INF/services/net.shibboleth.utilities.java.support.plugin.PluginDescription b/totp-impl/src/main/resources/META-INF/services/net.shibboleth.utilities.java.support.plugin.PluginDescription
new file mode 100644
index 0000000..ad98521
--- /dev/null
+++ b/totp-impl/src/main/resources/META-INF/services/net.shibboleth.utilities.java.support.plugin.PluginDescription
@@ -0,0 +1 @@
+net.shibboleth.idp.plugin.totp.TOTPDescription
diff --git a/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/PluginTest.java b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/PluginTest.java
new file mode 100644
index 0000000..1306a4a
--- /dev/null
+++ b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/PluginTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.totp;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.util.ServiceLoader;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.installer.plugin.impl.PluginState;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.plugin.PluginDescription;
+
+/** basic sanity tests */
+ at SuppressWarnings("javadoc")
+public class PluginTest {
+
+ private PluginDescription totp;
+
+ @BeforeClass
+ public void SetupPlugin() {
+ final ServiceLoader<PluginDescription> loader = ServiceLoader.load(PluginDescription.class);
+ for (final PluginDescription service:loader) {
+ if (getClass().getPackageName().contentEquals(service.getPluginId())) {
+ totp = service;
+ break;
+ }
+ }
+ assertNotNull(totp);
+ }
+
+ @Test
+ public void testState() throws ComponentInitializationException {
+
+ final PluginState state = new PluginState(totp);
+ state.initialize();
+
+ }
+
+ @Test
+ public void testDownload() throws Exception {
+ // TODO
+ }
+}
diff --git a/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSourceTest.java b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSourceTest.java
new file mode 100644
index 0000000..cf3e1e6
--- /dev/null
+++ b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/AttributeResolverSeedSourceTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.totp.impl;
+
+import static org.testng.Assert.*;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.ext.spring.service.MockApplicationContext;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeDefinition;
+import net.shibboleth.idp.attribute.resolver.DataConnector;
+import net.shibboleth.idp.attribute.resolver.MockAttributeDefinition;
+import net.shibboleth.idp.attribute.resolver.impl.AttributeResolverImpl;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.idp.profile.RequestContextBuilder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.service.MockReloadableService;
+
+/** Test case for {@link AttributeResolverSeedSource}. */
+public class AttributeResolverSeedSourceTest {
+
+ private AttributeResolverSeedSource source;
+
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ final IdPAttribute single = new IdPAttribute("single");
+ single.setValues(Collections.singletonList(StringAttributeValue.valueOf("one")));
+ final AttributeDefinition singledef = new MockAttributeDefinition("single", single);
+ singledef.initialize();
+
+ final IdPAttribute multiple = new IdPAttribute("multiple");
+ multiple.setValues(List.of(StringAttributeValue.valueOf("one"), StringAttributeValue.valueOf(null), StringAttributeValue.valueOf("two")));
+ final AttributeDefinition multipledef = new MockAttributeDefinition("multiple", multiple);
+ multipledef.initialize();
+
+ final AttributeResolverImpl resolver = newAttributeResolverImpl(List.of(singledef, multipledef), null);
+
+ source = new AttributeResolverSeedSource();
+ source.setAttributeResolver(new MockReloadableService<>(resolver));
+ }
+
+ @Test(expectedExceptions=ComponentInitializationException.class)
+ public void invalid() throws ComponentInitializationException {
+ // No attributeId
+ source.initialize();
+ }
+
+ @Test public void noContextOrUsername() throws ComponentInitializationException {
+
+ source.setSourceAttribute("foo");
+ source.initialize();
+
+ // does nothing
+ source.accept(null);
+
+ final ProfileRequestContext prc = new RequestContextBuilder().buildProfileRequestContext();
+
+ // also does nothing
+ source.accept(prc);
+
+ final TOTPContext totp = prc.getSubcontext(AuthenticationContext.class, true).getSubcontext(TOTPContext.class, true);
+ source.accept(prc);
+ assertTrue(totp.getTokenSeeds().isEmpty());
+ }
+
+ @Test public void noAttribute() throws ComponentInitializationException {
+
+ source.setSourceAttribute("foo");
+ source.initialize();
+
+ final ProfileRequestContext prc = new RequestContextBuilder().buildProfileRequestContext();
+ final TOTPContext totp = prc.getSubcontext(AuthenticationContext.class, true).getSubcontext(TOTPContext.class, true);
+ totp.setUsername("jdoe");
+ source.accept(prc);
+
+ assertTrue(totp.getTokenSeeds().isEmpty());
+ }
+
+ @Test public void single() throws ComponentInitializationException {
+
+ source.setSourceAttribute("single");
+ source.initialize();
+
+ final ProfileRequestContext prc = new RequestContextBuilder().buildProfileRequestContext();
+ final TOTPContext totp = prc.getSubcontext(AuthenticationContext.class, true).getSubcontext(TOTPContext.class, true);
+ totp.setUsername("jdoe");
+ source.accept(prc);
+
+ assertEquals(totp.getTokenSeeds().size(), 1);
+ assertEquals(totp.getTokenSeeds().iterator().next(), "one");
+ }
+
+ @Test public void multiple() throws ComponentInitializationException {
+
+ source.setSourceAttribute("multiple");
+ source.initialize();
+
+ final ProfileRequestContext prc = new RequestContextBuilder().buildProfileRequestContext();
+ final TOTPContext totp = prc.getSubcontext(AuthenticationContext.class, true).getSubcontext(TOTPContext.class, true);
+ totp.setUsername("jdoe");
+ source.accept(prc);
+
+ assertEquals(totp.getTokenSeeds().size(), 2);
+ assertEquals(totp.getTokenSeeds(), List.of("one", "two"));
+ }
+
+ /**
+ * Mock a resolver.
+ *
+ * @param definitions
+ * @param connectors
+ *
+ * @return resolver
+ * @throws ComponentInitializationException
+ */
+ private static AttributeResolverImpl newAttributeResolverImpl(
+ @Nullable final Collection<AttributeDefinition> definitions,
+ @Nullable final Collection<DataConnector> connectors) throws ComponentInitializationException {
+ final AttributeResolverImpl result = new AttributeResolverImpl();
+ result.setId("test");
+
+ result.setAttributeDefinitions(definitions == null ? Collections.emptyList() : definitions);
+ result.setDataConnectors(connectors == null ? Collections.emptyList() : connectors);
+ result.setApplicationContext(new MockApplicationContext());
+ result.initialize();
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/GoogleAuthenticatorCredentialValidatorTest.java b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/GoogleAuthenticatorCredentialValidatorTest.java
new file mode 100644
index 0000000..fe8b3c1
--- /dev/null
+++ b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/GoogleAuthenticatorCredentialValidatorTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.totp.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.security.auth.login.LoginException;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.AuthenticationErrorContext;
+import net.shibboleth.idp.authn.impl.BaseAuthenticationContextTest;
+import net.shibboleth.idp.authn.impl.ValidateCredentials;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.idp.plugin.totp.principal.TOTPPrincipal;
+import net.shibboleth.idp.profile.ActionTestingSupport;
+
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.warrenstrange.googleauth.GoogleAuthenticator;
+import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
+
+/** Unit test for {@link GoogleAuthenticatorCredentialValidator}. */
+public class GoogleAuthenticatorCredentialValidatorTest extends BaseAuthenticationContextTest {
+
+ private GoogleAuthenticatorCredentialValidator validator;
+
+ private ValidateCredentials action;
+
+ @BeforeMethod public void setUp() throws Exception {
+ super.setUp();
+
+ validator = new GoogleAuthenticatorCredentialValidator();
+ validator.setId("gauthtest");
+
+ action = new ValidateCredentials();
+ action.setValidators(Collections.singletonList(validator));
+
+ final Map<String,Collection<String>> mappings = new HashMap<>();
+ mappings.put(AuthnEventIds.INVALID_CREDENTIALS, Collections.singleton(AuthnEventIds.INVALID_CREDENTIALS));
+ mappings.put(AuthnEventIds.UNKNOWN_USERNAME, Collections.singleton(AuthnEventIds.UNKNOWN_USERNAME));
+ action.setClassifiedMessages(mappings);
+ }
+
+ @Test public void testMissingContext() throws Exception {
+ prc.getSubcontext(AuthenticationContext.class).setAttemptedFlow(authenticationFlows.get(0));
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testMissingUser() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+ ac.getSubcontext(TOTPContext.class, true);
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+
+ @Test public void testMissingCode() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+ ac.getSubcontext(TOTPContext.class, true).setUsername("foo").getTokenSeeds().add("foo");
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
+ }
+
+ @Test public void testMissingSeeds() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+ ac.getSubcontext(TOTPContext.class, true).setUsername("foo").setTokenCode(123456);
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
+ }
+
+ @Test public void testUnmatchedUser() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+ ac.getSubcontext(TOTPContext.class, true).setUsername("bar").setTokenCode(123456).getTokenSeeds().add("foo");
+
+
+ validator.setMatchExpression(Pattern.compile("foo.+"));
+ validator.initialize();
+
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.REQUEST_UNSUPPORTED);
+ }
+
+ @Test public void testInvalidSeed() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+ ac.getSubcontext(TOTPContext.class, true).setUsername("foo").setTokenCode(123456).getTokenSeeds().add("foo");
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
+ AuthenticationErrorContext errorCtx = ac.getSubcontext(AuthenticationErrorContext.class);
+ Assert.assertTrue(errorCtx.getExceptions().get(0) instanceof IllegalArgumentException);
+ }
+
+ @Test public void testInvalidCode() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+ ac.getSubcontext(TOTPContext.class, true).setUsername("foo").setTokenCode(123456).getTokenSeeds().add("G24YUKCHHXRDWCPR");
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
+ AuthenticationErrorContext errorCtx = ac.getSubcontext(AuthenticationErrorContext.class);
+ Assert.assertTrue(errorCtx.getExceptions().get(0) instanceof LoginException);
+ }
+
+ @Test public void testSuccess() throws Exception {
+ final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+ ac.setAttemptedFlow(authenticationFlows.get(0));
+
+ final GoogleAuthenticator auth = new GoogleAuthenticator();
+ final GoogleAuthenticatorKey creds = auth.createCredentials();
+
+ ac.getSubcontext(TOTPContext.class, true)
+ .setUsername("foo")
+ .setTokenCode(auth.getTotpPassword(creds.getKey()))
+ .getTokenSeeds().add(creds.getKey());
+
+ validator.initialize();
+ action.initialize();
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ Assert.assertNotNull(ac.getAuthenticationResult());
+ Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(TOTPPrincipal.class).iterator()
+ .next().getName(), "foo");
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list