[java-idp-plugin-duo] branch master updated: Add Issuer validation for completeness

Phil Smart philip.smart at jisc.ac.uk
Wed Aug 5 12:52:51 UTC 2020


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch master
in repository java-idp-plugin-duo.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=15579841e58ceb2e03726cd804c3fd5cf8abf72b

The following commit(s) were added to refs/heads/master by this push:
       new  1557984   Add Issuer validation for completeness
1557984 is described below

commit 15579841e58ceb2e03726cd804c3fd5cf8abf72b
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Aug 5 13:52:46 2020 +0100

    Add Issuer validation for completeness
---
 .../authn/duo/impl/ValidateDuoTokenIssuedAt.java   |  3 +-
 .../authn/duo/impl/ValidateDuoTokenIssuer.java     | 48 ++++++++++++++--
 .../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml   | 64 +++++++++++-----------
 .../flows/authn/DuoOIDC/duo-oidc-authn-flow.xml    |  1 +
 .../impl/AbstractAuthnXmlFlowExecutionTests.java   |  9 ++-
 .../authn/duo/impl/AbstractDuoActionTest.java      |  8 +--
 .../plugin/authn/duo/impl/DuoAuthnFlowTest.java    | 12 ++--
 .../duo/impl/ValidateDuoTokenAudienceTest.java     |  4 +-
 .../ValidateDuoTokenAuthenticationResultTest.java  |  6 +-
 .../ValidateDuoTokenAuthenticationTimeTest.java    | 10 ++--
 .../impl/ValidateDuoTokenExpirationTimeTest.java   |  8 +--
 .../duo/impl/ValidateDuoTokenIssuedAtTest.java     |  6 +-
 ...ctTest.java => ValidateDuoTokenIssuerTest.java} | 61 +++++++++++----------
 .../duo/impl/ValidateDuoTokenSubjectTest.java      |  4 +-
 .../plugin/authn/mock/MockDuoOIDCClient_OK.java    |  2 +-
 15 files changed, 146 insertions(+), 100 deletions(-)

diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAt.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAt.java
index ed9906f..4ee87c0 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAt.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAt.java
@@ -57,7 +57,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * @event {@link net.shibboleth.idp.authn.AuthnEventIds#AUTHN_EXCEPTION}
  */
 public class ValidateDuoTokenIssuedAt extends AbstractDuoAuthenticationAction {
-
+    
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateDuoTokenIssuedAt.class);
        
@@ -70,6 +70,7 @@ public class ValidateDuoTokenIssuedAt extends AbstractDuoAuthenticationAction {
     /** The Duo authentication token. */
     @Nullable private DuoAuthToken token;
     
+    
     /** Constructor. */
     public ValidateDuoTokenIssuedAt() {        
         iatMaxClockSkew = Duration.ofMinutes(3);
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuer.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuer.java
index 0e2de20..298a31a 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuer.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuer.java
@@ -30,12 +30,16 @@ import net.shbboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
 import net.shbboleth.idp.plugin.authn.duo.model.DuoAuthToken;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * An action that verifies the issuer (iss) of the id_token exactly matches that of the configured 
- * Duo token provider. See section 3.1.3.7 of OpenID Connect core 1.0.
+ * <p>An action that verifies the issuer (iss) of the id_token exactly matches that of the configured 
+ * Duo token provider. See section 3.1.3.7 of OpenID Connect core 1.0.</p>
  * 
- * * @pre
+ * <p>This is specific to the Duo implementation, and mimics the native Duo client issuer validation.</p>
+ * 
+ * @pre
  * 
  * <pre>
  * ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null
@@ -56,15 +60,35 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
  * @event {@link net.shibboleth.idp.authn.AuthnEventIds#AUTHN_EXCEPTION}
  * 
- * TODO: Can we do this for Duo (The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery) MUST exactly match the value of the iss (issuer) Claim.)
  */
 public class ValidateDuoTokenIssuer extends AbstractDuoAuthenticationAction {
+    
+    /** HTTPS scheme protocol.*/
+    private static final String HTTPS = "https://";
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateDuoTokenIssuer.class);
 
     /** The Duo authentication token. */
     @Nullable private DuoAuthToken token;
+    
+    /** The URL path component of the issuer.*/
+    @Nonnull private String issuerPath;
+    
+    /** Constructor.*/
+    public ValidateDuoTokenIssuer() {
+        issuerPath = "/oauth/v1/token";
+    }
+    
+    /**
+     * Sets the issuer URL path component.
+     * 
+     * @param path the issuer path
+     */
+    public synchronized void setIssuerPath(@Nonnull final String path) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        issuerPath = Constraint.isNotNull(path, "Issuer URL path cannot be null");       
+    }
 
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -97,9 +121,21 @@ public class ValidateDuoTokenIssuer extends AbstractDuoAuthenticationAction {
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
             return;
         }
+        final String apiHost = duoContext.getIntegration().getAPIHost();
+        if (apiHost == null) {
+            log.error("{} No Duo integration API host found", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+            return;
+        }
         
-       //TODO: NOT IMPLEMENTED.
-        //audience is fine
+        final String issuerFromIntegration = HTTPS+apiHost+issuerPath;
+        if (!issuer.equals(issuerFromIntegration)) {
+            log.error("{} Token issuer differs from that expected, issuer is '{}', expected '{}'",getLogPrefix(),
+                    issuer,issuerFromIntegration);
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+            return;
+        }
+        //isuer is fine.
 
     }
 
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 139e68f..479ee38 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
@@ -24,26 +24,24 @@
 
     <!-- Default Duo Integration -->
     <bean id="shibboleth.authn.duo.OIDC.DuoIntegration"
-        class="net.shbboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration" 
-        p:APIHost="%{idp.duo.oidc.apiHost:none}"
-        p:clientId="%{idp.duo.oidc.clientId:none}"
-        p:secretKey="%{idp.duo.oidc.secretKey:none}" 
-        p:redirectURI="%{idp.duo.oidc.redirectUri:none}" />        
+        class="net.shbboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration" p:APIHost="%{idp.duo.oidc.apiHost:none}"
+        p:clientId="%{idp.duo.oidc.clientId:none}" p:secretKey="%{idp.duo.oidc.secretKey:none}"
+        p:redirectURI="%{idp.duo.oidc.redirectUri:none}" />
     <bean id="shibboleth.authn.duo.OIDC.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant"
         c:target-ref="shibboleth.authn.duo.OIDC.DuoIntegration" />
-        
-     <!-- Default username comes from previous c14n or session. -->        
+
+    <!-- Default username comes from previous c14n or session. -->
     <bean id="shibboleth.authn.duo.OIDC.UsernameLookupStrategy"
         class="net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy" />
-     
-     <!-- Duo Client factory and bean registry -->
+
+    <!-- Duo Client factory and bean registry -->
     <bean id="shibboleth.authn.duo.OIDC.clientRegistry" scope="singleton"
         class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoOIDCClientRegistry"
         p:clientFactory="#{getObject('shibboleth.authn.duo.OIDC.clientFactory') ?: getObject('shibboleth.authn.duo.OIDC.internal.clientFactory')}" />
 
-    
-    <!-- FIXME: aim here is to allow this to be switched out, but as it stands you always need this dep as it is
-    explicit here! -->
+
+    <!-- FIXME: aim here is to allow this to be switched out, but as it stands you always need this dep as it is explicit 
+        here! -->
     <bean id="shibboleth.authn.duo.OIDC.internal.clientFactory"
         class="net.shibboleth.idp.plugin.authn.duo.sdk.impl.DuoSDKClientFactory" scope="singleton">
         <!-- certficate pins. probably need managing external to the flow/plugin or overwritable -->
@@ -62,12 +60,12 @@
             </list>
         </property>
     </bean>
-        
-          
+
+
     <!-- Can override one or more of the beans above. -->
     <import resource="${idp.duo.oidc.config.user:%{idp.home}/conf/authn/duo-authn-config.xml}" />
 
-   
+
     <!-- Prototype per conversation beans -->
     <bean id="PopulateDuoAuthenticationContext" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.PopulateDuoAuthenticationContext"
@@ -76,35 +74,37 @@
         p:clientRegistry-ref="shibboleth.authn.duo.OIDC.clientRegistry" />
 
     <bean id="HealthCheckDuoOIDCAuthAPI" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.duo.impl.HealthCheckDuoOIDCAuthAPI"/>
+        class="net.shibboleth.idp.plugin.authn.duo.impl.HealthCheckDuoOIDCAuthAPI" />
 
     <bean id="ValidateDuoResponseState" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoResponseState" />
-        
-     <bean id="ValidateDuoTokenAudience" scope="prototype"
+
+    <bean id="ValidateDuoTokenAudience" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenAudience" />
-        
-     <bean id="ValidateDuoTokenExpirationTime" scope="prototype"
+
+    <bean id="ValidateDuoTokenIssuer" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenIssuer" />
+
+    <bean id="ValidateDuoTokenExpirationTime" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenExpirationTime" />
-        
-      <bean id="ValidateDuoTokenAuthenticationTime" scope="prototype"
+
+    <bean id="ValidateDuoTokenAuthenticationTime" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenAuthenticationTime" />
-        
-     <bean id="ValidateDuoTokenIssuedAt" scope="prototype"
+
+    <bean id="ValidateDuoTokenIssuedAt" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenIssuedAt" />
 
     <bean id="ExchangeCodeForDuoToken" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.duo.impl.ExchangeCodeForDuoToken"/>
+        class="net.shibboleth.idp.plugin.authn.duo.impl.ExchangeCodeForDuoToken" />
 
-    <bean id="ValidateDuoTokenAuthenticationResult" scope="prototype" 
-        class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenAuthenticationResult" 
+    <bean id="ValidateDuoTokenAuthenticationResult" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoTokenAuthenticationResult"
         p:addDefaultPrincipals="#{getObject('shibboleth.authn.duo.OIDC.addDefaultPrincipals') ?: true}"
         p:classifiedMessages="#{getObject('shibboleth.authn.duo.OIDC.ClassifiedMessageMap') ?: getObject('shibboleth.authn.duo.OIDC.DefaultClassifiedMessageMap')}"
-        p:resultCachingPredicate="#{getObject('shibboleth.authn.duo.OIDC.resultCachingPredicate')}"
-        />
-               
-        <!-- TODO may not be the correct values for the new API -->
-     <util:map id="shibboleth.authn.duo.OIDC.DefaultClassifiedMessageMap">
+        p:resultCachingPredicate="#{getObject('shibboleth.authn.duo.OIDC.resultCachingPredicate')}" />
+
+    <!-- TODO may not be the correct values for the new API -->
+    <util:map id="shibboleth.authn.duo.OIDC.DefaultClassifiedMessageMap">
         <entry key="AccountLocked">
             <list>
                 <value>locked_out</value>
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 4b68345..3015391 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
@@ -35,6 +35,7 @@
         <evaluate expression="ExchangeCodeForDuoToken"/>
         <!-- validate the token and set principal -->
         <evaluate expression="ValidateDuoTokenAudience"/>
+        <evaluate expression="ValidateDuoTokenIssuer"/>
         <evaluate expression="ValidateDuoTokenExpirationTime"/>
         <evaluate expression="ValidateDuoTokenAuthenticationTime"/>
         <evaluate expression="ValidateDuoTokenIssuedAt"/>
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
index 4c93c3c..db64e0f 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
@@ -328,7 +328,8 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends AbstractXmlFlow
      * @param forceAuthn force authentication
      * @return a profile request context.
      */
-    @Nonnull protected ProfileRequestContext buildProfileRequestContext(@Nonnull final boolean forceAuthn) {
+    @Nonnull protected ProfileRequestContext buildProfileRequestContext(@Nonnull final boolean forceAuthn,
+            @Nonnull boolean addC14Context) {
         
         final ProfileRequestContext prc = new ProfileRequestContext();
         final AuthenticationContext ac = new AuthenticationContext();
@@ -338,9 +339,11 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends AbstractXmlFlow
         ac.addSubcontext(new RelyingPartyUIContext());
         ac.setForceAuthn(forceAuthn);
         prc.addSubcontext(ac);
+        if (addC14Context) {
         final SubjectCanonicalizationContext scc = new SubjectCanonicalizationContext();
-        scc.setPrincipalName("jdoe");
-        prc.addSubcontext(scc);
+            scc.setPrincipalName("jdoe");
+            prc.addSubcontext(scc);
+        }
         final SessionContext sc = new SessionContext();
         final IdPSession session = Mockito.mock(IdPSession.class);
         Mockito.when(session.getPrincipalName()).thenReturn("jdoe");
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java
index 6e706e8..f81d2d7 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java
@@ -60,7 +60,7 @@ public abstract class AbstractDuoActionTest {
     
     protected static final String REDIRECT_URI = "http://localhost/";
     
-    protected static final String API_HOST = "host.com";
+    protected static final String API_HOST = "api.duosecurity.com";
     
     protected static final String SECRET = "rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh";
        
@@ -103,15 +103,15 @@ public abstract class AbstractDuoActionTest {
      * @param exp expiration time.
      * @param iat issued at.
      * @param authTime the auth time.
-     * 
+     * @param apiHost TODO
      * @return the duo auth token.
      */
     protected DuoAuthToken createDummyToken(@Nonnull final String authResult, 
             @Nonnull final String authResultMessage, @Nonnull final String aud,
             @Nonnull final Instant exp, @Nonnull final Instant iat, 
-            @Nonnull final Instant authTime) {
+            @Nonnull final Instant authTime, @Nonnull final String apiHost) {
         return DuoAuthToken.builder()
-                .withIss("https://api.duosecurity.com/oauth/v1/token")
+                .withIss("https://"+apiHost+"/oauth/v1/token")
                 .withSub("jdoe")
                 .withAud(aud)
                 .withExp(Math.toIntExact(exp.getEpochSecond()))
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
index d8c6ee3..9dd7584 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
@@ -114,7 +114,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
-        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false));
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false,true));
         updateFlowExecution(flowExecution);
         flowExecution.start(inputMap, externalContext);         
         assertFlowExecutionEnded();
@@ -143,7 +143,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
-        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false));
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false,true));
         updateFlowExecution(flowExecution);         
         flowExecution.start(inputMap, externalContext);         
         assertFlowExecutionActive();
