[java-idp-integration-tests] branch main updated: Work on OIDC tests - run an RP via Docker
Tom Zeller
tzeller at dragonacea.biz
Thu Feb 22 02:07:28 UTC 2024
This is an automated email from the git hooks/post-receive script.
tzeller pushed a commit to branch main
in repository java-idp-integration-tests.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-integration-tests.git;a=commit;h=6668711a23d3e636782d4c0f6af48a0284b6d509
The following commit(s) were added to refs/heads/main by this push:
new 6668711 Work on OIDC tests - run an RP via Docker
6668711 is described below
commit 6668711a23d3e636782d4c0f6af48a0284b6d509
Author: Tom Zeller <tzeller at dragonacea.biz>
AuthorDate: Wed Feb 21 13:29:37 2024 -0600
Work on OIDC tests - run an RP via Docker
https://shibboleth.atlassian.net/browse/IDP-2243
---
pom.xml | 7 +
src/test/docker/shib-tests-rp/Dockerfile | 47 ++++
src/test/docker/shib-tests-rp/docker-compose.yml | 27 ++
.../etc/httpd/conf.modules.d/10-auth_openidc.conf | 23 ++
.../shib-tests-rp/etc/pki/tls/certs/.gitignore | 2 +
.../shib-tests-rp/etc/pki/tls/private/.gitignore | 2 +
.../docker/shib-tests-rp/var/www/cgi-bin/printenv | 26 ++
.../docker/shib-tests-rp/var/www/html/index.html | 10 +
.../shib-tests-rp/var/www/html/secure/index.html | 10 +
.../idp/integration/tests/oidc/OIDCTest.java | 92 +++++++
.../idp/integration/tests/oidc/RPContainer.java | 303 +++++++++++++++++++++
11 files changed, 549 insertions(+)
diff --git a/pom.xml b/pom.xml
index 4a2a58a..2c32b68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,6 +126,13 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>route53</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <version>1.19.5</version>
+ <scope>test</scope>
</dependency>
</dependencies>
diff --git a/src/test/docker/shib-tests-rp/Dockerfile b/src/test/docker/shib-tests-rp/Dockerfile
new file mode 100644
index 0000000..ae73c08
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/Dockerfile
@@ -0,0 +1,47 @@
+# Run httpd + mod_auth_openidc on Rocky Linux.
+#
+# Provides URLs which display the following :
+# / : "Hello world"
+# /secure/ : "Secure"
+# /cgi-bin/printenv/ : environment variables
+#
+# Only the /secure/ path is protected by mod_auth_openidc.
+
+# Use Rocky Linux 9 base image.
+FROM rockylinux:9
+
+# Install Apache with mod_auth_openidc.
+# Include perl for /cgi-bin/printenv.
+RUN dnf install -y \
+ openssl \
+ httpd \
+ mod_ssl \
+ mod_auth_openidc \
+ perl \
+ && dnf clean all \
+ && rm -rf /var/cache/yum
+
+# Replace ServerName with environment variable or the default 'rp.tests.shibboleth.net'.
+ENV ServerName=rp.tests.shibboleth.net
+
+RUN sed -i -e 's/#ServerName www.example.com:80/ServerName ${ServerName}:80/' /etc/httpd/conf/httpd.conf
+RUN sed -i -e 's/#ServerName www.example.com:443/ServerName ${ServerName}:443/' /etc/httpd/conf.d/ssl.conf
+
+# Enable httpd debug logging.
+# RUN sed -i -e 's/LogLevel warn/LogLevel debug/' /etc/httpd/conf.d/ssl.conf
+
+# Copy demo web pages, non-secure displays "Hello world" while secure displays "Secure".
+COPY var/www/html/index.html /var/www/html/index.html
+COPY var/www/html/secure/index.html /var/www/html/secureindex.html
+COPY var/www/cgi-bin/printenv /var/www/cgi-bin/printenv
+
+# Expose http and https ports.
+EXPOSE 80 443
+
+# Run httpd as apache user.
+RUN chown -R apache:apache /var/log/httpd
+RUN chown -R apache:apache /run/httpd
+
+USER apache
+
+CMD ["httpd", "-D", "FOREGROUND"]
diff --git a/src/test/docker/shib-tests-rp/docker-compose.yml b/src/test/docker/shib-tests-rp/docker-compose.yml
new file mode 100644
index 0000000..2d50f1b
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/docker-compose.yml
@@ -0,0 +1,27 @@
+# Example Docker Compose file to run the test RP container.
+#
+# Docker Compose is not used by the integration tests (they use TestContainers.com).
+# Note that the ServerName is defined as an environment variable.
+#
+# TLS cert and key should be copied to etc/pki/tls/.
+#
+services:
+ shib-tests-rp:
+ container_name: shib-tests-rp
+ image: shib-tests-rp
+ build: .
+ environment:
+ - ServerName=${ServerName:-rp.tests.shibboleth.net}
+ extra_hosts:
+ - "idp.tests.shibboleth.net:host-gateway"
+ ports:
+ - "40080:80"
+ - "40443:443"
+ volumes:
+ - ./etc/pki/tls/certs/fullchain.cer:/etc/pki/tls/certs/localhost.crt
+ - ./etc/pki/tls/private/tests.shibboleth.net.key:/etc/pki/tls/private/localhost.key
+ - ./etc/httpd/conf.modules.d/10-auth_openidc.conf:/etc/httpd/conf.modules.d/10-auth_openidc.conf
+
+networks:
+ default:
+ name: shib-tests-network
diff --git a/src/test/docker/shib-tests-rp/etc/httpd/conf.modules.d/10-auth_openidc.conf b/src/test/docker/shib-tests-rp/etc/httpd/conf.modules.d/10-auth_openidc.conf
new file mode 100644
index 0000000..8c71e1b
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/etc/httpd/conf.modules.d/10-auth_openidc.conf
@@ -0,0 +1,23 @@
+# Test configuration for mod_auth_openidc RP
+LoadModule auth_openidc_module modules/mod_auth_openidc.so
+
+OIDCProviderMetadataURL https://idp.tests.shibboleth.net:8443/.well-known/openid-configuration
+OIDCClientID test_oidc_rp
+OIDCClientSecret topsecret
+
+# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
+# Relative redirect URI makes testing easier since port is not known until after the container has started.
+# OIDCRedirectURI https://rp.tests.shibboleth.net:40443/redirect_uri
+OIDCRedirectURI /redirect_uri
+OIDCCryptoPassphrase secret_passphrase
+OIDCResponseType id_token
+
+<Location /redirect_uri>
+ AuthType openid-connect
+ Require valid-user
+</Location>
+
+<Location /secure>
+ AuthType openid-connect
+ Require valid-user
+</Location>
diff --git a/src/test/docker/shib-tests-rp/etc/pki/tls/certs/.gitignore b/src/test/docker/shib-tests-rp/etc/pki/tls/certs/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/etc/pki/tls/certs/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-rp/etc/pki/tls/private/.gitignore b/src/test/docker/shib-tests-rp/etc/pki/tls/private/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/etc/pki/tls/private/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-rp/var/www/cgi-bin/printenv b/src/test/docker/shib-tests-rp/var/www/cgi-bin/printenv
new file mode 100644
index 0000000..c15db75
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/var/www/cgi-bin/printenv
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+# To permit this cgi, replace # on the first line above with the
+# appropriate #!/path/to/perl shebang, and on Unix / Linux also
+# set this script executable with chmod 755.
+#
+# ***** !!! WARNING !!! *****
+# This script echoes the server environment variables and therefore
+# leaks information - so NEVER use it in a live server environment!
+# It is provided only for testing purpose.
+# Also note that it is subject to cross site scripting attacks on
+# MS IE and any other browser which fails to honor RFC2616.
+
+##
+## printenv -- demo CGI program which just prints its environment
+##
+use strict;
+use warnings;
+
+print "Content-type: text/plain; charset=iso-8859-1\n\n";
+foreach my $var (sort(keys(%ENV))) {
+ my $val = $ENV{$var};
+ $val =~ s|\n|\\n|g;
+ $val =~ s|"|\\"|g;
+ print "${var}=\"${val}\"\n";
+}
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-rp/var/www/html/index.html b/src/test/docker/shib-tests-rp/var/www/html/index.html
new file mode 100644
index 0000000..767a6e7
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/var/www/html/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Hello World</title>
+</head>
+<body>
+Hello world
+</body>
+</html>
\ No newline at end of file
diff --git a/src/test/docker/shib-tests-rp/var/www/html/secure/index.html b/src/test/docker/shib-tests-rp/var/www/html/secure/index.html
new file mode 100644
index 0000000..020bf1f
--- /dev/null
+++ b/src/test/docker/shib-tests-rp/var/www/html/secure/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Secure</title>
+</head>
+<body>
+Secure
+</body>
+</html>
\ No newline at end of file
diff --git a/src/test/java/net/shibboleth/idp/integration/tests/oidc/OIDCTest.java b/src/test/java/net/shibboleth/idp/integration/tests/oidc/OIDCTest.java
new file mode 100644
index 0000000..5277dbb
--- /dev/null
+++ b/src/test/java/net/shibboleth/idp/integration/tests/oidc/OIDCTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.integration.tests.oidc;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.Container.ExecResult;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.integration.tests.BaseIntegrationTest;
+import net.shibboleth.idp.integration.tests.BrowserData;
+
+// WIP
+public class OIDCTest extends BaseIntegrationTest {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(OIDCTest.class);
+
+ @BeforeClass
+ public void setUpURLs() throws Exception {
+
+ loginPageURLPath = "/idp/profile/oidc/authorize";
+
+ }
+
+ @Test(dataProvider = "sauceOnDemandBrowserDataProvider")
+ public void testRP(@Nullable final BrowserData browserData) throws Exception {
+
+ // Start the RP
+
+ final RPContainer rp = new RPContainer();
+
+ rp.setId("rp.tests.shibboleth.net");
+
+ rp.initialize();
+
+ rp.start();
+
+ // PoC : check ServerName environment variable from container itself.
+
+ final String expectedServerNameFromEnv = "SERVER_NAME=\"rp.tests.shibboleth.net\"";
+
+ final ExecResult resultFromContainer = rp.container.execInContainer( //
+ "curl", //
+ "-vvv", //
+ "https://rp.tests.shibboleth.net:443/cgi-bin/printenv");
+
+ final String envFromInsideContainer = resultFromContainer.getStdout();
+
+ log.debug("Print env from container\n{}\n", envFromInsideContainer);
+
+ Assert.assertTrue(envFromInsideContainer.contains(expectedServerNameFromEnv), "Expected ServerName not found");
+
+ // PoC : check ServerName environment variable from browser.
+
+ startSeleniumClient(browserData);
+
+ final String exposedPort = rp.httpsPort.toString();
+
+ driver.get("https://rp.tests.shibboleth.net:" + exposedPort + "/cgi-bin/printenv");
+
+ final String pageSource = getPageSource();
+
+ log.debug("Print env from browser\n{}\n", pageSource);
+
+ Assert.assertTrue(pageSource.contains(expectedServerNameFromEnv), "Expected ServerName not found");
+
+ rp.stop();
+ }
+
+}
diff --git a/src/test/java/net/shibboleth/idp/integration/tests/oidc/RPContainer.java b/src/test/java/net/shibboleth/idp/integration/tests/oidc/RPContainer.java
new file mode 100644
index 0000000..bf3030e
--- /dev/null
+++ b/src/test/java/net/shibboleth/idp/integration/tests/oidc/RPContainer.java
@@ -0,0 +1,303 @@
+/*
+ * 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.integration.tests.oidc;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.Lifecycle;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.builder.ImageFromDockerfile;
+import org.testcontainers.utility.MountableFile;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Start and stop an OpenID Connect Relying Party (RP) Docker container.
+ *
+ * Ports are defined after the container is started.
+ *
+ * Default ServerName is rp.tests.shibboleth.net, may be overridden as system
+ * property.
+ *
+ * TLS cert and key should be copied to etc/pki/tls/, or path overridden as a
+ * system property.
+ *
+ * See src/test/docker/shib-tests-rp for Dockerfile and container files.
+ */
+public class RPContainer extends AbstractIdentifiableInitializableComponent implements Lifecycle {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(RPContainer.class);
+
+ /** Cached log prefix. */
+ @Nullable
+ private String logPrefix;
+
+ /** The Docker container. */
+ @NonnullAfterInit
+ public GenericContainer<?> container;
+
+ /** Exposed HTTP port. */
+ @Nullable
+ public Integer httpPort;
+
+ /** Exposed HTTPS port. */
+ @Nullable
+ public Integer httpsPort;
+
+ /** Name of Docker image. */
+ public String image = "shib-tests-rp";
+
+ /** Path to RP directory. */
+ @NonnullAfterInit
+ private Path pathToRP;
+
+ /** System property to set path to TLS cert. */
+ final static String pathToTLSCertSystemProperty = "tlsCert";
+
+ /** System property to set path to TLS key. */
+ final static String pathToTLSKeySystemProperty = "tlsKey";
+
+ /**
+ * Path to RP directory.
+ *
+ * @return path to RP directory
+ */
+ protected Path pathToRP() {
+
+ if (pathToRP != null) {
+ return pathToRP;
+ }
+
+ final Path pathToDockerDir = Paths.get("src", "test", "docker");
+ log.trace("{} Path to docker directory '{}'", getLogPrefix(), pathToDockerDir);
+ assert pathToDockerDir.toFile().exists() : "Path to 'docker' directory does not exist";
+
+ pathToRP = pathToDockerDir.resolve(image);
+ log.trace("{} Path to '{}' directory '{}'", getLogPrefix(), image, pathToRP);
+ assert pathToRP.toFile().exists() : "Path to RP does not exist";
+
+ return pathToRP;
+ }
+
+ /**
+ * Path to Dockerfile.
+ *
+ * @return path to Dockerfile
+ */
+ protected Path pathToDockerfile() {
+
+ final Path pathToDockerfile = pathToRP().resolve("Dockerfile");
+
+ log.debug("{} Path to Dockerfile '{}'", getLogPrefix(), pathToDockerfile);
+
+ assert pathToDockerfile.toFile().exists() : "Path to Dockerfile does not exist";
+
+ return pathToDockerfile;
+ }
+
+ /**
+ * Path to TLS cert.
+ *
+ * Default path may be overridden using the {@link #pathToTLSCertSystemProperty}
+ * system property.
+ *
+ * @return path to TLS cert
+ */
+ protected Path pathToCert() {
+
+ final String defaultPathToCert = pathToRP().toString() + "/etc/pki/tls/certs/fullchain.cer";
+
+ final Path pathToCert = Paths.get(System.getProperty(pathToTLSCertSystemProperty, defaultPathToCert));
+
+ log.debug("{} Path to cert '{}'", getLogPrefix(), pathToCert);
+
+ return pathToCert;
+ }
+
+ /**
+ * Path to TLS key.
+ *
+ * Default path may be overridden using the {@link #pathToTLSKeySystemProperty}
+ * system property.
+ *
+ * @return path to TLS key
+ */
+ protected Path pathToKey() {
+
+ final String defaultPathToKey = pathToRP().toString() + "/etc/pki/tls/private/tests.shibboleth.net.key";
+
+ final Path pathToKey = Paths.get(System.getProperty(pathToTLSKeySystemProperty, defaultPathToKey));
+
+ log.debug("{} Path to key '{}'", getLogPrefix(), pathToKey);
+
+ assert pathToKey.toFile().exists() : "Path to key does not exist";
+
+ return pathToKey;
+ }
+
+ /**
+ * Path to mod_auth_openidc configuration file.
+ *
+ * @return path to mod_auth_openidc configuration file
+ */
+ protected Path pathToOpenIDCConf() {
+
+ final Path relativePath = Paths.get("etc", "httpd", "conf.modules.d", "10-auth_openidc.conf");
+
+ final Path pathToOpenIDCConf = pathToRP().resolve(relativePath);
+
+ log.debug("{} Path to 10-auth_openidc.conf '{}'", getLogPrefix(), pathToOpenIDCConf);
+
+ assert pathToOpenIDCConf.toFile().exists() : "Path to 10-auth_openidc.conf does not exist";
+
+ return pathToOpenIDCConf;
+ }
+
+ /**
+ * Set up container.
+ *
+ * Copy TLS cert and key into container.
+ *
+ * Copy mod_auth_openidc configuration file into container.
+ *
+ * Allow access to IdP / OP on host.
+ *
+ * Set container name and hostname to id.
+ *
+ * Expose ports 80 and 443.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+
+ super.doInitialize();
+
+ log.debug("{} Initializing", getLogPrefix());
+
+ // Do not delete image on exit
+ final ImageFromDockerfile imageFromDockerfile = new ImageFromDockerfile(image, false)
+ .withDockerfile(pathToDockerfile());
+ log.debug("{} Initializing with image '{}'", getLogPrefix(), imageFromDockerfile);
+
+ // TODO Does this build the image if it does not exist or is updated ?
+ log.debug("{} Initializing generic container", getLogPrefix());
+ container = new GenericContainer<>(imageFromDockerfile.getDockerImageName());
+
+ // Copy TLS cert and key to container
+ final MountableFile cert = MountableFile.forHostPath(pathToCert());
+ final MountableFile key = MountableFile.forHostPath(pathToKey());
+ container.withCopyFileToContainer(cert, "/etc/pki/tls/certs/localhost.crt");
+ container.withCopyFileToContainer(key, "/etc/pki/tls/private/localhost.key");
+
+ // Copy OpenID Connect configuration to container
+ final MountableFile conf = MountableFile.forHostPath(pathToOpenIDCConf());
+ container.withCopyFileToContainer(conf, "/etc/httpd/conf.modules.d/10-auth_openidc.conf");
+
+ // Add access to the IdP / OP
+ container.withAccessToHost(true);
+
+ // Add DNS resolution for the IdP / OP
+ container.withExtraHost("idp.tests.shibboleth.net", "host-gateway");
+
+ // Set container name to the id
+ container.withCreateContainerCmdModifier(cmd -> cmd.withName(getId()));
+
+ // Set container hostname to the id
+ container.withCreateContainerCmdModifier(cmd -> cmd.withHostName(getId()));
+
+ // Set 'ServerName' environment variable to the id
+ container.withEnv("ServerName", getId());
+
+ // Expose ports 80 and 443
+ container.addExposedPort(80);
+ container.addExposedPort(443);
+
+ log.debug("{} Initialized", getLogPrefix());
+ }
+
+ /**
+ * Return a prefix for logging messages for this component.
+ *
+ * @return a string for insertion at the beginning of any log messages
+ */
+ @Nonnull
+ @NotEmpty
+ protected String getLogPrefix() {
+ if (logPrefix == null) {
+ logPrefix = "RP '" + getId() + "' :";
+ }
+ assert logPrefix != null;
+ return logPrefix;
+ }
+
+ /**
+ * Start container and wait for web server to be available.
+ *
+ * Set {@link #httpPort} and {@link #httpsPort} from
+ * {@link GenericContainer#getMappedPort()}.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void start() {
+
+ log.info("{} Starting ...", getLogPrefix());
+ container.start();
+ log.debug("{} Started container : '{}'", getLogPrefix(), container.getContainerName());
+
+ httpPort = container.getMappedPort(80);
+ log.debug("{} HTTP port {}", getLogPrefix(), httpPort);
+
+ httpsPort = container.getMappedPort(443);
+ log.debug("{} HTTPS port {}", getLogPrefix(), httpsPort);
+
+ log.debug("{} Waiting for \"/\" to be available ...", getLogPrefix());
+ container.waitingFor(Wait.forHttp("/"));
+ container.waitingFor(Wait.forHttps("/"));
+
+ log.info("{} Started", getLogPrefix());
+ }
+
+ @Override
+ public void stop() {
+ log.debug("{} Stopping ...", getLogPrefix());
+ container.stop();
+ log.info("{} Stopped", getLogPrefix());
+ }
+
+ @Override
+ public boolean isRunning() {
+ boolean isRunning = container.isRunning();
+ log.info("{} Is running '{}'", getLogPrefix(), isRunning);
+ return isRunning;
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list