[java-idp-plugin-duo] branch main updated: JDUO-61 - Log transactional information ahead of redirect to Duo
Phil Smart
philip.smart at jisc.ac.uk
Tue Nov 15 10:03:52 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-duo.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=5928a6cd2cd2449b43c9d3a21bb060a285430339
The following commit(s) were added to refs/heads/main by this push:
new 5928a6c JDUO-61 - Log transactional information ahead of redirect to Duo
5928a6c is described below
commit 5928a6cd2cd2449b43c9d3a21bb060a285430339
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Nov 15 10:03:44 2022 +0000
JDUO-61 - Log transactional information ahead of redirect to Duo
- Add INFO level logging of username and state to controller
- Add pluggable audit logging to the Duo2FA request
https://shibboleth.atlassian.net/browse/JDUO-61
---
.../idp/plugin/authn/duo/DuoOIDCAuthAPI.java | 15 ++++
.../authn/duo/audit/AbstractDuoAuditExtractor.java | 68 +++++++++++++++
.../AbstractDuoTokenClaimsAuditExtractor.java | 96 ++++++++++++++++++++++
.../idp/plugin/authn/duo/audit/AuditFields.java | 62 ++++++++++++++
idp-duo-api/src/test/resources/logback-test.xml | 7 +-
.../audit/impl/DuoAuthnDeviceIdAuditExtractor.java | 63 ++++++++++++++
.../impl/DuoAuthnDeviceNameAuditExtractor.java | 63 ++++++++++++++
.../duo/audit/impl/DuoFactorAuditExtractor.java | 56 +++++++++++++
.../duo/audit/impl/DuoReasonAuditExtractor.java | 56 +++++++++++++
.../audit/impl/DuoRequestStateAuditExtractor.java | 32 ++++++++
.../audit/impl/DuoResponseStateAuditExtractor.java | 34 ++++++++
.../audit/impl/DuoTransactionIdAuditExtractor.java | 56 +++++++++++++
.../duo/audit/impl/DuoUsernameAuditExtractor.java | 30 +++++++
.../duo/audit/impl/FixedStringAuditExtractor.java | 49 +++++++++++
.../authn/duo/impl/DuoOIDCAuthnController.java | 22 ++---
.../idp/plugin/authn/duo/impl/DuoSupport.java | 2 +-
.../duo/impl/PopulateDuoAuthenticationContext.java | 10 ++-
.../META-INF/net.shibboleth.idp/postconfig.xml | 89 ++++++++++++++++++++
.../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml | 33 ++++++++
.../flows/authn/DuoOIDC/duo-oidc-authn-flow.xml | 8 +-
.../authn/duo/impl/DuoOIDCAuthnControllerTest.java | 5 +-
.../duo/nimbus/conf/authn/duo-oidc.properties | 5 +-
.../authn/duo/sdk/conf/authn/duo-oidc.properties | 5 +-
23 files changed, 844 insertions(+), 22 deletions(-)
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
index 00e82c6..7fc335e 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
@@ -55,6 +55,21 @@ public final class DuoOIDCAuthAPI {
/** The name of the JSON authentication context object.*/
@Nonnull @NotEmpty public static final String DUO_AUTH_CONTEXT_JSON_OBJECT = "auth_context";
+ /** The name of the JSON authentication device object.*/
+ @Nonnull @NotEmpty public static final String DUO_AUTH_DEVICE_JSON_OBJECT = "auth_device";
+
+ /** The name of the JSON transaction id object.*/
+ @Nonnull @NotEmpty public static final String DUO_TXID_JSON_OBJECT = "txid";
+
+ /** The name of the JSON authenticating device friendly name object.*/
+ @Nonnull @NotEmpty public static final String DUO_AUTH_DEVICE_NAME_JSON_OBJECT = "name";
+
+ /** The name of the JSON authenticating device key object.*/
+ @Nonnull @NotEmpty public static final String DUO_AUTH_DEVICE_KEY_JSON_OBJECT = "key";
+
+ /** The name of the JSON reason object.*/
+ @Nonnull @NotEmpty public static final String DUO_REASON_JSON_OBJECT = "reason";
+
/** The name of the JSON authentication factor object.*/
@Nonnull @NotEmpty public static final String DUO_AUTH_FACTOR_JSON_OBJECT = "factor";
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AbstractDuoAuditExtractor.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AbstractDuoAuditExtractor.java
new file mode 100644
index 0000000..495ae7d
--- /dev/null
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AbstractDuoAuditExtractor.java
@@ -0,0 +1,68 @@
+/*
+ * 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.authn.duo.audit;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+
+/**
+ * An abstract audit extractor function to extract values directly from the Duo context.
+ *
+ * @param <T> the claim type to extract
+ */
+ at ThreadSafe
+public abstract class AbstractDuoAuditExtractor<T> implements Function<ProfileRequestContext, T> {
+
+
+ @Override
+ @Nullable public T apply(@Nullable final ProfileRequestContext profileRequestContext) {
+
+ if (profileRequestContext == null) {
+ return null;
+ }
+ final AuthenticationContext authnContext =
+ profileRequestContext.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ return null;
+ }
+ final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null) {
+ return null;
+ }
+ return doLookup(duoContext);
+ }
+
+ /**
+ * Implemented to perform the actual lookup.
+ *
+ * @param context the Duo OIDC context
+ *
+ * @return lookup value or {@code null} if not found
+ */
+ @Nullable protected abstract T doLookup(@Nonnull final DuoOIDCAuthenticationContext context);
+
+}
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AbstractDuoTokenClaimsAuditExtractor.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AbstractDuoTokenClaimsAuditExtractor.java
new file mode 100644
index 0000000..3f8eae4
--- /dev/null
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AbstractDuoTokenClaimsAuditExtractor.java
@@ -0,0 +1,96 @@
+/*
+ * 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.authn.duo.audit;
+
+import java.text.ParseException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+
+/**
+ * An extension of the {@link AbstractDuoAuditExtractor} that allows extraction of claims inside the
+ * Duo authentication token.
+ *
+ * @param <T> the claim type to extract
+ */
+ at ThreadSafe
+public abstract class AbstractDuoTokenClaimsAuditExtractor<T> extends AbstractDuoAuditExtractor<T> {
+
+
+ @Override
+ @Nullable public T apply(@Nullable final ProfileRequestContext profileRequestContext) {
+
+ if (profileRequestContext == null) {
+ return null;
+ }
+ final AuthenticationContext authnContext =
+ profileRequestContext.getSubcontext(AuthenticationContext.class);
+ if (authnContext == null) {
+ return null;
+ }
+ final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+ if (duoContext == null || duoContext.getAuthToken() == null) {
+ return null;
+ }
+ JWTClaimsSet claimsSet = null;
+ try {
+ claimsSet = duoContext.getAuthToken().getJWTClaimsSet();
+ if (claimsSet == null) {
+ return null;
+ }
+ } catch (final ParseException e) {
+ return null;
+ }
+ return doLookup(claimsSet, duoContext);
+ }
+
+ @Override
+ @Nullable protected T doLookup(@Nonnull final DuoOIDCAuthenticationContext duoContext) {
+ JWTClaimsSet claimsSet = null;
+ try {
+ claimsSet = duoContext.getAuthToken().getJWTClaimsSet();
+ if (claimsSet == null) {
+ return null;
+ }
+ } catch (final ParseException e) {
+ return null;
+ }
+ return doLookup(claimsSet, duoContext);
+ }
+
+ /**
+ * Implemented to perform the actual lookup.
+ *
+ * @param claimsSet the claims set of the Duo authentication token
+ * @param context the Duo OIDC context
+ *
+ * @return lookup value or {@code null} if not found
+ */
+ @Nullable protected abstract T doLookup(
+ final @Nonnull JWTClaimsSet claimsSet, @Nonnull final DuoOIDCAuthenticationContext context);
+
+}
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AuditFields.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AuditFields.java
new file mode 100644
index 0000000..37b295b
--- /dev/null
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/AuditFields.java
@@ -0,0 +1,62 @@
+/*
+ * 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.authn.duo.audit;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.profile.context.AuditContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/** Constants to use for Duo audit logging fields stored in an {@link AuditContext}. */
+public final class AuditFields {
+
+ /**
+ * Constructor.
+ */
+ private AuditFields() {
+ // no op
+ }
+
+ /** Duo Transaction log discriminator. */
+ @Nonnull @NotEmpty public static final String DISCRIMINATOR = "DuoDISC";
+
+ /** Duo Transaction ID. */
+ @Nonnull @NotEmpty public static final String TXID = "DuoTXID";
+
+ /** Duo Device ID (auth_device:key). */
+ @Nonnull @NotEmpty public static final String DEVICE_ID = "DuoDID";
+
+ /** Duo Device friendly name (auth_device:name). */
+ @Nonnull @NotEmpty public static final String DEVICE_NAME = "DuoDN";
+
+ /** Duo reason. */
+ @Nonnull @NotEmpty public static final String REASON = "DuoR";
+
+ /** Duo factor used for 2FA. */
+ @Nonnull @NotEmpty public static final String FACTOR = "DuoF";
+
+ /** Duo username used for 2FA. */
+ @Nonnull @NotEmpty public static final String DUO_USER = "DuoU";
+
+ /** The response state value received from the Duo 2FA authz endpoint. */
+ @Nonnull @NotEmpty public static final String RESP_STATE = "DuoRespS";
+
+ /** The request state value sent to the Duo 2FA authz endpoint. */
+ @Nonnull @NotEmpty public static final String REQ_STATE = "DuoReqS";
+
+}
diff --git a/idp-duo-api/src/test/resources/logback-test.xml b/idp-duo-api/src/test/resources/logback-test.xml
index 6ddfb8a..608430e 100644
--- a/idp-duo-api/src/test/resources/logback-test.xml
+++ b/idp-duo-api/src/test/resources/logback-test.xml
@@ -14,13 +14,10 @@
<appender-ref ref="STDOUT" />
</logger>
- <logger name="net.shibboleth.idp.plugin.authn.duo" level="TRACE" additivity="false">
+ <logger name="net.shibboleth.idp.plugin" level="TRACE" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
- <logger name="net.shibboleth.idp.ext.authn.duo.impl.Saml2FlowTest" level="DEBUG" additivity="false">
- <appender-ref ref="STDOUT" />
- </logger>
-
+
<logger name="org.springframework.webflow" level="TRACE" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoAuthnDeviceIdAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoAuthnDeviceIdAuditExtractor.java
new file mode 100644
index 0000000..5310c54
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoAuthnDeviceIdAuditExtractor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoTokenClaimsAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo 2FA device ID (Key) from the auth_device inside the Duo token response.*/
+public class DuoAuthnDeviceIdAuditExtractor extends AbstractDuoTokenClaimsAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(
+ @Nonnull final JWTClaimsSet claimsSet, @Nonnull final DuoOIDCAuthenticationContext context) {
+ String value = null;
+ try {
+ final Map<String, Object> authnContextClaimObj =
+ claimsSet.getJSONObjectClaim(DuoOIDCAuthAPI.DUO_AUTH_CONTEXT_JSON_OBJECT);
+
+ if (authnContextClaimObj != null) {
+
+ final Object authDeviceObj =
+ authnContextClaimObj.get(DuoOIDCAuthAPI.DUO_AUTH_DEVICE_JSON_OBJECT);
+ if (authDeviceObj instanceof Map) {
+ final Map<?, ?> mapValue = (Map<?, ?>)authDeviceObj;
+
+ final Object valueObj =
+ mapValue.get(DuoOIDCAuthAPI.DUO_AUTH_DEVICE_KEY_JSON_OBJECT);
+ if (valueObj instanceof String) {
+ value = (String)valueObj;
+ }
+ }
+ }
+ } catch (final ParseException e) {
+ // Do nothing, just return null
+ }
+ return value;
+ }
+
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoAuthnDeviceNameAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoAuthnDeviceNameAuditExtractor.java
new file mode 100644
index 0000000..683b444
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoAuthnDeviceNameAuditExtractor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoTokenClaimsAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo 2FA device name from the auth_device in the Duo token response.*/
+public class DuoAuthnDeviceNameAuditExtractor extends AbstractDuoTokenClaimsAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(
+ @Nonnull final JWTClaimsSet claimsSet, @Nonnull final DuoOIDCAuthenticationContext context) {
+ String value = null;
+ try {
+ final Map<String, Object> authnContextClaimObj =
+ claimsSet.getJSONObjectClaim(DuoOIDCAuthAPI.DUO_AUTH_CONTEXT_JSON_OBJECT);
+
+ if (authnContextClaimObj != null) {
+
+ final Object authDeviceObj =
+ authnContextClaimObj.get(DuoOIDCAuthAPI.DUO_AUTH_DEVICE_JSON_OBJECT);
+ if (authDeviceObj instanceof Map) {
+ final Map<?, ?> mapValue = (Map<?, ?>)authDeviceObj;
+
+ final Object valueObj =
+ mapValue.get(DuoOIDCAuthAPI.DUO_AUTH_DEVICE_NAME_JSON_OBJECT);
+ if (valueObj instanceof String) {
+ value = (String)valueObj;
+ }
+ }
+ }
+ } catch (final ParseException e) {
+ // Do nothing, just return null
+ }
+ return value;
+ }
+
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoFactorAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoFactorAuditExtractor.java
new file mode 100644
index 0000000..fe4a035
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoFactorAuditExtractor.java
@@ -0,0 +1,56 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoTokenClaimsAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo authentication factor from the Duo token response.*/
+public class DuoFactorAuditExtractor extends AbstractDuoTokenClaimsAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(
+ @Nonnull final JWTClaimsSet claimsSet, @Nonnull final DuoOIDCAuthenticationContext context) {
+ String value = null;
+ try {
+ final Map<String, Object> authnContextClaimObj =
+ claimsSet.getJSONObjectClaim(DuoOIDCAuthAPI.DUO_AUTH_CONTEXT_JSON_OBJECT);
+
+ if (authnContextClaimObj != null) {
+ final Object valueObj =
+ authnContextClaimObj.get(DuoOIDCAuthAPI.DUO_AUTH_FACTOR_JSON_OBJECT);
+ if (valueObj instanceof String) {
+ value = (String)valueObj;
+ }
+ }
+ } catch (final ParseException e) {
+ // Do nothing, just return null
+ }
+ return value;
+ }
+
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoReasonAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoReasonAuditExtractor.java
new file mode 100644
index 0000000..3795478
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoReasonAuditExtractor.java
@@ -0,0 +1,56 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoTokenClaimsAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo authentication reason from the Duo token response.*/
+public class DuoReasonAuditExtractor extends AbstractDuoTokenClaimsAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(
+ @Nonnull final JWTClaimsSet claimsSet, @Nonnull final DuoOIDCAuthenticationContext context) {
+ String value = null;
+ try {
+ final Map<String, Object> authnContextClaimObj =
+ claimsSet.getJSONObjectClaim(DuoOIDCAuthAPI.DUO_AUTH_CONTEXT_JSON_OBJECT);
+
+ if (authnContextClaimObj != null) {
+ final Object valueObj =
+ authnContextClaimObj.get(DuoOIDCAuthAPI.DUO_REASON_JSON_OBJECT);
+ if (valueObj instanceof String) {
+ value = (String)valueObj;
+ }
+ }
+ } catch (final ParseException e) {
+ // Do nothing, just return null
+ }
+ return value;
+ }
+
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoRequestStateAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoRequestStateAuditExtractor.java
new file mode 100644
index 0000000..a7c5df1
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoRequestStateAuditExtractor.java
@@ -0,0 +1,32 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo request state OAuth 2.0 parameter from the Duo context.*/
+public class DuoRequestStateAuditExtractor extends AbstractDuoAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(@Nonnull final DuoOIDCAuthenticationContext context) {
+ return context.getRequestState();
+ }
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoResponseStateAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoResponseStateAuditExtractor.java
new file mode 100644
index 0000000..0fed05c
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoResponseStateAuditExtractor.java
@@ -0,0 +1,34 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo response state OAuth 2.0 parameter from the Duo context.*/
+public class DuoResponseStateAuditExtractor extends AbstractDuoAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(@Nonnull final DuoOIDCAuthenticationContext context) {
+ return context.getResponseState();
+ }
+
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoTransactionIdAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoTransactionIdAuditExtractor.java
new file mode 100644
index 0000000..9d70085
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoTransactionIdAuditExtractor.java
@@ -0,0 +1,56 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoTokenClaimsAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo transaction ID from the Duo token response.*/
+public class DuoTransactionIdAuditExtractor extends AbstractDuoTokenClaimsAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(
+ @Nonnull final JWTClaimsSet claimsSet, @Nonnull final DuoOIDCAuthenticationContext context) {
+ String txid = null;
+ try {
+ final Map<String, Object> authnContextClaimObj =
+ claimsSet.getJSONObjectClaim(DuoOIDCAuthAPI.DUO_AUTH_CONTEXT_JSON_OBJECT);
+
+ if (authnContextClaimObj != null) {
+ final Object txidObj =
+ authnContextClaimObj.get(DuoOIDCAuthAPI.DUO_TXID_JSON_OBJECT);
+ if (txidObj instanceof String) {
+ txid = (String)txidObj;
+ }
+ }
+ } catch (final ParseException e) {
+ // Do nothing, just return null
+ }
+ return txid;
+ }
+
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoUsernameAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoUsernameAuditExtractor.java
new file mode 100644
index 0000000..97fe80f
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/DuoUsernameAuditExtractor.java
@@ -0,0 +1,30 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import net.shibboleth.idp.plugin.authn.duo.audit.AbstractDuoAuditExtractor;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** An audit extractor to extract the Duo username from the Duo context.*/
+public class DuoUsernameAuditExtractor extends AbstractDuoAuditExtractor<String> {
+
+ @Override
+ protected String doLookup(final DuoOIDCAuthenticationContext context) {
+ return context.getUsername();
+ }
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/FixedStringAuditExtractor.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/FixedStringAuditExtractor.java
new file mode 100644
index 0000000..d0488da
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/audit/impl/FixedStringAuditExtractor.java
@@ -0,0 +1,49 @@
+/*
+ * 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.authn.duo.audit.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** A simple audit extractor that allows a fixed string to be logged.*/
+public class FixedStringAuditExtractor implements Function<ProfileRequestContext, String> {
+
+ /** The fixed string to log.*/
+ @Nonnull private final String value;
+
+ /**
+ * Constructor.
+ *
+ * @param stringToLog the fixed string to log
+ */
+ public FixedStringAuditExtractor(@Nonnull @NotEmpty @ParameterName(name = "value") final String stringToLog) {
+ value = Constraint.isNotEmpty(stringToLog, "The fixed string to log can not be empty");
+ }
+
+ @Override
+ public String apply(final ProfileRequestContext input) {
+ return value;
+ }
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnController.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnController.java
index 9f193f0..c27f4af 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnController.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnController.java
@@ -40,8 +40,8 @@ import net.shibboleth.idp.authn.ExternalAuthenticationException;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.duo.DuoClientException;
import net.shibboleth.idp.plugin.authn.duo.DuoException;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCClient;
import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCClient;
import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
@@ -133,20 +133,17 @@ public class DuoOIDCAuthnController extends AbstractInitializableComponent{
}
- log.trace("Starting Duo 2FA for integration client '{}' and user '{}'",
- duoContext.getIntegration() != null ? duoContext.getIntegration().getClientId():
- "none", duoContext.getUsername());
try {
final DuoOIDCClient client = duoContext.getClient();
if (client == null) {
throw new DuoClientException("Duo client is null, has the context been created correctly?");
}
- //generate state, stash in the context for checking on return.
- //TODO: could use a type of replay cache and storage service?
- final String nonce = DuoSupport.generateNonce(32);
- final String state = DuoSupport.generateState(nonce, key);
- //store only the nonce component as the request state. The key component is only used to resume the flow.
- duoContext.setRequestState(nonce);
+
+ final String state = DuoSupport.generateState(duoContext.getRequestState(), key);
+
+ log.info("Starting Duo 2FA for client '{}', user '{}', and unique request state '{}'",
+ duoContext.getIntegration() != null ? duoContext.getIntegration().getClientId():
+ "none", duoContext.getUsername(), duoContext.getRequestState());
String authURL;
//if an OIDC id_token nonce is supported, add it to the authz request and context
@@ -213,6 +210,11 @@ public class DuoOIDCAuthnController extends AbstractInitializableComponent{
}
duoContext.setAuthorizationCode(code);
duoContext.setResponseState(nonce);
+
+ log.info("Finished Duo 2FA for client '{}', user '{}', and unique request state '{}'",
+ duoContext.getIntegration() != null ? duoContext.getIntegration().getClientId():
+ "none", duoContext.getUsername(), nonce);
+
ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoSupport.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoSupport.java
index f97b0c3..3402970 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoSupport.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoSupport.java
@@ -74,7 +74,7 @@ public final class DuoSupport {
* @return the combined state component.
*/
@Nonnull static String generateState(@Nonnull final String nonce, @Nonnull final String key) {
- Constraint.isNotNull(nonce, "NonceHex key can not be null");
+ Constraint.isNotNull(nonce, "Nonce Hex key can not be null");
Constraint.isNotNull(key, "Webflow execution key can not be null");
final String keyHex = Hex.encodeHexString(key.getBytes());
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PopulateDuoAuthenticationContext.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PopulateDuoAuthenticationContext.java
index 9851e75..569a6b1 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PopulateDuoAuthenticationContext.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/PopulateDuoAuthenticationContext.java
@@ -51,10 +51,12 @@ import net.shibboleth.utilities.java.support.logic.FunctionSupport;
* An action to create (or lookup) and populate the {@link DuoOIDCAuthenticationContext}
* with the username, chosen {@link DuoOIDCIntegration}, and {@link DuoOIDCClient} appropriate for this request.
*
- * <p>Also determines the usable redirect_uri, either from one registered, or computed from the
+ * <p>Determines the usable redirect_uri, either from one registered, or computed from the
* HTTP request. Is set once, before the client is constructed, for every client. If however, the client supports
* dynamic use of the redirect_uri, it is also set into the context for use downstream.</p>
*
+ * <p>Adds the nonce part of the state parameter for matching on callback from the 2FA check.</p>
+ *
* @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
* @event {@link org.opensaml.profile.action.EventIds#INVALID_PROFILE_CTX}
* @event {@link net.shibboleth.idp.authn.AuthnEventIds#NO_CREDENTIALS}
@@ -205,6 +207,12 @@ public class PopulateDuoAuthenticationContext extends AbstractAuthenticationActi
}
context.setUsername(username);
+ // Generate state, stash in the context for checking on return.
+ final String nonce = DuoSupport.generateNonce(32);
+ // Store only the nonce component as the request state. The SWF key is added by the controller
+ // And included in the authorization request to Duo.
+ context.setRequestState(nonce);
+
try {
computeAndStoreRedirectURIIfSupported(duoIntegration, request, context);
diff --git a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 285d8e0..807d5f6 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -34,5 +34,94 @@
<!-- Controller implementation -->
<bean id="shibboleth.DuoOIDCAuthnController"
class="net.shibboleth.idp.plugin.authn.duo.impl.DuoOIDCAuthnController" />
+
+
+ <!-- Default audit format and extractors -->
+ <util:map id="shibboleth.authn.DuoOIDC.DefaultAuditFormattingMap">
+ <entry key="Shibboleth-Audit" value="%a|%ST|%T|%DuoDISC|%DuoU|%DuoReqS|%DuoRespS|%DuoTXID|%DuoDID|%DuoDN|%DuoR|%DuoF" />
+ </util:map>
+
+
+ <bean id="shibboleth.authn.DuoOIDC.DefaultPreDuoPopulateAuditExtractors" lazy-init="true"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.DISCRIMINATOR"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.FixedStringAuditExtractor"
+ c:value="Duo2FA"/>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.DUO_USER"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoUsernameAuditExtractor"/>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.REQ_STATE"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoRequestStateAuditExtractor"/>
+ </entry>
+ </map>
+ </property>
+ </bean>
+
+ <!-- Included so the state can be stashed inside the audit context before it is validated and removed -->
+ <bean id="shibboleth.authn.DuoOIDC.DefaultPreStateValidationPopulateAuditExtractors" lazy-init="true"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.RESP_STATE"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoResponseStateAuditExtractor"/>
+ </entry>
+ </map>
+ </property>
+ </bean>
+
+
+ <bean id="shibboleth.authn.DuoOIDC.DefaultPostDuoPopulateAuditExtractors" lazy-init="true"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.TXID"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoTransactionIdAuditExtractor"/>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.DEVICE_ID"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoAuthnDeviceIdAuditExtractor"/>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.DEVICE_NAME"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoAuthnDeviceNameAuditExtractor"/>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.REASON"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoReasonAuditExtractor"/>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.authn.duo.audit.AuditFields.FACTOR"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.authn.duo.audit.impl.DuoFactorAuditExtractor"/>
+ </entry>
+
+ </map>
+ </property>
+ </bean>
</beans>
\ No newline at end of file
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
index 102b10f..fc970c2 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
@@ -123,6 +123,10 @@
class="net.shibboleth.idp.plugin.authn.duo.impl.HealthCheckDuoOIDCAuthAPI"
p:activationCondition="%{idp.duo.oidc.healthcheck.enabled:true}">
</bean>
+
+ <bean id="PreDuoPopulateAuditContext" parent="shibboleth.authn.DuoOIDC.AbstractPopulateAuditContext"
+ p:fieldExtractors="#{getObject('shibboleth.authn.DuoOIDC.PreDuoPopulateAuditExtractors') ?: getObject('shibboleth.authn.DuoOIDC.DefaultPreDuoPopulateAuditExtractors')}" />
+
<bean id="ValidateExternalAuthenticationContext" scope="prototype"
class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateExternalAuthenticationContext" />
@@ -231,6 +235,21 @@
<bean id="shibboleth.authn.DuoOIDC.DefaultCleanupHook"
class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenAuthenticationResult.DuoOIDCCleanupHook" />
+
+
+ <bean id="PreStateValidationPopulateAuditContext" parent="shibboleth.authn.DuoOIDC.AbstractPopulateAuditContext"
+ p:fieldExtractors="#{getObject('shibboleth.authn.DuoOIDC.PreStateValidationPopulateAuditExtractors') ?: getObject('shibboleth.authn.DuoOIDC.DefaultPreStateValidationPopulateAuditExtractors')}" />
+
+
+ <bean id="PostDuoPopulateAuditContext" parent="shibboleth.authn.DuoOIDC.AbstractPopulateAuditContext"
+ p:fieldExtractors="#{getObject('shibboleth.authn.DuoOIDC.PostDuoPopulateAuditExtractors') ?: getObject('shibboleth.authn.DuoOIDC.DefaultPostDuoPopulateAuditExtractors')}" />
+
+ <bean id="WriteAuditLog" class="net.shibboleth.idp.profile.audit.impl.WriteAuditLog" scope="prototype"
+ p:formattingMap="#{getObject('shibboleth.authn.DuoOIDC.AuditFormattingMap') ?: getObject('shibboleth.authn.DuoOIDC.DefaultAuditFormattingMap')}"
+ p:dateTimeFormat="#{getObject('shibboleth.AuditDateTimeFormat')}"
+ p:useDefaultTimeZone="#{getObject('shibboleth.AuditDefaultTimeZone') ?: false}"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:activationCondition="%{idp.duo.oidc.writeaudit:false}"/>
<bean id="ValidateDuoTokenAuthenticationResult" scope="prototype"
class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenAuthenticationResult"
@@ -241,4 +260,18 @@
p:addDefaultPrincipals="#{getObject('shibboleth.authn.DuoOIDC.ContextToPrincipalMappingStrategy') == null}"/>
+ <!-- Audit System Setup Specific to Duo -->
+
+ <bean id="shibboleth.authn.DuoOIDC.AbstractPopulateAuditContext" abstract="true"
+ class="net.shibboleth.idp.profile.audit.impl.PopulateAuditContext" scope="prototype"
+ p:formattingMapParser-ref="shibboleth.authn.DuoOIDC.AuditFormattingMapParser"
+ p:dateTimeFormat="#{getObject('shibboleth.AuditDateTimeFormat')}"
+ p:useDefaultTimeZone="#{getObject('shibboleth.AuditDefaultTimeZone') ?: false}"
+ p:fieldReplacements="#{getObject('shibboleth.AuditFieldReplacementMap')}" />
+
+ <bean id="shibboleth.authn.DuoOIDC.AuditFormattingMapParser" scope="prototype"
+ class="net.shibboleth.idp.profile.audit.impl.PopulateAuditContext.FormattingMapParser"
+ c:_0="#{getObject('shibboleth.authn.DuoOIDC.AuditFormattingMap') ?: getObject('shibboleth.authn.DuoOIDC.DefaultAuditFormattingMap')}" />
+
+
</beans>
\ No newline at end of file
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml
index ca568ce..d83170c 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml
@@ -26,6 +26,8 @@
<action-state id="CheckDuoOIDCAuthAPI">
<evaluate expression="PopulateDuoAuthenticationContext" />
<evaluate expression="HealthCheckDuoOIDCAuthAPI" />
+ <evaluate expression="PreDuoPopulateAuditContext" />
+ <evaluate expression="WriteAuditLog"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="Duo2FAAuthorizationRequest" />
</action-state>
@@ -44,6 +46,8 @@
<!-- match the response state to the request state, fail if error -->
<action-state id="ValidateDuoResponse">
<evaluate expression="ValidateExternalAuthenticationContext"/>
+ <!-- Populate the audit context with the state before it is internall removed -->
+ <evaluate expression="PreStateValidationPopulateAuditContext" />
<evaluate expression="ValidateDuoResponseState"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="ExchangeCodeForDuoToken" />
@@ -54,8 +58,10 @@
<!-- validate the token signature and claims -->
<evaluate expression="ValidateTokenSignature"/>
<evaluate expression="ValidateTokenClaims"/>
+ <evaluate expression="PostDuoPopulateAuditContext" />
+ <evaluate expression="WriteAuditLog" />
<!-- final validation of the response status to build an authn result -->
- <evaluate expression="ValidateDuoTokenAuthenticationResult"/>
+ <evaluate expression="ValidateDuoTokenAuthenticationResult"/>
<evaluate expression="'proceed'" />
<transition on="proceed" to="proceed" />
</action-state>
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnControllerTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnControllerTest.java
index 669df02..43ebad8 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnControllerTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoOIDCAuthnControllerTest.java
@@ -85,7 +85,7 @@ public class DuoOIDCAuthnControllerTest extends AbstractTestNGSpringContextTests
@Nonnull private final String KEY = "e1s1";
/** Duo OIDC nonce component of the state parameter.*/
- @Nonnull private String NONCE = "8821febf7b3237fc3974f76a75f37e8c";
+ @Nonnull private final String NONCE = "8821febf7b3237fc3974f76a75f37e8c";
/** Duo OIDC state parameter.*/
@NonnullAfterInit private String state;
@@ -284,7 +284,7 @@ public class DuoOIDCAuthnControllerTest extends AbstractTestNGSpringContextTests
final FlowExecutionRepository mockFlowExecutionRepo = Mockito.mock(FlowExecutionRepository.class);
final FlowExecution mockFlowExecution = Mockito.mock(FlowExecution.class);
- final MutableAttributeMap<Object> map = new LocalAttributeMap<Object>();
+ final MutableAttributeMap<Object> map = new LocalAttributeMap<>();
map.put(ProfileRequestContext.BINDING_KEY, buildProfileRequestContext());
Mockito.when(mockFlowExecutor.getExecutionRepository()).thenReturn(mockFlowExecutionRepo);
@@ -322,6 +322,7 @@ public class DuoOIDCAuthnControllerTest extends AbstractTestNGSpringContextTests
dc.setUsername("jdoe");
dc.setIntegration(integ);
dc.setClient(new MockDuoOIDCClient_OK(integ));
+ dc.setRequestState(NONCE);
final AuthenticationFlowDescriptor afd = new AuthenticationFlowDescriptor();
afd.setId("authn/DuoOIDC");
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties b/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties
index 436aee4..1533dd5 100644
--- a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties
+++ b/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties
@@ -60,4 +60,7 @@ idp.duo.oidc.redirectURL = https://<hostname>:<port>/idp/profile/Authn/Duo/2FA/d
#idp.duo.oidc.jwt.verifier.issuerPath = /oauth/v1/token
#idp.duo.oidc.jwt.verifier.preferredUsername = preferred_username
# Applies only to forced authentication
-#idp.duo.oidc.jwt.verifier.authLifetime = PT60S
\ No newline at end of file
+#idp.duo.oidc.jwt.verifier.authLifetime = PT60S
+
+## Write audit entries before the Duo redirect and after response validation
+#idp.duo.oidc.writeaudit = false
\ No newline at end of file
diff --git a/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties b/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties
index f0abb74..6d787d7 100644
--- a/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties
+++ b/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties
@@ -48,4 +48,7 @@ idp.duo.oidc.redirectURL = https://<hostname>:<port>/idp/profile/Authn/Duo/2FA/d
#idp.duo.oidc.jwt.verifier.issuerPath = /oauth/v1/token
#idp.duo.oidc.jwt.verifier.preferredUsername = preferred_username
# Applies only to forced authentication
-#idp.duo.oidc.jwt.verifier.authLifetime = PT60S
\ No newline at end of file
+#idp.duo.oidc.jwt.verifier.authLifetime = PT60S
+
+## Write audit entries before the Duo redirect and after response validation
+#idp.duo.oidc.writeaudit = false
\ 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