@@ -174,7 +174,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
-        final ProfileRequestContext prcOne =  buildProfileRequestContext(false);
+        final ProfileRequestContext prcOne =  buildProfileRequestContext(false,true);
         final RelyingPartyContext rpcOne = new RelyingPartyContext();
         rpcOne.setRelyingPartyId(FIRST_INTEGRATION_SP);
         prcOne.addSubcontext(rpcOne);
@@ -217,7 +217,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
-        final ProfileRequestContext prcOne =  buildProfileRequestContext(false);
+        final ProfileRequestContext prcOne =  buildProfileRequestContext(false,true);
         final RelyingPartyContext rpcOne = new RelyingPartyContext();
         rpcOne.setRelyingPartyId(SECOND_INTEGRATION_SP);
         prcOne.addSubcontext(rpcOne);
@@ -261,7 +261,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
-        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false));
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false,true));
         updateFlowExecution(flowExecution);         
         flowExecution.start(inputMap, externalContext);         
         assertFlowExecutionActive();
@@ -289,7 +289,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
 
         final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
                 .createFlowExecution(getFlowDefinition());
-        final ProfileRequestContext prc =  buildProfileRequestContext(false);
+        final ProfileRequestContext prc =  buildProfileRequestContext(false,false);
         //add a DuoContext
         final DuoOIDCAuthenticationContext duoContext = new DuoOIDCAuthenticationContext();
         final String nonce = DuoSupport.generateNonce(32);
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAudienceTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAudienceTest.java
index 7e0949b..0815b9e 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAudienceTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAudienceTest.java
@@ -69,7 +69,7 @@ public class ValidateDuoTokenAudienceTest extends AbstractDuoActionTest {
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -84,7 +84,7 @@ public class ValidateDuoTokenAudienceTest extends AbstractDuoActionTest {
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful","WRONG",
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationResultTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationResultTest.java
index e725765..4f90ae6 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationResultTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationResultTest.java
@@ -61,7 +61,7 @@ public class ValidateDuoTokenAuthenticationResultTest extends AbstractDuoActionT
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID,
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         dc.setUsername("jdoe");
         action.initialize();
         
@@ -100,7 +100,7 @@ public class ValidateDuoTokenAuthenticationResultTest extends AbstractDuoActionT
         action.setClassifiedMessages(classifiedMessages);
         
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_DENY,"Account locked",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         dc.setUsername("jdoe");
         action.initialize();
         
@@ -140,7 +140,7 @@ public class ValidateDuoTokenAuthenticationResultTest extends AbstractDuoActionT
         addAttemptedFlow("authn/DuoOIDC");
         //made up fail message, TODO: do they describe this anywhere.
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_DENY,"Login Failed",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         dc.setUsername("jdoe");
         action.initialize();
         
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTimeTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTimeTest.java
index 1a6e876..0af88ee 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTimeTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTimeTest.java
@@ -68,7 +68,7 @@ public class ValidateDuoTokenAuthenticationTimeTest extends AbstractDuoActionTes
         action.setClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         action.setAuthnLifetime(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now()));
+                Instant.now(),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -90,7 +90,7 @@ public class ValidateDuoTokenAuthenticationTimeTest extends AbstractDuoActionTes
         action.setClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         action.setAuthnLifetime(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now()));
