[java-idp-plugin-totp] branch master updated: Initial flow design, extraction actions, some tests.
Scott Cantor
cantor.2 at osu.edu
Wed Aug 5 22:38:04 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=780709c33a19529f239cc978b34eb968d96c301c
The following commit(s) were added to refs/heads/master by this push:
new 780709c Initial flow design, extraction actions, some tests.
780709c is described below
commit 780709c33a19529f239cc978b34eb968d96c301c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Aug 5 18:39:17 2020 -0400
Initial flow design, extraction actions, some tests.
---
totp-impl/pom.xml | 5 +
.../totp/impl/AbstractTOTPCredentialValidator.java | 60 ++++++++-
.../totp/impl/AbstractTOTPExtractionAction.java | 135 +++++++++++++++++++++
.../totp/impl/ExtractTOTPFromFormRequest.java | 80 ++++++++++++
.../plugin/totp/impl/ExtractTOTPFromHeader.java | 80 ++++++++++++
.../GoogleAuthenticatorCredentialValidator.java | 2 +-
.../shibboleth/idp/flows/authn/totp/totp-beans.xml | 53 ++++++++
.../shibboleth/idp/flows/authn/totp/totp-flow.xml | 52 ++++++++
.../totp/impl/ExtractTOTPFromFormRequestTest.java | 97 +++++++++++++++
.../totp/impl/ExtractTOTPFromHeaderTest.java | 97 +++++++++++++++
...GoogleAuthenticatorCredentialValidatorTest.java | 5 +
11 files changed, 660 insertions(+), 6 deletions(-)
diff --git a/totp-impl/pom.xml b/totp-impl/pom.xml
index 386564b..4b494cb 100644
--- a/totp-impl/pom.xml
+++ b/totp-impl/pom.xml
@@ -64,6 +64,11 @@
<artifactId>idp-authn-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-session-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
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
index e338552..b331029 100644
--- 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
@@ -17,6 +17,7 @@
package net.shibboleth.idp.plugin.totp.impl;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
@@ -35,9 +36,12 @@ 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.authn.context.SubjectCanonicalizationContext;
import net.shibboleth.idp.plugin.totp.context.TOTPContext;
import net.shibboleth.idp.plugin.totp.principal.TOTPPrincipal;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -56,6 +60,9 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
/** Lookup strategy for TOTP context. */
@Nonnull private Function<AuthenticationContext,TOTPContext> totpContextLookupStrategy;
+ /** Source of token seeds. */
+ @NonnullAfterInit private Consumer<ProfileRequestContext> seedSource;
+
/** A regular expression to apply for acceptance testing. */
@Nullable private Pattern matchExpression;
@@ -63,7 +70,7 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
public AbstractTOTPCredentialValidator() {
totpContextLookupStrategy = new ChildContextLookup<>(TOTPContext.class);
}
-
+
/**
* Set the lookup strategy to locate the {@link TOTPContext}.
*
@@ -73,7 +80,18 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
@Nonnull final Function<AuthenticationContext,TOTPContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- totpContextLookupStrategy = Constraint.isNotNull(strategy, "TOTPContextLookupStrategy cannot be null");
+ totpContextLookupStrategy = Constraint.isNotNull(strategy, "TOTPContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set source of token seeds.
+ *
+ * @param source seed source
+ */
+ public void setSeedSource(@Nonnull final Consumer<ProfileRequestContext> source) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ seedSource = Constraint.isNotNull(source, "Token seed source cannot be null");
}
/**
@@ -86,7 +104,18 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
matchExpression = expression;
}
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (seedSource == null) {
+ throw new ComponentInitializationException("Token seed source cannot be null");
+ }
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -110,8 +139,8 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
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());
+ } else if (totpContext.getTokenCode() == null) {
+ log.info("{} No tokencode available within TOTPContext", getLogPrefix());
if (errorHandler != null) {
errorHandler.handleError(profileRequestContext, authenticationContext, (String) null,
AuthnEventIds.INVALID_CREDENTIALS);
@@ -119,6 +148,20 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
throw new LoginException(AuthnEventIds.INVALID_CREDENTIALS);
}
+ if (totpContext.getTokenSeeds().isEmpty()) {
+ // Resolve seeds.
+ seedSource.accept(profileRequestContext);
+
+ if (totpContext.getTokenSeeds().isEmpty()) {
+ log.info("{} No seeds were obtained for user '{}'", getLogPrefix(), totpContext.getUsername());
+ 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;
@@ -126,7 +169,8 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
return doValidate(profileRequestContext, authenticationContext, totpContext, warningHandler, errorHandler);
}
-
+// Checkstyle: CyclomaticComplexity ON
+
/**
* Override method for subclasses to use to perform the actual TOTP validation.
*
@@ -150,14 +194,20 @@ public abstract class AbstractTOTPCredentialValidator extends AbstractCredential
* Decorate the subject with "standard" content from the validation.
*
* @param subject the subject being returned
+ * @param profileRequestContext current profile request context
* @param totpContext the TOTP context being validated
*
* @return the decorated subject
*/
@Nonnull protected Subject populateSubject(@Nonnull final Subject subject,
+ @Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final TOTPContext totpContext) {
subject.getPrincipals().add(new TOTPPrincipal(totpContext.getUsername()));
+ // Bypass c14n. We already operate on a canonical name, so just re-confirm it.
+ profileRequestContext.getSubcontext(SubjectCanonicalizationContext.class, true).setPrincipalName(
+ totpContext.getUsername());
+
return super.populateSubject(subject);
}
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AbstractTOTPExtractionAction.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AbstractTOTPExtractionAction.java
new file mode 100644
index 0000000..9c9ec1d
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/AbstractTOTPExtractionAction.java
@@ -0,0 +1,135 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+/**
+ * An action that derives a username from a lookup strategy, a TOTP code from an arbitrary source,
+ * creates a {@link TOTPContext}, and attaches it to the {@link AuthenticationContext}.
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ */
+public abstract class AbstractTOTPExtractionAction extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractTOTPExtractionAction.class);
+
+ /** Lookup strategy for username to use in resolving token seeds. */
+ @Nonnull private Function<ProfileRequestContext,String> usernameLookupStrategy;
+
+ /** Creation strategy for TOTP context. */
+ @Nonnull private Function<AuthenticationContext,TOTPContext> totpContextCreationStrategy;
+
+ /** TOTP context being operated on. */
+ @Nullable private TOTPContext totpContext;
+
+ /** Constructor. */
+ public AbstractTOTPExtractionAction() {
+ usernameLookupStrategy = new CanonicalUsernameLookupStrategy();
+ totpContextCreationStrategy = new ChildContextLookup<>(TOTPContext.class, true);
+ }
+
+ /**
+ * Set the lookup strategy to use for the username to use in resolving token seeds.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setUsernameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ usernameLookupStrategy = Constraint.isNotNull(strategy, "Username lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy to locate/create the {@link TOTPContext}.
+ *
+ * @param strategy lookup/creation strategy
+ */
+ public void setTOTPContextCreationStrategy(@Nonnull final Function<AuthenticationContext,TOTPContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ totpContextCreationStrategy = Constraint.isNotNull(strategy, "TOTPContext creation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ totpContext = totpContextCreationStrategy.apply(authenticationContext);
+ totpContext.setTokenCode(null);
+
+ // Fill in username if not set.
+ if (totpContext.getUsername() == null) {
+ final String username = usernameLookupStrategy.apply(profileRequestContext);
+ if (username == null) {
+ log.warn("{} No principal name available", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ totpContext.setUsername(username);
+ }
+
+ final HttpServletRequest request = getHttpServletRequest();
+ if (request == null) {
+ log.debug("{} Profile action does not contain an HttpServletRequest", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ final Integer code = extractCode(request);
+ if (code == null) {
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ totpContext.setTokenCode(code);
+ }
+
+ /**
+ * Gets the token code from the HTTP request.
+ *
+ * @param httpRequest current HTTP request
+ *
+ * @return the token code, or null
+ */
+ @Nullable protected abstract Integer extractCode(@Nonnull final HttpServletRequest httpRequest);
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromFormRequest.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromFormRequest.java
new file mode 100644
index 0000000..622f0a2
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromFormRequest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.servlet.http.HttpServletRequest;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that derives the TOTP code from a form parameter.
+ */
+public class ExtractTOTPFromFormRequest extends AbstractTOTPExtractionAction {
+
+ /** Default token code field name. */
+ @Nonnull @NotEmpty public static final String DEFAULT_FIELD_NAME = "tokencode";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractTOTPFromFormRequest.class);
+
+ /** Name of header. */
+ @NonnullAfterInit @NotEmpty private String fieldName;
+
+ /** Constructor. */
+ public ExtractTOTPFromFormRequest() {
+ fieldName = DEFAULT_FIELD_NAME;
+ }
+
+ /**
+ * Set the name of the field to examine.
+ *
+ * @param field field name
+ */
+ public void setFieldName(@Nonnull @NotEmpty final String field) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ fieldName = Constraint.isNotNull(StringSupport.trimOrNull(field), "Field name cannot be null or empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Integer extractCode(@Nonnull final HttpServletRequest httpRequest) {
+ final String code = httpRequest.getParameter(fieldName);
+ if (code != null) {
+ try {
+ return Integer.valueOf(code);
+ } catch (final NumberFormatException e) {
+ log.warn("{} Exception converting parameter value to integer code", getLogPrefix(), e);
+ }
+ } else {
+ log.trace("{} Token code field {} not found", getLogPrefix(), fieldName);
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromHeader.java b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromHeader.java
new file mode 100644
index 0000000..d07fc0f
--- /dev/null
+++ b/totp-impl/src/main/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromHeader.java
@@ -0,0 +1,80 @@
+/*
+ * 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.servlet.http.HttpServletRequest;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that derives the TOTP code from an HTTP header.
+ */
+public class ExtractTOTPFromHeader extends AbstractTOTPExtractionAction {
+
+ /** Default token code header. */
+ @Nonnull @NotEmpty public static final String DEFAULT_HEADER_NAME = "X-Shibboleth-TOTP";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractTOTPFromHeader.class);
+
+ /** Name of header. */
+ @NonnullAfterInit @NotEmpty private String headerName;
+
+ /** Constructor. */
+ public ExtractTOTPFromHeader() {
+ headerName = DEFAULT_HEADER_NAME;
+ }
+
+ /**
+ * Set the name of the header to examine.
+ *
+ * @param header header name
+ */
+ public void setHeaderName(@Nonnull @NotEmpty final String header) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ headerName = Constraint.isNotNull(StringSupport.trimOrNull(header), "Header name cannot be null or empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Integer extractCode(@Nonnull final HttpServletRequest httpRequest) {
+ final String code = httpRequest.getHeader(headerName);
+ if (code != null) {
+ try {
+ return Integer.valueOf(code);
+ } catch (final NumberFormatException e) {
+ log.warn("{} Exception converting header value to integer code", getLogPrefix(), e);
+ }
+ } else {
+ log.trace("{} Token code header {} not found", getLogPrefix(), headerName);
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
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
index 02651ee..2f76f90 100644
--- 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
@@ -85,7 +85,7 @@ public class GoogleAuthenticatorCredentialValidator extends AbstractTOTPCredenti
if (totpContext.getTokenSeeds().stream().anyMatch(
seed -> gAuth.authorize(seed, totpContext.getTokenCode()))) {
log.info("{} Login by '{}' succeeded", getLogPrefix(), totpContext.getUsername());
- return populateSubject(new Subject(), totpContext);
+ return populateSubject(new Subject(), profileRequestContext, totpContext);
}
throw new LoginException(AuthnEventIds.INVALID_CREDENTIALS);
diff --git a/totp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/totp/totp-beans.xml b/totp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/totp/totp-beans.xml
new file mode 100644
index 0000000..900b39c
--- /dev/null
+++ b/totp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/totp/totp-beans.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <bean class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor" />
+ <bean class="net.shibboleth.idp.profile.impl.ProfileActionBeanPostProcessor" />
+
+ <import resource="conditional:%{idp.home}/conf/authn/totp-authn-config.xml" />
+
+ <bean id="ExtractTOTPFromHeader"
+ class="net.shibboleth.idp.plugin.totp.impl.ExtractTOTPFromHeader" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:headerName="#{getObject('shibboleth.authn.TOTP.HeaderName') ?: T(net.shibboleth.idp.plugin.totp.impl.ExtractTOTPFromHeader).DEFAULT_HEADER_NAME}" />
+
+ <bean id="ExtractTOTPFromFormRequest"
+ class="net.shibboleth.idp.plugin.totp.impl.ExtractTOTPFromFormRequest" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:fieldName="#{getObject('shibboleth.authn.TOTP.FieldName') ?: T(net.shibboleth.idp.plugin.totp.impl.ExtractTOTPFromFormRequest).DEFAULT_FIELD_NAME}" />
+
+ <bean id="ValidateTOTPCredentials"
+ class="net.shibboleth.idp.authn.impl.ValidateCredentials" scope="prototype"
+ p:validators="#{getObject('shibboleth.authn.TOTP.Validator') ?: getObject('DefaultTOTPValidator')}"
+ p:addDefaultPrincipals="#{getObject('shibboleth.authn.TOTP.addDefaultPrincipals') ?:
+ (getObject('shibboleth.authn.TOTP.PrincipalOverride') == null
+ or getObject('shibboleth.authn.TOTP.PrincipalOverride').isEmpty())}"
+ p:supportedPrincipals="#{getObject('shibboleth.authn.TOTP.PrincipalOverride')}"
+ p:classifiedMessages-ref="shibboleth.authn.TOTP.ClassifiedMessageMap"
+ p:resultCachingPredicate="#{getObject('shibboleth.authn.TOTP.resultCachingPredicate')}"
+ p:lockoutManager="#{getObject('shibboleth.authn.TOTP.AccountLockoutManager')}" />
+
+ <!-- These are singletons acting as default "back-ends". -->
+
+ <bean id="DefaultTOTPValidator" class="net.shibboleth.idp.plugin.totp.impl.GoogleAuthenticatorCredentialValidator" lazy-init="true"
+ p:matchExpression="#{getObject('shibboleth.authn.TOTP.matchExpression')}" />
+
+ <bean id="DefaultSeedSource" class="net.shibboleth.idp.plugin.totp.impl.AttributeResolverSeedSource" lazy-init="true"
+ p:attributeResolver-ref="shibboleth.AttributeResolverService"
+ p:sourceAttribute="#{getObject('shibboleth.authn.TOTP.TokenSeedAttribute') ?: T(net.shibboleth.idp.plugin.totp.impl.AttributeResolverSeedSource).DEFAULT_ATTRIBUTE_ID}" />
+
+</beans>
diff --git a/totp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/totp/totp-flow.xml b/totp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/totp/totp-flow.xml
new file mode 100644
index 0000000..153b84e
--- /dev/null
+++ b/totp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/totp/totp-flow.xml
@@ -0,0 +1,52 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="authn.abstract, authn/conditions">
+
+ <!-- This is a login flow for TOTP authentication -->
+
+ <!-- First check for HTTP header. -->
+ <action-state id="ExtractTOTPFromHeader">
+ <evaluate expression="ExtractTOTPFromHeader" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="ValidateTOTPCredentials" />
+ <transition on="NoCredentials" to="ExtractTOTPFromFormRequest" />
+ </action-state>
+
+ <!-- Then check for propagation via password (or other) form. -->
+ <action-state id="ExtractTOTPFromFormRequest">
+ <evaluate expression="ExtractTOTPFromFormRequest" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="ValidateTOTPCredentials" />
+ <transition on="NoCredentials" to="DisplayTOTPView" />
+ </action-state>
+
+ <view-state id="DisplayTOTPView" view="totp">
+ <on-render>
+ <evaluate expression="environment" result="viewScope.environment" />
+ <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))"
+ result="viewScope.authenticationContext" />
+ <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="viewScope.encoder" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
+ <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+ <evaluate
+ expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.CustomViewContext') ? flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CustomViewContext') : null"
+ result="viewScope.custom" />
+ </on-render>
+ <transition on="proceed" to="ExtractTOTPFromFormRequest" />
+ </view-state>
+
+ <action-state id="ValidateTOTPCredentials">
+ <evaluate expression="ValidateTOTPCredentials" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="proceed" />
+ <transition on="InvalidCredentials" to="DisplayTOTPView" />
+ <transition on="NoCredentials" to="DisplayTOTPView" />
+ </action-state>
+
+ <bean-import resource="totp-beans.xml" />
+
+</flow>
diff --git a/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromFormRequestTest.java b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromFormRequestTest.java
new file mode 100644
index 0000000..2b0d2e0
--- /dev/null
+++ b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromFormRequestTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.impl.BaseAuthenticationContextTest;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.idp.profile.ActionTestingSupport;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+
+/** {@link ExtractTOTPFromFormRequest} unit test. */
+public class ExtractTOTPFromFormRequestTest extends BaseAuthenticationContextTest {
+
+ private ExtractTOTPFromFormRequest action;
+
+ @BeforeMethod public void setUp() throws Exception {
+ super.setUp();
+
+ action = new ExtractTOTPFromFormRequest();
+ action.setFieldName("Foo");
+ action.setHttpServletRequest(new MockHttpServletRequest());
+ action.setUsernameLookupStrategy(FunctionSupport.constant("jdoe"));
+ action.initialize();
+ }
+
+ @Test public void testNoServlet() throws Exception {
+ action = new ExtractTOTPFromFormRequest();
+ action.initialize();
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testNoUsername() throws Exception {
+ action = new ExtractTOTPFromFormRequest();
+ action.setUsernameLookupStrategy(FunctionSupport.constant(null));
+ action.initialize();
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testMissingField() throws Exception {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testWrongField() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("Bar", "123456");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testInvalidFormat() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("Foo", "A123456");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testValid() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("Foo", "123456");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+ final TOTPContext totpCtx = authCtx.getSubcontext(TOTPContext.class);
+ Assert.assertNotNull(totpCtx);
+ Assert.assertEquals(totpCtx.getUsername(), "jdoe");
+ Assert.assertEquals(totpCtx.getTokenCode(), Integer.valueOf(123456));
+ }
+
+}
\ No newline at end of file
diff --git a/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromHeaderTest.java b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromHeaderTest.java
new file mode 100644
index 0000000..b8b3ad9
--- /dev/null
+++ b/totp-impl/src/test/java/net/shibboleth/idp/plugin/totp/impl/ExtractTOTPFromHeaderTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.impl.BaseAuthenticationContextTest;
+import net.shibboleth.idp.plugin.totp.context.TOTPContext;
+import net.shibboleth.idp.profile.ActionTestingSupport;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+
+/** {@link ExtractTOTPFromHeader} unit test. */
+public class ExtractTOTPFromHeaderTest extends BaseAuthenticationContextTest {
+
+ private ExtractTOTPFromHeader action;
+
+ @BeforeMethod public void setUp() throws Exception {
+ super.setUp();
+
+ action = new ExtractTOTPFromHeader();
+ action.setHeaderName("X-Foo");
+ action.setHttpServletRequest(new MockHttpServletRequest());
+ action.setUsernameLookupStrategy(FunctionSupport.constant("jdoe"));
+ action.initialize();
+ }
+
+ @Test public void testNoServlet() throws Exception {
+ action = new ExtractTOTPFromHeader();
+ action.initialize();
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testNoUsername() throws Exception {
+ action = new ExtractTOTPFromHeader();
+ action.setUsernameLookupStrategy(FunctionSupport.constant(null));
+ action.initialize();
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testMissingHeader() throws Exception {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testWrongHeader() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addHeader("Foo", "123456");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testInvalidFormat() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addHeader("X-Foo", "A123456");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ }
+
+ @Test public void testValid() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addHeader("X-Foo", "123456");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+ final TOTPContext totpCtx = authCtx.getSubcontext(TOTPContext.class);
+ Assert.assertNotNull(totpCtx);
+ Assert.assertEquals(totpCtx.getUsername(), "jdoe");
+ Assert.assertEquals(totpCtx.getTokenCode(), Integer.valueOf(123456));
+ }
+
+}
\ 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
index fe8b3c1..7dd69de 100644
--- 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
@@ -52,8 +52,13 @@ public class GoogleAuthenticatorCredentialValidatorTest extends BaseAuthenticati
@BeforeMethod public void setUp() throws Exception {
super.setUp();
+ // We pre-populate the seeds for testing, but this is required for componnent init.
+ final StaticSeedSource seedsource = new StaticSeedSource();
+ seedsource.initialize();
+
validator = new GoogleAuthenticatorCredentialValidator();
validator.setId("gauthtest");
+ validator.setSeedSource(seedsource);
action = new ValidateCredentials();
action.setValidators(Collections.singletonList(validator));
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list