+                Instant.now(),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -112,7 +112,7 @@ public class ValidateDuoTokenAuthenticationTimeTest extends AbstractDuoActionTes
         action.setClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         action.setAuthnLifetime(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().plus(Duration.of(10, ChronoUnit.MINUTES))));
+                Instant.now(),Instant.now(), Instant.now().plus(Duration.of(10, ChronoUnit.MINUTES)), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -134,7 +134,7 @@ public class ValidateDuoTokenAuthenticationTimeTest extends AbstractDuoActionTes
         action.setClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         action.setAuthnLifetime(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().minus(Duration.of(10, ChronoUnit.MINUTES))));
+                Instant.now(),Instant.now(), Instant.now().minus(Duration.of(10, ChronoUnit.MINUTES)), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         assertEventId(event, AuthnEventIds.AUTHN_EXCEPTION);
@@ -155,7 +155,7 @@ public class ValidateDuoTokenAuthenticationTimeTest extends AbstractDuoActionTes
         action.setClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         action.setAuthnLifetime(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().plus(Duration.of(10, ChronoUnit.MINUTES))));
+                Instant.now(),Instant.now(), Instant.now().plus(Duration.of(10, ChronoUnit.MINUTES)), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         assertEventId(event, AuthnEventIds.AUTHN_EXCEPTION);
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenExpirationTimeTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenExpirationTimeTest.java
index 4d54bcb..e186ce1 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenExpirationTimeTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenExpirationTimeTest.java
@@ -56,7 +56,7 @@ public class ValidateDuoTokenExpirationTimeTest extends AbstractDuoActionTest {
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -76,7 +76,7 @@ public class ValidateDuoTokenExpirationTimeTest extends AbstractDuoActionTest {
         addAttemptedFlow("authn/DuoOIDC");
         //create token, set expiry 1 day in the past.
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().minus(1,ChronoUnit.DAYS),Instant.now(), Instant.now()));
+                Instant.now().minus(1,ChronoUnit.DAYS),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         assertEventId(event, AuthnEventIds.AUTHN_EXCEPTION);
@@ -98,7 +98,7 @@ public class ValidateDuoTokenExpirationTimeTest extends AbstractDuoActionTest {
         action.setClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         //create token, set expiry 50 second in the past.
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().minus(50,ChronoUnit.SECONDS),Instant.now(), Instant.now()));
+                Instant.now().minus(50,ChronoUnit.SECONDS),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -120,7 +120,7 @@ public class ValidateDuoTokenExpirationTimeTest extends AbstractDuoActionTest {
         action.setClockSkew(Duration.of(0, ChronoUnit.SECONDS));
         //create token, set expiry 1 second in the past.
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().minus(1,ChronoUnit.SECONDS),Instant.now(), Instant.now()));
+                Instant.now().minus(1,ChronoUnit.SECONDS),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         assertEventId(event, AuthnEventIds.AUTHN_EXCEPTION);
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAtTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAtTest.java
index 32d3d3a..1a971f0 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAtTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuedAtTest.java
@@ -59,7 +59,7 @@ public class ValidateDuoTokenIssuedAtTest extends AbstractDuoActionTest {
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now()));
+                Instant.now(),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -79,7 +79,7 @@ public class ValidateDuoTokenIssuedAtTest extends AbstractDuoActionTest {
         addAttemptedFlow("authn/DuoOIDC");
         action.setIatMaxClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now().minus(3,ChronoUnit.MINUTES), Instant.now()));
+                Instant.now(),Instant.now().minus(3,ChronoUnit.MINUTES), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
        
@@ -99,7 +99,7 @@ public class ValidateDuoTokenIssuedAtTest extends AbstractDuoActionTest {
         addAttemptedFlow("authn/DuoOIDC");
         action.setIatMaxClockSkew(Duration.of(1, ChronoUnit.MINUTES));
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now().plus(3,ChronoUnit.MINUTES), Instant.now()));
+                Instant.now(),Instant.now().plus(3,ChronoUnit.MINUTES), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
        
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuerTest.java
similarity index 56%
copy from idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java
copy to idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuerTest.java
index 0ae9b1b..8048d76 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenIssuerTest.java
@@ -1,20 +1,3 @@
-/*
- * 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.impl;
 
 import static org.testng.Assert.assertNull;
@@ -30,20 +13,20 @@ import net.shbboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
-class ValidateDuoTokenSubjectTest extends AbstractDuoActionTest {
+public class ValidateDuoTokenIssuerTest extends AbstractDuoActionTest {
 
     /** The action to test. */
-    private ValidateDuoTokenSubject action;
+    private ValidateDuoTokenIssuer action;
 
     @BeforeMethod
     public void setUp() throws Exception {
         super.setup();
-        action = new ValidateDuoTokenSubject();
+        action = new ValidateDuoTokenIssuer();
 
     }
-
+    
     /** 
-     * Test Duo 2FA token subject validation, success.
+     * Test Duo 2FA token issuer validation, success.
      * 
      * @throws ComponentInitializationException on error.
      */
@@ -53,8 +36,9 @@ class ValidateDuoTokenSubjectTest extends AbstractDuoActionTest {
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
         dc.setUsername("jdoe");
+        
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -62,22 +46,43 @@ class ValidateDuoTokenSubjectTest extends AbstractDuoActionTest {
     }
     
     /** 
-     * Test Duo 2FA token subject validation, failure.
+     * Test Duo 2FA token issuer validation, different issuer.
      * 
      * @throws ComponentInitializationException on error.
      */
     @Test
-    public final void testExecuteInvalidSubject() throws ComponentInitializationException {
+    public final void testExecuteDifferentIssuer() throws ComponentInitializationException {
         addDuoContext();
         addDuoIntegrationToContext();
         addAttemptedFlow("authn/DuoOIDC");
-        dc.setUsername("different-user");
+        dc.setUsername("jdoe");
+        
+        dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "different.host.com"));
+        action.initialize();
+        final Event event = action.execute(src);
+        // null event is success.
+        assertEventId(event, AuthnEventIds.AUTHN_EXCEPTION);
+    }
+    
+    /** 
+     * Test Duo 2FA token issuer validation, no context.
+     * 
+     * @throws ComponentInitializationException on error.
+     */
+    @Test
+    public final void testExecuteNoIntegration() throws ComponentInitializationException {
+        addDuoContext();
+
+        addAttemptedFlow("authn/DuoOIDC");
+        dc.setUsername("jdoe");
+        
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "different.host.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
-        assertEventId(event, AuthnEventIds.INVALID_CREDENTIALS);
+        assertEventId(event, AuthnEventIds.AUTHN_EXCEPTION);
     }
 
 }
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java
index 0ae9b1b..0dc31c0 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubjectTest.java
@@ -54,7 +54,7 @@ class ValidateDuoTokenSubjectTest extends AbstractDuoActionTest {
         addAttemptedFlow("authn/DuoOIDC");
         dc.setUsername("jdoe");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
@@ -73,7 +73,7 @@ class ValidateDuoTokenSubjectTest extends AbstractDuoActionTest {
         addAttemptedFlow("authn/DuoOIDC");
         dc.setUsername("different-user");
         dc.setAuthToken(createDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now()));
+                Instant.now().plus(1,ChronoUnit.MINUTES),Instant.now(), Instant.now(), "api.duosecurity.com"));
         action.initialize();
         final Event event = action.execute(src);
         // null event is success.
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
index ac65ddd..c78efc2 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
@@ -68,7 +68,7 @@ public class MockDuoOIDCClient_OK implements DuoOIDCClient{
     public DuoAuthToken exchangeAuthorizationCodeFor2FAResult(final String code, 
             final String username) throws DuoClientException {
         return DuoAuthToken.builder()
-                .withIss("https://api.duosecurity.com/oauth/v1/token")
+                .withIss("https://"+integration.getAPIHost()+"/oauth/v1/token")
                 .withSub(SUB)
                 .withAud(integration.getClientId())
                 .withExp(Math.toIntExact(Instant.now().plus(10,ChronoUnit.MINUTES).getEpochSecond()))

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list