First version
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# 1920_INFOB318_CT
|
39
bin/README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Guide de démarrage rapide
|
||||||
|
|
||||||
|
## Configuration requise
|
||||||
|
Pour pouvoir exécuter l'application, il est nécessaire de disposer d'une base de données Postgresql. Les informations nécessaires pour l'accès à la base de données doivent être renseignées dans le fichier de configuration `application.properties`.
|
||||||
|
|
||||||
|
```
|
||||||
|
spring.datasource.url=jdbc:postgresql://ip_base_données:port/nom_bd
|
||||||
|
spring.datasource.username=utilisateur
|
||||||
|
spring.datasource.password=mot_de_passe
|
||||||
|
```
|
||||||
|
|
||||||
|
Remplacez les éléments suivants:
|
||||||
|
* `ip_base_données` par l'adresse IP de la machine où s'exécute la base de données
|
||||||
|
* `port` par le port sur lequel la base de données est accessible
|
||||||
|
* `nom_bd` par le nom de la base de données à utiliser
|
||||||
|
* `utilisateur` par le nom d'utilisateur à utiliser pour accéder à la base de données
|
||||||
|
* `mot_de_passe` par le mot de passe à utiliser pour accéder à la base de données
|
||||||
|
|
||||||
|
## Ajustement des paramètres
|
||||||
|
En fonction de l'environnement dans lequel va s'exécuter l'application, adaptez le nombre de threads alloués pour les différentes activités de l'application. Les paramètres par défaut permettent à l'application de s'exécuter mais n'offrent pas les meilleures performances.
|
||||||
|
|
||||||
|
## Lancement de l'application
|
||||||
|
Une fois les paramètres personnalisés dans le fichier de configuration, lancez l'application avec la commande suivante.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
java -jar ct-1.0.0-PROJECT.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour accéder à l'interface, connectez vous au port `8090` (par défaut) de la machine sur laquelle s'exécute l'application.
|
||||||
|
|
||||||
|
## Utilisation de l'application
|
||||||
|
Pour utiliser les fonctionnalités de l'application, il faut lui fournir au moins un serveur où trouver des logs. Pour commencer rapidement, rendez-vous sur la page `Servers` et ajoutez un nouveau serveur avec les informations suivantes.
|
||||||
|
|
||||||
|
```
|
||||||
|
Argon 2020
|
||||||
|
https://ct.googleapis.com/logs/argon2020/
|
||||||
|
```
|
||||||
|
|
||||||
|
Cliquez ensuite sur `Start` en regard du serveur qui vient de s'ajouter à la liste.
|
12
bin/application.properties
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Database settings
|
||||||
|
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/db
|
||||||
|
spring.datasource.username=user
|
||||||
|
spring.datasource.password=password
|
||||||
|
|
||||||
|
# Port
|
||||||
|
server.port = 8090
|
||||||
|
|
||||||
|
# Threads configuration
|
||||||
|
threads-decode = 3
|
||||||
|
threads-slice = 3
|
||||||
|
threads-scrap = 3
|
BIN
bin/ct-1.0.0-PROJECT.jar
Normal file
31
code/.gitignore
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**
|
||||||
|
!**/src/test/**
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
114
code/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF 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
|
||||||
|
|
||||||
|
https://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class MavenWrapperDownloader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_DOWNLOAD_URL =
|
||||||
|
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||||
|
* use instead of the default one.
|
||||||
|
*/
|
||||||
|
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||||
|
".mvn/wrapper/maven-wrapper.properties";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path where the maven-wrapper.jar will be saved to.
|
||||||
|
*/
|
||||||
|
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||||
|
".mvn/wrapper/maven-wrapper.jar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the property which should be used to override the default download url for the wrapper.
|
||||||
|
*/
|
||||||
|
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
System.out.println("- Downloader started");
|
||||||
|
File baseDirectory = new File(args[0]);
|
||||||
|
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||||
|
|
||||||
|
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||||
|
// wrapperUrl parameter.
|
||||||
|
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||||
|
String url = DEFAULT_DOWNLOAD_URL;
|
||||||
|
if(mavenWrapperPropertyFile.exists()) {
|
||||||
|
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||||
|
try {
|
||||||
|
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||||
|
Properties mavenWrapperProperties = new Properties();
|
||||||
|
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||||
|
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if(mavenWrapperPropertyFileInputStream != null) {
|
||||||
|
mavenWrapperPropertyFileInputStream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("- Downloading from: : " + url);
|
||||||
|
|
||||||
|
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||||
|
if(!outputFile.getParentFile().exists()) {
|
||||||
|
if(!outputFile.getParentFile().mkdirs()) {
|
||||||
|
System.out.println(
|
||||||
|
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||||
|
try {
|
||||||
|
downloadFileFromURL(url, outputFile);
|
||||||
|
System.out.println("Done");
|
||||||
|
System.exit(0);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.out.println("- Error downloading");
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||||
|
URL website = new URL(urlString);
|
||||||
|
ReadableByteChannel rbc;
|
||||||
|
rbc = Channels.newChannel(website.openStream());
|
||||||
|
FileOutputStream fos = new FileOutputStream(destination);
|
||||||
|
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||||
|
fos.close();
|
||||||
|
rbc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
code/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
1
code/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
|
286
code/mvnw
vendored
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF 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
|
||||||
|
#
|
||||||
|
# https://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.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Maven2 Start Up Batch script
|
||||||
|
#
|
||||||
|
# Required ENV vars:
|
||||||
|
# ------------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# M2_HOME - location of maven2's installed home dir
|
||||||
|
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
# e.g. to debug Maven itself, use
|
||||||
|
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||||
|
|
||||||
|
if [ -f /etc/mavenrc ] ; then
|
||||||
|
. /etc/mavenrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.mavenrc" ] ; then
|
||||||
|
. "$HOME/.mavenrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OS specific support. $var _must_ be set to either true or false.
|
||||||
|
cygwin=false;
|
||||||
|
darwin=false;
|
||||||
|
mingw=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN*) cygwin=true ;;
|
||||||
|
MINGW*) mingw=true;;
|
||||||
|
Darwin*) darwin=true
|
||||||
|
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||||
|
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
if [ -x "/usr/libexec/java_home" ]; then
|
||||||
|
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||||
|
else
|
||||||
|
export JAVA_HOME="/Library/Java/Home"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
if [ -r /etc/gentoo-release ] ; then
|
||||||
|
JAVA_HOME=`java-config --jre-home`
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$M2_HOME" ] ; then
|
||||||
|
## resolve links - $0 may be a link to maven's home
|
||||||
|
PRG="$0"
|
||||||
|
|
||||||
|
# need this for relative symlinks
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG="`dirname "$PRG"`/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
saveddir=`pwd`
|
||||||
|
|
||||||
|
M2_HOME=`dirname "$PRG"`/..
|
||||||
|
|
||||||
|
# make it fully qualified
|
||||||
|
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||||
|
|
||||||
|
cd "$saveddir"
|
||||||
|
# echo Using m2 at $M2_HOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$M2_HOME" ] &&
|
||||||
|
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||||
|
if $mingw ; then
|
||||||
|
[ -n "$M2_HOME" ] &&
|
||||||
|
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||||
|
# TODO classpath?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ]; then
|
||||||
|
javaExecutable="`which javac`"
|
||||||
|
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||||
|
# readlink(1) is not available as standard on Solaris 10.
|
||||||
|
readLink=`which readlink`
|
||||||
|
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||||
|
if $darwin ; then
|
||||||
|
javaHome="`dirname \"$javaExecutable\"`"
|
||||||
|
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||||
|
else
|
||||||
|
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||||
|
fi
|
||||||
|
javaHome="`dirname \"$javaExecutable\"`"
|
||||||
|
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||||
|
JAVA_HOME="$javaHome"
|
||||||
|
export JAVA_HOME
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVACMD" ] ; then
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="`which java`"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||||
|
echo " We cannot execute $JAVACMD" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVA_HOME" ] ; then
|
||||||
|
echo "Warning: JAVA_HOME environment variable is not set."
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||||
|
|
||||||
|
# traverses directory structure from process work directory to filesystem root
|
||||||
|
# first directory with .mvn subdirectory is considered project base directory
|
||||||
|
find_maven_basedir() {
|
||||||
|
|
||||||
|
if [ -z "$1" ]
|
||||||
|
then
|
||||||
|
echo "Path not specified to find_maven_basedir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
basedir="$1"
|
||||||
|
wdir="$1"
|
||||||
|
while [ "$wdir" != '/' ] ; do
|
||||||
|
if [ -d "$wdir"/.mvn ] ; then
|
||||||
|
basedir=$wdir
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||||
|
if [ -d "${wdir}" ]; then
|
||||||
|
wdir=`cd "$wdir/.."; pwd`
|
||||||
|
fi
|
||||||
|
# end of workaround
|
||||||
|
done
|
||||||
|
echo "${basedir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# concatenates all lines of a file
|
||||||
|
concat_lines() {
|
||||||
|
if [ -f "$1" ]; then
|
||||||
|
echo "$(tr -s '\n' ' ' < "$1")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||||
|
if [ -z "$BASE_DIR" ]; then
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
##########################################################################################
|
||||||
|
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||||
|
fi
|
||||||
|
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||||
|
while IFS="=" read key value; do
|
||||||
|
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||||
|
esac
|
||||||
|
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Downloading from: $jarUrl"
|
||||||
|
fi
|
||||||
|
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||||
|
|
||||||
|
if command -v wget > /dev/null; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Found wget ... using wget"
|
||||||
|
fi
|
||||||
|
wget "$jarUrl" -O "$wrapperJarPath"
|
||||||
|
elif command -v curl > /dev/null; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Found curl ... using curl"
|
||||||
|
fi
|
||||||
|
curl -o "$wrapperJarPath" "$jarUrl"
|
||||||
|
else
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo "Falling back to using Java to download"
|
||||||
|
fi
|
||||||
|
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||||
|
if [ -e "$javaClass" ]; then
|
||||||
|
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||||
|
fi
|
||||||
|
# Compiling the Java class
|
||||||
|
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||||
|
fi
|
||||||
|
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||||
|
# Running the downloader
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo " - Running MavenWrapperDownloader.java ..."
|
||||||
|
fi
|
||||||
|
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
##########################################################################################
|
||||||
|
# End of extension
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||||
|
if [ "$MVNW_VERBOSE" = true ]; then
|
||||||
|
echo $MAVEN_PROJECTBASEDIR
|
||||||
|
fi
|
||||||
|
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin; then
|
||||||
|
[ -n "$M2_HOME" ] &&
|
||||||
|
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||||
|
[ -n "$JAVA_HOME" ] &&
|
||||||
|
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||||
|
[ -n "$CLASSPATH" ] &&
|
||||||
|
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||||
|
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||||
|
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
exec "$JAVACMD" \
|
||||||
|
$MAVEN_OPTS \
|
||||||
|
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||||
|
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||||
|
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
161
code/mvnw.cmd
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Maven2 Start Up Batch script
|
||||||
|
@REM
|
||||||
|
@REM Required ENV vars:
|
||||||
|
@REM JAVA_HOME - location of a JDK home dir
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM M2_HOME - location of maven2's installed home dir
|
||||||
|
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||||
|
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||||
|
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||||
|
@REM e.g. to debug Maven itself, use
|
||||||
|
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||||
|
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||||
|
@echo off
|
||||||
|
@REM set title of command window
|
||||||
|
title %0
|
||||||
|
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||||
|
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||||
|
|
||||||
|
@REM set %HOME% to equivalent of $HOME
|
||||||
|
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||||
|
|
||||||
|
@REM Execute a user defined script before this one
|
||||||
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||||
|
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||||
|
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||||
|
:skipRcPre
|
||||||
|
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
set ERROR_CODE=0
|
||||||
|
|
||||||
|
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
@REM ==== START VALIDATION ====
|
||||||
|
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME not found in your environment. >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
:OkJHome
|
||||||
|
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||||
|
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||||
|
echo location of your Java installation. >&2
|
||||||
|
echo.
|
||||||
|
goto error
|
||||||
|
|
||||||
|
@REM ==== END VALIDATION ====
|
||||||
|
|
||||||
|
:init
|
||||||
|
|
||||||
|
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||||
|
@REM Fallback to current working directory if not found.
|
||||||
|
|
||||||
|
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||||
|
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||||
|
|
||||||
|
set EXEC_DIR=%CD%
|
||||||
|
set WDIR=%EXEC_DIR%
|
||||||
|
:findBaseDir
|
||||||
|
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||||
|
cd ..
|
||||||
|
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||||
|
set WDIR=%CD%
|
||||||
|
goto findBaseDir
|
||||||
|
|
||||||
|
:baseDirFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
goto endDetectBaseDir
|
||||||
|
|
||||||
|
:baseDirNotFound
|
||||||
|
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||||
|
cd "%EXEC_DIR%"
|
||||||
|
|
||||||
|
:endDetectBaseDir
|
||||||
|
|
||||||
|
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||||
|
|
||||||
|
@setlocal EnableExtensions EnableDelayedExpansion
|
||||||
|
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||||
|
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||||
|
|
||||||
|
:endReadAdditionalConfig
|
||||||
|
|
||||||
|
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||||
|
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||||
|
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||||
|
|
||||||
|
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||||
|
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
|
||||||
|
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||||
|
)
|
||||||
|
|
||||||
|
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||||
|
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||||
|
if exist %WRAPPER_JAR% (
|
||||||
|
echo Found %WRAPPER_JAR%
|
||||||
|
) else (
|
||||||
|
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||||
|
echo Downloading from: %DOWNLOAD_URL%
|
||||||
|
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
|
||||||
|
echo Finished downloading %WRAPPER_JAR%
|
||||||
|
)
|
||||||
|
@REM End of extension
|
||||||
|
|
||||||
|
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||||
|
if ERRORLEVEL 1 goto error
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:error
|
||||||
|
set ERROR_CODE=1
|
||||||
|
|
||||||
|
:end
|
||||||
|
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||||
|
|
||||||
|
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||||
|
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||||
|
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||||
|
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||||
|
:skipRcPost
|
||||||
|
|
||||||
|
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||||
|
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||||
|
|
||||||
|
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||||
|
|
||||||
|
exit /B %ERROR_CODE%
|
126
code/pom.xml
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.2.0.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>be.unamur</groupId>
|
||||||
|
<artifactId>ct</artifactId>
|
||||||
|
<version>1.0.0-PROJECT</version>
|
||||||
|
<name>ct</name>
|
||||||
|
<description>Demo project for Spring Boot</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<version>1.4.194</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<version>28.2-jre</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.tomakehurst</groupId>
|
||||||
|
<artifactId>wiremock</artifactId>
|
||||||
|
<version>2.6.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>3.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
<version>1.64</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.11.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.javatuples</groupId>
|
||||||
|
<artifactId>javatuples</artifactId>
|
||||||
|
<version>1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains</groupId>
|
||||||
|
<artifactId>annotations</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
13
code/src/main/java/be/unamur/ct/CtApplication.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class CtApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(CtApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
code/src/main/java/be/unamur/ct/data/dao/CertificateDao.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package be.unamur.ct.data.dao;
|
||||||
|
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CertificateDao extends JpaRepository<Certificate, Integer> {
|
||||||
|
List<Certificate> findAllByOrderByIdAsc(Pageable pageable);
|
||||||
|
|
||||||
|
List<Certificate> findAllByVATNotNullOrderByIdAsc(Pageable pageable);
|
||||||
|
|
||||||
|
List<Certificate> findByVatSearched(boolean value);
|
||||||
|
|
||||||
|
@Query(value = "select issuer, count(*) as num from certificate group by issuer", nativeQuery = true)
|
||||||
|
List<Object[]> distinctIssuer();
|
||||||
|
|
||||||
|
@Query(value = "select signature_alg, count(*) as num from certificate group by signature_alg", nativeQuery = true)
|
||||||
|
List<Object[]> distinctAlgorithm();
|
||||||
|
|
||||||
|
Integer countByVATIsNotNullAndVatSearched(boolean vatSearched);
|
||||||
|
|
||||||
|
Integer countByVATIsNullAndVatSearched(boolean vatSearched);
|
||||||
|
|
||||||
|
long count();
|
||||||
|
}
|
14
code/src/main/java/be/unamur/ct/data/dao/ServerDao.java
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package be.unamur.ct.data.dao;
|
||||||
|
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ServerDao extends JpaRepository<Server, Integer> {
|
||||||
|
|
||||||
|
Server findById(long id);
|
||||||
|
|
||||||
|
boolean existsByUrl(String url);
|
||||||
|
|
||||||
|
}
|
21
code/src/main/java/be/unamur/ct/data/dao/SliceDao.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package be.unamur.ct.data.dao;
|
||||||
|
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import be.unamur.ct.download.model.Slice;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface SliceDao extends JpaRepository<Slice, Integer> {
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
long deleteById(long id);
|
||||||
|
|
||||||
|
boolean existsById(long id);
|
||||||
|
|
||||||
|
List<Slice> findByServerOrderByEndSliceDesc(Server server);
|
||||||
|
|
||||||
|
List<Slice> findByServerOrderByStartSlice(Server server);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
package be.unamur.ct.data.service;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.CertificateDao;
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class providing methods to help certificate management in the application
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CertificateService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateDao certificateDao;
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(CertificateService.class);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of certificate to be displayed. Certificates returned in the page are fetched from the database.
|
||||||
|
* Only certificates having a non-null value for the VAT field may be selected when using vatOnly = true
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param page Index of the page to fetch
|
||||||
|
* @param size Size of the page to fetch
|
||||||
|
* @param vatOnly If set, returns only certificate having a VAT number
|
||||||
|
* @return A list of certificates, as specified by pageable and vatOnly
|
||||||
|
*/
|
||||||
|
public List<Certificate> findPaginatedCertificates(int page, int size, boolean vatOnly) {
|
||||||
|
|
||||||
|
List<Certificate> list;
|
||||||
|
|
||||||
|
if (certificateDao.count() < page*size) {
|
||||||
|
list = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
Pageable pageable = PageRequest.of(page, size, Sort.by("id").ascending());
|
||||||
|
list = vatOnly ? certificateDao.findAllByVATNotNullOrderByIdAsc(pageable) :
|
||||||
|
certificateDao.findAllByOrderByIdAsc(pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ArrayList of the amount of certification with a VAT number, without a VAT number but already scrapped
|
||||||
|
* and not yet scrapped
|
||||||
|
* This ArrayList is used to display a graph on the web page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return An array of the number of certificates in the categories explained before
|
||||||
|
*/
|
||||||
|
public ArrayList<Integer> vatGraphData() {
|
||||||
|
// Create graph data for the VAT numbers
|
||||||
|
ArrayList<Integer> vatCount = new ArrayList<>();
|
||||||
|
|
||||||
|
Integer exists = certificateDao.countByVATIsNotNullAndVatSearched(true);
|
||||||
|
Integer notFound = certificateDao.countByVATIsNullAndVatSearched(true);
|
||||||
|
Integer notSearched = certificateDao.countByVATIsNullAndVatSearched(false);
|
||||||
|
|
||||||
|
if (!(exists == 0 && notFound == 0 && notSearched == 0)) {
|
||||||
|
vatCount.add(exists);
|
||||||
|
vatCount.add(notFound);
|
||||||
|
vatCount.add(notSearched);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pair of arrays, counting the number of certificates for each issuer
|
||||||
|
* The first array holds the number of occurrences of each issuer
|
||||||
|
* The second array holds the names of the issuers
|
||||||
|
* The arrays are cropped to six elements maximum. If the arrays should have been longer, the exceeding elements
|
||||||
|
* are grouped in an "Other" category at the end of the array
|
||||||
|
* This Pair of ArrayList is used to display a graph on the web page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return A pair of arrays, counting the number of certificates for each issuer
|
||||||
|
*/
|
||||||
|
public Pair<ArrayList<BigInteger>, ArrayList<String>> issuerGraphData() {
|
||||||
|
|
||||||
|
// Create graph data for issuer
|
||||||
|
List<Object[]> result = certificateDao.distinctIssuer();
|
||||||
|
|
||||||
|
return createPairForGraph(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pair of arrays, counting the number of certificates for each issuer
|
||||||
|
* The first array holds the number of occurrences of each algorithm
|
||||||
|
* The second array holds the names of the algorithms
|
||||||
|
* The arrays are cropped to six elements maximum. If the arrays should have been longer, the exceeding elements
|
||||||
|
* are grouped in an "Other" category at the end of the array
|
||||||
|
* This Pair of ArrayList is used to display a graph on the web page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return A pair of arrays, counting the number of certificates for each algorithm
|
||||||
|
*/
|
||||||
|
public Pair<ArrayList<BigInteger>, ArrayList<String>> algorithmGraphData() {
|
||||||
|
|
||||||
|
// Create graph data for issuer
|
||||||
|
List<Object[]> result = certificateDao.distinctAlgorithm();
|
||||||
|
|
||||||
|
return createPairForGraph(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give an list of Arrays of objects (each array of objects is supposed to contain a String and a BigInteger),
|
||||||
|
* it returns a Pair of Arrays.
|
||||||
|
* The first Array contains the BigIntegers and the second Array contains the Strings
|
||||||
|
* Elements in the same array of object in the parameter will be at the same index in the Arrays outputted
|
||||||
|
* The Array of BigInteger is sorted descending
|
||||||
|
* The outputted arrays are six element long maximum. If the list of Object[] is longer than six elements
|
||||||
|
* the Object[] which have the lower value for the BigInteger are aggregated under the category "Other", a the end
|
||||||
|
* of the array.
|
||||||
|
*
|
||||||
|
* Example
|
||||||
|
* -------
|
||||||
|
*
|
||||||
|
* result = { ["A", 10], ["B", 3], ["C", 4], ["D", 9], ["E", 2], ["F", 9], ["G", 1] }
|
||||||
|
* Will return Pair({10, 9, 9, 4, 3, 3}, {"A", "D", "F", "C", "B", "Others"})
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param result The List of Object[] to process
|
||||||
|
* @return Pair of Arrays (BigInteger and String)
|
||||||
|
*/
|
||||||
|
public Pair<ArrayList<BigInteger>, ArrayList<String>> createPairForGraph(List<Object[]> result) {
|
||||||
|
ArrayList<Pair<String, BigInteger>> count = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Object[] o : result) {
|
||||||
|
count.add(new Pair<>((String) o[0], (BigInteger) o[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(count, (o1, o2) -> o2.getValue1().compareTo(o1.getValue1()));
|
||||||
|
|
||||||
|
List<Pair<String, BigInteger>> data;
|
||||||
|
|
||||||
|
if (count.size() > 6) {
|
||||||
|
data = count.subList(0, 5);
|
||||||
|
|
||||||
|
BigInteger others = BigInteger.ZERO;
|
||||||
|
|
||||||
|
for (Pair<String, BigInteger> p : count.subList(5, count.size())) {
|
||||||
|
others = others.add(p.getValue1());
|
||||||
|
}
|
||||||
|
|
||||||
|
data.add(new Pair<>("Others", others));
|
||||||
|
} else {
|
||||||
|
data = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<BigInteger> num = new ArrayList<>();
|
||||||
|
ArrayList<String> labels = new ArrayList<>();
|
||||||
|
for (Pair<String, BigInteger> p : data) {
|
||||||
|
labels.add(p.getValue0());
|
||||||
|
num.add(p.getValue1());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Pair<>(num, labels);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package be.unamur.ct.decode.exceptions;
|
||||||
|
|
||||||
|
public class NotAValidDomainException extends RuntimeException {
|
||||||
|
public NotAValidDomainException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
129
code/src/main/java/be/unamur/ct/decode/model/Certificate.java
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package be.unamur.ct.decode.model;
|
||||||
|
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity class used to represent certificates in the application.
|
||||||
|
* This class is used by JPA to create the corresponding SQL table in the database.
|
||||||
|
* The class contains variables needed to represent a certificate and basic getters, setters and toString methods
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class Certificate {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Length(min = 3)
|
||||||
|
private String subject;
|
||||||
|
|
||||||
|
private String issuer;
|
||||||
|
private Date notAfter;
|
||||||
|
private Date notBefore;
|
||||||
|
|
||||||
|
@Column(name = "signature_alg")
|
||||||
|
private String signatureAlg;
|
||||||
|
private int versionNumber;
|
||||||
|
private String VAT;
|
||||||
|
private boolean vatSearched = false;
|
||||||
|
|
||||||
|
public Certificate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Certificate(@Length(min = 3) String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIssuer() {
|
||||||
|
return issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIssuer(String issuer) {
|
||||||
|
this.issuer = issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getNotAfter() {
|
||||||
|
return notAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotAfter(Date notAfter) {
|
||||||
|
this.notAfter = notAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(Date notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignatureAlg() {
|
||||||
|
return signatureAlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureAlg(String signatureAlg) {
|
||||||
|
this.signatureAlg = signatureAlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersionNumber() {
|
||||||
|
return versionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersionNumber(int versionNumber) {
|
||||||
|
this.versionNumber = versionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVAT() {
|
||||||
|
return VAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVAT(String VAT) {
|
||||||
|
this.VAT = VAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVatSearched() {
|
||||||
|
return vatSearched;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVatSearched(boolean vatSearched) {
|
||||||
|
this.vatSearched = vatSearched;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Certificate{" +
|
||||||
|
"id=" + id +
|
||||||
|
", subject='" + subject + '\'' +
|
||||||
|
", issuer='" + issuer + '\'' +
|
||||||
|
", notAfter=" + notAfter +
|
||||||
|
", notBefore=" + notBefore +
|
||||||
|
", signatureAlg='" + signatureAlg + '\'' +
|
||||||
|
", versionNumber=" + versionNumber +
|
||||||
|
", VAT='" + VAT + '\'' +
|
||||||
|
", vatSearched=" + vatSearched +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
package be.unamur.ct.decode.service;
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.CertificateDao;
|
||||||
|
import be.unamur.ct.decode.exceptions.NotAValidDomainException;
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import be.unamur.ct.download.model.LogEntry;
|
||||||
|
import be.unamur.ct.scrap.service.VATScrapper;
|
||||||
|
import be.unamur.ct.scrap.thread.VATScrapperThread;
|
||||||
|
import be.unamur.ct.thread.ThreadPool;
|
||||||
|
import org.bouncycastle.asn1.x500.RDN;
|
||||||
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
|
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||||
|
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
|
||||||
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class providing methods to decode certificates downloaded from
|
||||||
|
* a Certificate Transparency log server in Base64 format
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class DecodeService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateDao certificateDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VATScrapper vatScrapper;
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(DecodeService.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
|
||||||
|
|
||||||
|
public DecodeService() {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a single log entry (a single certificate) from its Base64 form to
|
||||||
|
* a certificate as described in the Certificate class.
|
||||||
|
* Once decoded, the certificate is saved in the database.
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param entry LogEntry object representing the Base64 downloaded certificate
|
||||||
|
* @see Certificate
|
||||||
|
*/
|
||||||
|
public void decodeToCert(LogEntry entry) {
|
||||||
|
// Get an object from the previously saved list and extract certificate
|
||||||
|
String leaf = entry.getLeaf();
|
||||||
|
String extra = entry.getData();
|
||||||
|
byte[] leafBin = Base64.decode(leaf);
|
||||||
|
|
||||||
|
// Get certificate type (X.509 or PreCert)
|
||||||
|
int id = (leafBin[11] & 0xFF) | ((leafBin[10] & 0xFF) << 8);
|
||||||
|
int l = (leafBin[14] & 0xFF) | ((leafBin[13] & 0xFF) << 8) | ((leafBin[12] & 0x0F) << 16);
|
||||||
|
|
||||||
|
if (id == 0) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Extract only interesting part from certificate and create a X509CertificateHolder Object from it
|
||||||
|
byte[] certBin = Arrays.copyOfRange(leafBin, 15, l + 15);
|
||||||
|
X509CertificateHolder certX = new X509CertificateHolder(certBin);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String cns;
|
||||||
|
RDN cn;
|
||||||
|
|
||||||
|
// Get Subject
|
||||||
|
try {
|
||||||
|
cn = certX.getSubject().getRDNs(BCStyle.CN)[0];
|
||||||
|
cns = IETFUtils.valueToString(cn.getFirst().getValue());
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
//logger.warn("Cannot get domain name");
|
||||||
|
throw new NotAValidDomainException("createDomainFromCert(X509CertificateHolder) in DomainService: "
|
||||||
|
+ "Cannot get domain name");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check TLD
|
||||||
|
if (cns == null || !(cns.endsWith(".be") || cns.endsWith(".vlaanderen") || cns.endsWith(".brussels"))) {
|
||||||
|
throw new NotAValidDomainException("createDomainFromCert(X509CertificateHolder) in DomainService: "
|
||||||
|
+ cn + " is not a valid domain");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create certificate
|
||||||
|
Certificate certificate = new Certificate(cns);
|
||||||
|
|
||||||
|
// Get root CA
|
||||||
|
String issuer = searchRoot(extra);
|
||||||
|
certificate.setIssuer(issuer);
|
||||||
|
|
||||||
|
certificate = setAttributes(certificate, certX);
|
||||||
|
certificate = certificateDao.save(certificate);
|
||||||
|
|
||||||
|
// NEXT STEP - Scrap for VAT
|
||||||
|
threadPool.getVATScrapperExecutor().execute(new VATScrapperThread(certificate, vatScrapper));
|
||||||
|
} catch (NotAValidDomainException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Cannot get certificate from binary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a certificate filled with the validity period, the signature algorithm and the version number.
|
||||||
|
* The details are extracted from a X509CertificateHolder object to be set in a Certificate object.
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param certificate Certificate object to be filled with the details
|
||||||
|
* @param cert X509CertificateHolder object representing the certificate to extract data from
|
||||||
|
* @return The certificate filled with details
|
||||||
|
*/
|
||||||
|
public Certificate setAttributes(Certificate certificate, X509CertificateHolder cert) {
|
||||||
|
|
||||||
|
// Get validity period
|
||||||
|
Date notBefore = cert.getNotBefore();
|
||||||
|
certificate.setNotBefore(notBefore);
|
||||||
|
Date notAfter = cert.getNotAfter();
|
||||||
|
certificate.setNotAfter(notAfter);
|
||||||
|
|
||||||
|
// Get Signature Algorithm
|
||||||
|
AlgorithmIdentifier algoId = cert.getSignatureAlgorithm();
|
||||||
|
DefaultAlgorithmNameFinder nameFinder = new DefaultAlgorithmNameFinder();
|
||||||
|
String algoName = nameFinder.getAlgorithmName(algoId);
|
||||||
|
certificate.setSignatureAlg(algoName);
|
||||||
|
|
||||||
|
// Get Version Number
|
||||||
|
int versionNumber = cert.getVersionNumber();
|
||||||
|
certificate.setVersionNumber(versionNumber);
|
||||||
|
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the root Certificate Authority (CA) from the downloaded certificate in its Base64 representation.
|
||||||
|
* The Base64 data downloaded from the logs contains a chain of trust from the entity certified
|
||||||
|
* by the certificate up to a CA accepted by the log.
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param extra_data extra_data field from the downloaded certificate, in Base64 representation
|
||||||
|
* @return String containing the name of the CA
|
||||||
|
* @throws NotAValidDomainException if no CA can be found while parsing the data
|
||||||
|
*/
|
||||||
|
public String searchRoot(String extra_data) throws NotAValidDomainException {
|
||||||
|
/*
|
||||||
|
* Browse extra_data from the end to the beginning looking for a certificate. The first certificate found is
|
||||||
|
* the certificate of the CA as it is the last one of the structure that is browsed from the end to the start.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* find a better way to determine the RootCA, the approach described above is probably not the best
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte[] extraBin = Base64.decode(extra_data);
|
||||||
|
int start = extraBin.length - 5;
|
||||||
|
|
||||||
|
while (start >= 0) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get certificate type (X.509 or PreCert)
|
||||||
|
int id = (extraBin[start + 1] & 0xFF) | ((extraBin[start] & 0xFF) << 8);
|
||||||
|
int l = (extraBin[start + 4] & 0xFF) | ((extraBin[start + 3] & 0xFF) << 8) | ((extraBin[start + 2] & 0x0F) << 16);
|
||||||
|
|
||||||
|
byte[] certBin = Arrays.copyOfRange(extraBin, start + 5, l + start + 5);
|
||||||
|
|
||||||
|
try {
|
||||||
|
X509CertificateHolder certX = new X509CertificateHolder(certBin);
|
||||||
|
RDN cn = certX.getSubject().getRDNs(BCStyle.CN)[0];
|
||||||
|
String cns = IETFUtils.valueToString(cn.getFirst().getValue());
|
||||||
|
return cns;
|
||||||
|
} catch (IOException e) {
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn(e.toString());
|
||||||
|
}
|
||||||
|
start--;
|
||||||
|
}
|
||||||
|
throw new NotAValidDomainException("createDomainFromCert(X509CertificateHolder) in DomainService: " +
|
||||||
|
"no CA found");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package be.unamur.ct.decode.thread;
|
||||||
|
|
||||||
|
import be.unamur.ct.decode.service.DecodeService;
|
||||||
|
import be.unamur.ct.download.model.LogEntry;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread class to launch the decode process for a downloaded certificate
|
||||||
|
*/
|
||||||
|
public class DecodeEntryThread extends Thread {
|
||||||
|
|
||||||
|
private DecodeService decodeService;
|
||||||
|
private LogEntry entry;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param entry Downloaded certificate in Base64 representation
|
||||||
|
* @param decodeService Reference of the DecodeService to use
|
||||||
|
* @see LogEntry
|
||||||
|
*/
|
||||||
|
public DecodeEntryThread(LogEntry entry, DecodeService decodeService) {
|
||||||
|
this.entry = entry;
|
||||||
|
this.decodeService = decodeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the decode process for the entry saved in the variables of the instance
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
decodeService.decodeToCert(entry);
|
||||||
|
}
|
||||||
|
}
|
62
code/src/main/java/be/unamur/ct/download/model/LogEntry.java
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package be.unamur.ct.download.model;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to represent the Base64 data downloaded from Certificate Transparency log servers.
|
||||||
|
* This class is mainly used to represent a log entry between the downloading part and the decoding part.
|
||||||
|
* The class contains variables needed to represent a log entry and basic getters, setters and toString methods.
|
||||||
|
*/
|
||||||
|
public class LogEntry {
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@JsonProperty(value = "leaf_input")
|
||||||
|
private String leaf;
|
||||||
|
|
||||||
|
@JsonProperty(value = "extra_data")
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
public LogEntry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEntry(String leaf, String data) {
|
||||||
|
this.leaf = leaf;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLeaf() {
|
||||||
|
return leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeaf(String leaf) {
|
||||||
|
this.leaf = leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(String data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LogEntry{" +
|
||||||
|
"id=" + id +
|
||||||
|
", leaf='" + leaf + '\'' +
|
||||||
|
", data='" + data + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
56
code/src/main/java/be/unamur/ct/download/model/LogList.java
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package be.unamur.ct.download.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to represent a list of log entries.
|
||||||
|
* The class contains variables needed to represent a certificate and basic getters, setters and toString methods.
|
||||||
|
* The class also implements an iterator.
|
||||||
|
*/
|
||||||
|
public class LogList implements Iterable<LogEntry> {
|
||||||
|
|
||||||
|
@JsonProperty(value = "entries")
|
||||||
|
private List<LogEntry> entries;
|
||||||
|
|
||||||
|
public LogList() {
|
||||||
|
this.entries = new ArrayList<LogEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogList(List<LogEntry> entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntry(LogEntry newEntry) {
|
||||||
|
entries.add(newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEntry getFirst() {
|
||||||
|
return entries.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEntry get(int n) {
|
||||||
|
return entries.get(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LogList{" +
|
||||||
|
"entries=" + entries +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<LogEntry> iterator() {
|
||||||
|
return entries.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
81
code/src/main/java/be/unamur/ct/download/model/Server.java
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package be.unamur.ct.download.model;
|
||||||
|
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity class used to represent a Certificate Transparency log server in the application.
|
||||||
|
* This class is used by JPA to create the corresponding SQL table in the database.
|
||||||
|
* The class contains variables needed to represent a log server and basic getters, setters and toString methods.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class Server {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@Column(unique = true)
|
||||||
|
@Length(min = 10)
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
public Server() {}
|
||||||
|
|
||||||
|
public Server(@Length(min = 10) String url) {
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
this.url = url;
|
||||||
|
} else {
|
||||||
|
this.url = url + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nickname = this.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server(@Length(min = 10) String url, String nickname) {
|
||||||
|
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
this.url = url;
|
||||||
|
} else {
|
||||||
|
this.url = url + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nickname = nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNickname() {
|
||||||
|
return nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNickname(String nickname) {
|
||||||
|
this.nickname = nickname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Server{" +
|
||||||
|
"id=" + id +
|
||||||
|
", url='" + url + '\'' +
|
||||||
|
", nickname='" + nickname + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
100
code/src/main/java/be/unamur/ct/download/model/Slice.java
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package be.unamur.ct.download.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity class used to represent a slice of a log server in the application.
|
||||||
|
* A slice of a server is a range of certificates on the server.
|
||||||
|
* Slices are used to split the downloading process among several threads.
|
||||||
|
* This class is used by JPA to create the corresponding SQL table in the database.
|
||||||
|
* The class contains variables needed to represent a slice and basic getters, setters and toString methods
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class Slice {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private long startSlice;
|
||||||
|
private long endSlice;
|
||||||
|
private long next;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@ManyToOne(fetch = FetchType.EAGER, optional = false)
|
||||||
|
@JoinColumn(name = "server_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Server server;
|
||||||
|
|
||||||
|
public Slice() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slice(long start, long end, long next) {
|
||||||
|
this.startSlice = start;
|
||||||
|
this.endSlice = end;
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slice(long start, long end, long next, Server server) {
|
||||||
|
this.startSlice = start;
|
||||||
|
this.endSlice = end;
|
||||||
|
this.next = next;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStartSlice() {
|
||||||
|
return startSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartSlice(long startSlice) {
|
||||||
|
this.startSlice = startSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEndSlice() {
|
||||||
|
return endSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndSlice(long endSlice) {
|
||||||
|
this.endSlice = endSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getNext() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNext(long next) {
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServer(Server server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Slice{" +
|
||||||
|
"id=" + id +
|
||||||
|
", startSlice=" + startSlice +
|
||||||
|
", endSlice=" + endSlice +
|
||||||
|
", next=" + next +
|
||||||
|
", serverId=" + server.getId() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
package be.unamur.ct.download.service;
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.ServerDao;
|
||||||
|
import be.unamur.ct.data.dao.SliceDao;
|
||||||
|
import be.unamur.ct.decode.service.DecodeService;
|
||||||
|
import be.unamur.ct.decode.thread.DecodeEntryThread;
|
||||||
|
import be.unamur.ct.download.model.LogEntry;
|
||||||
|
import be.unamur.ct.download.model.LogList;
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import be.unamur.ct.download.model.Slice;
|
||||||
|
import be.unamur.ct.download.thread.SearchSliceThread;
|
||||||
|
import be.unamur.ct.thread.ThreadPool;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class providing method to handle Certificate Transparency log servers in the application
|
||||||
|
* Log server are where the logs are downloaded from to get certificates
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ServerService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerDao serverDao;
|
||||||
|
@Autowired
|
||||||
|
private SliceDao sliceDao;
|
||||||
|
@Autowired
|
||||||
|
private DecodeService decodeService;
|
||||||
|
@Autowired
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
|
||||||
|
private ExecutorService decoder = threadPool.getDecodeExecutor();
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerService.class);
|
||||||
|
|
||||||
|
|
||||||
|
public ServerService() {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the slices for the specified server and start downloading logs using the created slices.
|
||||||
|
* The slices are handled by different thread in order to reduce waiting time due to blocking calls in the process
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param server Server to download logs from
|
||||||
|
*/
|
||||||
|
public void startSearch(Server server) {
|
||||||
|
logger.info("Updating slices for " + server.getNickname());
|
||||||
|
updateSlices(server);
|
||||||
|
|
||||||
|
logger.info("Adding slices to queue");
|
||||||
|
Iterable<Slice> slices = sliceDao.findByServerOrderByStartSlice(server);
|
||||||
|
for (Slice s : slices) {
|
||||||
|
SearchSliceThread search = new SearchSliceThread(s, this);
|
||||||
|
threadPool.getSliceExecutor().execute(search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads logs from the specified slice.
|
||||||
|
* Once downloaded each log is sent in a new to thread to be decoded and saved in the database.
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param slice Slice to download logs from
|
||||||
|
* @see Slice
|
||||||
|
*/
|
||||||
|
public void searchSlice(Slice slice) {
|
||||||
|
Server server = slice.getServer();
|
||||||
|
long start = slice.getNext();
|
||||||
|
long size = slice.getEndSlice();
|
||||||
|
long step = 1000;
|
||||||
|
boolean interrupted = false;
|
||||||
|
logger.info("Starting slice " + slice.toString());
|
||||||
|
|
||||||
|
while (start <= size && !Thread.currentThread().isInterrupted()) {
|
||||||
|
|
||||||
|
long end = Math.min((start + step - 1), size);
|
||||||
|
|
||||||
|
// Create REST request
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(server.getUrl() + "ct/v1/get-entries?start=" + start + "&end=" + end)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
LogList log = null;
|
||||||
|
try {
|
||||||
|
// Execute request
|
||||||
|
Call call = client.newCall(request);
|
||||||
|
Response response = call.execute();
|
||||||
|
|
||||||
|
// Save received JSON in an Object using ObjectMapper
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
log = objectMapper.readValue(response.body().string(), LogList.class);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
logger.warn("Timeout while requesting data from server");
|
||||||
|
} catch (InterruptedIOException e) {
|
||||||
|
interrupted = true;
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
logger.warn("Thread interrupted");
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error while requesting logs to server");
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEXT STEP - Send logs to be decoded
|
||||||
|
if (log != null) {
|
||||||
|
for (LogEntry entry : log) {
|
||||||
|
decoder.execute(new DecodeEntryThread(entry, decodeService));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!interrupted) {
|
||||||
|
slice.setNext(end + 1);
|
||||||
|
sliceDao.save(slice);
|
||||||
|
serverDao.save(server);
|
||||||
|
start += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or updates slices for the given server. Once created, slices are stored in the database
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param server Server to create slices for
|
||||||
|
* @see Slice
|
||||||
|
*/
|
||||||
|
public void updateSlices(Server server) {
|
||||||
|
/*
|
||||||
|
* If no slices exists, create all slices
|
||||||
|
* If slices exists, delete used slices except last and create new slices if needed
|
||||||
|
*/
|
||||||
|
long start;
|
||||||
|
long end;
|
||||||
|
long step = 1000000;
|
||||||
|
|
||||||
|
// Define where to start to create slices
|
||||||
|
List<Slice> slices = sliceDao.findByServerOrderByEndSliceDesc(server);
|
||||||
|
Slice last = null;
|
||||||
|
if (slices.isEmpty()) {
|
||||||
|
start = 0;
|
||||||
|
logger.info("No slices yet");
|
||||||
|
} else {
|
||||||
|
last = slices.get(0);
|
||||||
|
start = last.getEndSlice() + 1;
|
||||||
|
logger.info("Already slices to " + start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all completely used slices
|
||||||
|
int count = 0;
|
||||||
|
for (Slice s : slices) {
|
||||||
|
if (s.getNext() > s.getEndSlice()) {
|
||||||
|
sliceDao.deleteById(s.getId());
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info(count + " slices deleted");
|
||||||
|
|
||||||
|
// Re-insert last Slice to know where we previously stopped
|
||||||
|
if (last != null && !sliceDao.existsById(last.getId())) {
|
||||||
|
sliceDao.save(last);
|
||||||
|
logger.info("Re-saving last slice");
|
||||||
|
}
|
||||||
|
|
||||||
|
long serverSize = checkSize(server);
|
||||||
|
|
||||||
|
while (start < serverSize) {
|
||||||
|
end = Math.min(start + step - 1, serverSize - 1);
|
||||||
|
sliceDao.save(new Slice(start, end, start, server));
|
||||||
|
start += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size (the number of logs it contains) of a Certificate Transparency log server
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param server Server to check size for
|
||||||
|
* @return Number of logs present in the server at the time of the execution
|
||||||
|
*/
|
||||||
|
public long checkSize(Server server) {
|
||||||
|
|
||||||
|
// Create REST request
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(server.getUrl() + "ct/v1/get-sth")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Execute request
|
||||||
|
Call call = client.newCall(request);
|
||||||
|
try {
|
||||||
|
Response response = call.execute();
|
||||||
|
String json = response.body().string();
|
||||||
|
|
||||||
|
// Save received JSON in an Object using ObjectMapper
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
JsonNode jsonNode = objectMapper.readTree(json);
|
||||||
|
|
||||||
|
return jsonNode.get("tree_size").asLong();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package be.unamur.ct.download.thread;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import be.unamur.ct.download.service.ServerService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread class to start the downloading process for a give server
|
||||||
|
*/
|
||||||
|
public class ScanLogThread extends Thread {
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private ServerService serverService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param server Server to download logs from
|
||||||
|
* @param serverService Reference to the ServerService to use
|
||||||
|
* @see Server
|
||||||
|
*/
|
||||||
|
public ScanLogThread(Server server, ServerService serverService) {
|
||||||
|
super("Scan - " + server.getNickname());
|
||||||
|
this.server = server;
|
||||||
|
this.serverService = serverService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the downloading process for the server saved in the variables of the instance
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
serverService.startSearch(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package be.unamur.ct.download.thread;
|
||||||
|
|
||||||
|
import be.unamur.ct.download.model.Slice;
|
||||||
|
import be.unamur.ct.download.service.ServerService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread class to download logs from a slice of a server
|
||||||
|
*/
|
||||||
|
public class SearchSliceThread extends Thread {
|
||||||
|
|
||||||
|
private Slice slice;
|
||||||
|
private ServerService serverService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param slice Slice to download logs from
|
||||||
|
* @param serverService Reference of the ServerService to use
|
||||||
|
* @see Slice
|
||||||
|
*/
|
||||||
|
public SearchSliceThread(Slice slice, ServerService serverService) {
|
||||||
|
super("Process - Slice #" + slice.getId());
|
||||||
|
this.slice = slice;
|
||||||
|
this.serverService = serverService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the downloading process for the slice saved in the variables of the instance
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
serverService.searchSlice(slice);
|
||||||
|
}
|
||||||
|
}
|
268
code/src/main/java/be/unamur/ct/scrap/service/VATScrapper.java
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
package be.unamur.ct.scrap.service;
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.CertificateDao;
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import be.unamur.ct.scrap.thread.VATScrapperThread;
|
||||||
|
import be.unamur.ct.thread.ThreadPool;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service class providing methods to scrap a website searching for a VAT number
|
||||||
|
* VAT number are stored in the database with the certificate they are related to
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class VATScrapper {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateDao certificateDao;
|
||||||
|
@Autowired
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(VATScrapper.class);
|
||||||
|
private int timeout = 5000;
|
||||||
|
private int depth = 5;
|
||||||
|
private String patternVAT = "(?i)((BE)?0([. -])?[0-9]{3}([. -])?[0-9]{3}([. -])?[0-9]{3})";
|
||||||
|
|
||||||
|
|
||||||
|
public VATScrapper() {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scraps a website based on the certificate searching for a VAT number.
|
||||||
|
* If a VAT number is found, the certificate is updated in the database.
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param certificate Certificate of the website to scrap
|
||||||
|
*/
|
||||||
|
public void scrap(Certificate certificate) {
|
||||||
|
String subject = certificate.getSubject();
|
||||||
|
String preUrl = "http://" + (subject.startsWith("*.") ? "www." + subject.substring(2) : subject);
|
||||||
|
|
||||||
|
URL url;
|
||||||
|
try {
|
||||||
|
url = new URL(preUrl);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
logger.warn("Cannot make an URL from " + subject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Scrapping from base URL: " + url.toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
String VAT = searchPage(url, depth, new HashSet<URL>());
|
||||||
|
if (VAT != null) {
|
||||||
|
certificate.setVAT(VAT);
|
||||||
|
logger.info("Cert of " + certificate.getSubject() + " saved with VAT " + VAT);
|
||||||
|
} else {
|
||||||
|
logger.info("VAT not found for " + certificate.getSubject());
|
||||||
|
}
|
||||||
|
certificate.setVatSearched(true);
|
||||||
|
certificateDao.save(certificate);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the page at the give url for a VAT number.
|
||||||
|
* If no VAT number is found, recursively calls itself for each link of the same domain on the page.
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param url URL to search
|
||||||
|
* @param depth Maximum depth allowed for future searches. If it reaches 0, search is aborted.
|
||||||
|
* @param visited HashSet of URL already visited in previous calls for this website
|
||||||
|
* @return Nullable String containing the normalized VAT if found on the page or in one of the recursive calls
|
||||||
|
* @throws InterruptedException if the thread is interrupted
|
||||||
|
*/
|
||||||
|
public String searchPage(URL url, int depth, HashSet<URL> visited) throws InterruptedException {
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
* Find a better way to stop the thread
|
||||||
|
* The Thread.interrupt() method doesn't seem to work
|
||||||
|
*/
|
||||||
|
if (threadPool.getVATScrapperExecutor().isShutdown()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it is needed to visit URL
|
||||||
|
if (depth < 0 || visited.contains(url)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
visited.add(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve web page
|
||||||
|
Document document;
|
||||||
|
|
||||||
|
try {
|
||||||
|
document = Jsoup.parse(url, timeout);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for VAT number
|
||||||
|
String text;
|
||||||
|
|
||||||
|
try {
|
||||||
|
text = document.body().text();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Pattern r = Pattern.compile(patternVAT);
|
||||||
|
Matcher m = r.matcher(text);
|
||||||
|
|
||||||
|
if (m.find()) {
|
||||||
|
String VAT = m.group(1);
|
||||||
|
VAT = normalizeVAT(VAT);
|
||||||
|
|
||||||
|
if (isValidVAT(VAT)) {
|
||||||
|
return VAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not VAT number on page, search links on page and follow them
|
||||||
|
// Create a list of links to follow. Links in footer appear first
|
||||||
|
ArrayList<URL> linksOnPage = new ArrayList<URL>();
|
||||||
|
|
||||||
|
// Search links in footer
|
||||||
|
Elements footer = document.getElementsByTag("footer");
|
||||||
|
for (Element f : footer) {
|
||||||
|
Elements links = f.getElementsByTag("a");
|
||||||
|
|
||||||
|
for (Element l : links) {
|
||||||
|
try {
|
||||||
|
linksOnPage.add(new URL(l.absUrl("href")));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search links in the whole page
|
||||||
|
Elements links = document.body().getElementsByTag("a");
|
||||||
|
for (Element l : links) {
|
||||||
|
try {
|
||||||
|
linksOnPage.add(new URL(l.absUrl("href")));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If link points to same sub-domain, follow it
|
||||||
|
String hostBase = new String();
|
||||||
|
String hostLink = new String();
|
||||||
|
|
||||||
|
try {
|
||||||
|
String host = url.toURI().getHost();
|
||||||
|
hostBase = host.startsWith("www.") ? host.substring(4) : host;
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (URL link : linksOnPage) {
|
||||||
|
boolean sameDomain = false;
|
||||||
|
if (link != null) {
|
||||||
|
try {
|
||||||
|
String host = link.toURI().getHost();
|
||||||
|
hostLink = host.startsWith("www.") ? host.substring(4) : host;
|
||||||
|
} catch (URISyntaxException | NullPointerException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
sameDomain = hostBase.equals(hostLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sameDomain) {
|
||||||
|
String VAT = searchPage(link, depth - 1, visited);
|
||||||
|
if (VAT != null) {
|
||||||
|
return VAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a raw VAT number found on a web page to fit the format BExxxxxxxxxx where x is a digit
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param VAT Raw VAT number found on a web page
|
||||||
|
* @return Nullable (if input is null) String with the normalized VAT number
|
||||||
|
*/
|
||||||
|
public String normalizeVAT(String VAT) {
|
||||||
|
|
||||||
|
if (VAT == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAT = VAT.toUpperCase();
|
||||||
|
|
||||||
|
VAT = VAT.replace(".", "")
|
||||||
|
.replace("-", "")
|
||||||
|
.replace(" ", "");
|
||||||
|
|
||||||
|
VAT = (VAT.startsWith("BE") ? VAT : "BE" + VAT);
|
||||||
|
|
||||||
|
return VAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a normalized VAT number is valid.
|
||||||
|
* A valid VAT number meet the following criteria:
|
||||||
|
* - Let BE0xxx xxx xyy a VAT number
|
||||||
|
* - The VAT number if valid if 97-(xxxxxxx mod 97) == yy
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param VAT Normalized VAT number to check
|
||||||
|
* @return true if the VAT is valid, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isValidVAT(String VAT) {
|
||||||
|
|
||||||
|
VAT = VAT.substring(2);
|
||||||
|
|
||||||
|
int head = Integer.parseInt(VAT.substring(0, VAT.length() - 2));
|
||||||
|
int tail = Integer.parseInt(VAT.substring(8));
|
||||||
|
|
||||||
|
|
||||||
|
return (97 - (head % 97)) == tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume VAT scrapping after a restart of the application.
|
||||||
|
* Find all certificate not yet searched for VAT number and
|
||||||
|
* creates a scrapping thread for each certificate matching the query
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
*/
|
||||||
|
public void resumeVatScrapping() {
|
||||||
|
|
||||||
|
List<Certificate> cert = certificateDao.findByVatSearched(false);
|
||||||
|
logger.info("Restarting " + cert.size() + " searches");
|
||||||
|
|
||||||
|
for (Certificate c : cert) {
|
||||||
|
threadPool.getVATScrapperExecutor().execute(new VATScrapperThread(c, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Restarted " + cert.size() + " searches");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package be.unamur.ct.scrap.thread;
|
||||||
|
|
||||||
|
import be.unamur.ct.scrap.service.VATScrapper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread class to resume VAT scrapping when the application is restarted
|
||||||
|
*/
|
||||||
|
public class ResumeVATScrapThread extends Thread {
|
||||||
|
|
||||||
|
private VATScrapper vatScrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param vatScrapper Reference to the VATScrapper to use
|
||||||
|
*/
|
||||||
|
public ResumeVATScrapThread(VATScrapper vatScrapper) {
|
||||||
|
this.vatScrapper = vatScrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the VATScrapping
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
vatScrapper.resumeVatScrapping();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package be.unamur.ct.scrap.thread;
|
||||||
|
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import be.unamur.ct.scrap.service.VATScrapper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread class to start the scrapping of a single website
|
||||||
|
*/
|
||||||
|
public class VATScrapperThread extends Thread {
|
||||||
|
private Certificate cert;
|
||||||
|
private VATScrapper vatScrapper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param cert Certificate of the website to start scrapping for
|
||||||
|
* @param vatScrapper Reference to the VATScrapper to use
|
||||||
|
* @see Certificate
|
||||||
|
*/
|
||||||
|
public VATScrapperThread(Certificate cert, VATScrapper vatScrapper) {
|
||||||
|
this.cert = cert;
|
||||||
|
this.vatScrapper = vatScrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the VAT scrapping for the certificate saved in the variables of the instance
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
vatScrapper.scrap(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
124
code/src/main/java/be/unamur/ct/thread/ThreadPool.java
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package be.unamur.ct.thread;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class handling multiples ExecutorServices used in the application.
|
||||||
|
* This class uses the Singleton design pattern to prevent multiples instances of the same ExecutorService to be created.
|
||||||
|
*
|
||||||
|
* @see ExecutorService
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ThreadPool {
|
||||||
|
static private ExecutorService serverExecutor = null;
|
||||||
|
static private ExecutorService sliceExecutor = null;
|
||||||
|
static private ExecutorService decodeExecutor = null;
|
||||||
|
static private ExecutorService VATScrapperExecutor = null;
|
||||||
|
|
||||||
|
static private Logger logger = LoggerFactory.getLogger(ThreadPool.class);
|
||||||
|
|
||||||
|
static private Integer threadsDecode;
|
||||||
|
static private Integer threadsSlice;
|
||||||
|
static private Integer threadsScrap;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because the @Value cannot be applied to a static variable, this method set the @Value to the static variable
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param value Injected value to set
|
||||||
|
*/
|
||||||
|
@Value("${threads-decode}")
|
||||||
|
public void setThreadsDecode(Integer value) {
|
||||||
|
this.threadsDecode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because the @Value cannot be applied to a static variable, this method set the @Value to the static variable
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param value Injected value to set
|
||||||
|
*/
|
||||||
|
@Value("${threads-slice}")
|
||||||
|
public void setThreadsSlice(Integer value) {
|
||||||
|
this.threadsSlice = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because the @Value cannot be applied to a static variable, this method set the @Value to the static variable
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param value Injected value to set
|
||||||
|
*/
|
||||||
|
@Value("${threads-scrap}")
|
||||||
|
public void setThreadsScrap(Integer value) {
|
||||||
|
this.threadsScrap = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecutorService used to handle threads decoding certificates
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return ExecutorService to decode certificates
|
||||||
|
*/
|
||||||
|
static public synchronized ExecutorService getDecodeExecutor() {
|
||||||
|
if (decodeExecutor == null) {
|
||||||
|
logger.info("Creating " + threadsDecode + " threads for decodeExecutor");
|
||||||
|
decodeExecutor = Executors.newFixedThreadPool(threadsDecode);
|
||||||
|
}
|
||||||
|
return decodeExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecutorService used to handle threads downloading logs from slices
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return ExecutorService to download logs form slices
|
||||||
|
*/
|
||||||
|
static public synchronized ExecutorService getSliceExecutor() {
|
||||||
|
if (sliceExecutor == null) {
|
||||||
|
logger.info("Creating " + threadsSlice + " threads for sliceExecutor");
|
||||||
|
sliceExecutor = Executors.newFixedThreadPool(threadsSlice);
|
||||||
|
}
|
||||||
|
return sliceExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecutorService used to handle threads creating slices from a server and threads resuming VAT scrapping
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return ExecutorService to create slices and to resume VAT scrapping
|
||||||
|
*/
|
||||||
|
static public synchronized ExecutorService getServerExecutor() {
|
||||||
|
if (serverExecutor == null) {
|
||||||
|
serverExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
}
|
||||||
|
return serverExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecutorService used to handle threads scrapping VAT number from web sites
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return ExecutorService to scrap web site for VAT number
|
||||||
|
*/
|
||||||
|
static public synchronized ExecutorService getVATScrapperExecutor() {
|
||||||
|
if (VATScrapperExecutor == null) {
|
||||||
|
logger.info("Creating " + threadsScrap + " threads for VATScrapperExecutor");
|
||||||
|
VATScrapperExecutor = Executors.newFixedThreadPool(threadsScrap);
|
||||||
|
}
|
||||||
|
return VATScrapperExecutor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,346 @@
|
||||||
|
package be.unamur.ct.web.controller;
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.CertificateDao;
|
||||||
|
import be.unamur.ct.data.dao.ServerDao;
|
||||||
|
import be.unamur.ct.data.service.CertificateService;
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import be.unamur.ct.download.service.ServerService;
|
||||||
|
import be.unamur.ct.download.thread.ScanLogThread;
|
||||||
|
import be.unamur.ct.scrap.service.VATScrapper;
|
||||||
|
import be.unamur.ct.scrap.thread.ResumeVATScrapThread;
|
||||||
|
import be.unamur.ct.thread.ThreadPool;
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class WebController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerDao serverDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateDao certificateDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateService certificateService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VATScrapper vatScrapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerService serverService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(WebController.class);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the HTML page for the home web page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return HTML template to use
|
||||||
|
*/
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "index";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the HTML page for the server list page, displaying all
|
||||||
|
* the Certificate Transparency servers known by the application
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param model Model to use to create the HTML page
|
||||||
|
* @return HTML template to use
|
||||||
|
*/
|
||||||
|
@GetMapping("/serverList")
|
||||||
|
public String showServerList(Model model) {
|
||||||
|
|
||||||
|
Iterable<Server> servers = serverDao.findAll();
|
||||||
|
model.addAttribute("servers", servers);
|
||||||
|
|
||||||
|
Server serverFrom = new Server();
|
||||||
|
model.addAttribute("serverForm", serverFrom);
|
||||||
|
|
||||||
|
return "serverList";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle new server submission via the web page and redirect to the server list page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param model Model to use to create the HTML page
|
||||||
|
* @return Redirection to apply
|
||||||
|
*/
|
||||||
|
@PostMapping("/serverList")
|
||||||
|
public String saveServer(Model model, @ModelAttribute("serverForm") Server serverForm) {
|
||||||
|
logger.info(serverForm.getNickname() + " " + serverForm.getUrl());
|
||||||
|
|
||||||
|
Iterable<Server> servers = serverDao.findAll();
|
||||||
|
model.addAttribute("servers", servers);
|
||||||
|
|
||||||
|
String url = serverForm.getUrl();
|
||||||
|
url = url.endsWith("/") ? url : url + "/";
|
||||||
|
serverForm.setUrl((url));
|
||||||
|
|
||||||
|
if (serverDao.existsByUrl(url)) {
|
||||||
|
logger.warn("URL already exists in database: " + url);
|
||||||
|
model.addAttribute("errorMessage", "URL already exists in database");
|
||||||
|
return "serverList";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Server serverAdded = serverDao.save(serverForm);
|
||||||
|
if (serverAdded == null) {
|
||||||
|
model.addAttribute("errorMessage", "Cannot add server in the database");
|
||||||
|
return "serverList";
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
model.addAttribute("errorMessage", "Cannot add server in the database");
|
||||||
|
return "serverList";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "redirect:/serverList";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the HTML page displaying the status of the application
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param model Model to use to create the HTML page
|
||||||
|
* @return HTML template to use
|
||||||
|
*/
|
||||||
|
@GetMapping("/status")
|
||||||
|
public String status(Model model) {
|
||||||
|
|
||||||
|
model.addAttribute("server",
|
||||||
|
threadPool.getServerExecutor().isShutdown() ?
|
||||||
|
(threadPool.getServerExecutor().isTerminated() ? "Closed" : "Closing") : "Running");
|
||||||
|
|
||||||
|
model.addAttribute("slice",
|
||||||
|
threadPool.getSliceExecutor().isShutdown() ?
|
||||||
|
(threadPool.getSliceExecutor().isTerminated() ? "Closed" : "Closing") : "Running");
|
||||||
|
|
||||||
|
model.addAttribute("decode",
|
||||||
|
threadPool.getDecodeExecutor().isShutdown() ?
|
||||||
|
(threadPool.getDecodeExecutor().isTerminated() ? "Closed" : "Closing") : "Running");
|
||||||
|
|
||||||
|
model.addAttribute("vat",
|
||||||
|
threadPool.getVATScrapperExecutor().isShutdown() ?
|
||||||
|
(threadPool.getVATScrapperExecutor().isTerminated() ? "Closed" : "Closing") : "Running");
|
||||||
|
|
||||||
|
return "status";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the serverExecutor and the sliceExecutor in order to stop the downloading activity of the application.
|
||||||
|
* Redirects to the status page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return Redirection to apply
|
||||||
|
* @see ThreadPool
|
||||||
|
*/
|
||||||
|
@GetMapping("/stopdown")
|
||||||
|
public String stopdown() {
|
||||||
|
threadPool.getServerExecutor().shutdown();
|
||||||
|
threadPool.getSliceExecutor().shutdownNow();
|
||||||
|
|
||||||
|
return "redirect:/status";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the decodeExecutor and the vatScrapperExecutor in order to stop the processing activity of the application.
|
||||||
|
* Redirects to the status page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return Redirection to apply
|
||||||
|
* @see ThreadPool
|
||||||
|
*/
|
||||||
|
@GetMapping("/stopprocess")
|
||||||
|
public String stop() {
|
||||||
|
threadPool.getDecodeExecutor().shutdown();
|
||||||
|
threadPool.getVATScrapperExecutor().shutdownNow();
|
||||||
|
|
||||||
|
return "redirect:/status";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a pageable list of the certificates currently in the database
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param model Model to use to create the HTML page
|
||||||
|
* @param page Current page to show, if empty shows first page
|
||||||
|
* @param size Number of element to show per page, if empty shows 25
|
||||||
|
* @param vatonly If true, displays only the certificates with a VAT number
|
||||||
|
* @return HTML template to use
|
||||||
|
*/
|
||||||
|
@GetMapping("/data")
|
||||||
|
public String listCertificates(
|
||||||
|
Model model,
|
||||||
|
@RequestParam("page") Optional<Integer> page,
|
||||||
|
@RequestParam("size") Optional<Integer> size,
|
||||||
|
@RequestParam("vatonly") Optional<Boolean> vatonly) {
|
||||||
|
|
||||||
|
int currentPage = page.orElse(1);
|
||||||
|
int pageSize = size.orElse(25);
|
||||||
|
boolean vatOnly = vatonly.orElse(false);
|
||||||
|
|
||||||
|
List<Certificate> certificatePage
|
||||||
|
= certificateService.findPaginatedCertificates((currentPage-1), pageSize, vatOnly);
|
||||||
|
|
||||||
|
model.addAttribute("certificatePage", certificatePage);
|
||||||
|
|
||||||
|
int totalPages = (int) Math.ceil(certificateDao.count() / (float) pageSize);
|
||||||
|
if (totalPages > 0) {
|
||||||
|
List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
|
||||||
|
.boxed().collect(Collectors.toList());
|
||||||
|
model.addAttribute("pageNumbers", pageNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.addAttribute("nbCert", certificateDao.count());
|
||||||
|
model.addAttribute("pageSize", pageSize);
|
||||||
|
model.addAttribute("pageNumber", currentPage);
|
||||||
|
model.addAttribute("nbPages", totalPages);
|
||||||
|
model.addAttribute("vatonly", vatOnly);
|
||||||
|
|
||||||
|
|
||||||
|
return "data";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the VAT scrapping of the certificate not yet scrapped in the database
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return Redirection to apply
|
||||||
|
*/
|
||||||
|
@GetMapping("/resumevat")
|
||||||
|
public String resumeVat() {
|
||||||
|
vatScrapper.resumeVatScrapping();
|
||||||
|
return "redirect:/status";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the HTML page displaying the graph about
|
||||||
|
* - The most popular issuers
|
||||||
|
* - The most popular algorithms
|
||||||
|
* - The status of the VAT scrapping
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param model Model to use to create the HTML page
|
||||||
|
* @return HTML template to use
|
||||||
|
*/
|
||||||
|
@GetMapping("/graphs")
|
||||||
|
public String graphs(Model model) {
|
||||||
|
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> issuerData;
|
||||||
|
issuerData = certificateService.issuerGraphData();
|
||||||
|
|
||||||
|
// Set attribute for the issuers graph
|
||||||
|
model.addAttribute("dataIssuer", issuerData.getValue0());
|
||||||
|
model.addAttribute("labelsIssuer", issuerData.getValue1());
|
||||||
|
|
||||||
|
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> algorithmData;
|
||||||
|
algorithmData = certificateService.algorithmGraphData();
|
||||||
|
|
||||||
|
// Set attribute for the algorithm graph
|
||||||
|
model.addAttribute("dataAlg", algorithmData.getValue0());
|
||||||
|
model.addAttribute("labelsAlg", algorithmData.getValue1());
|
||||||
|
|
||||||
|
|
||||||
|
ArrayList<Integer> vatCount;
|
||||||
|
vatCount = certificateService.vatGraphData();
|
||||||
|
|
||||||
|
// Set the attributes for the VAT numbers
|
||||||
|
model.addAttribute("vatCount", vatCount);
|
||||||
|
|
||||||
|
|
||||||
|
model.addAttribute("count", certificateDao.count());
|
||||||
|
|
||||||
|
return "graphs";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts processing of a server and redirects to the server list page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @param id Id of the server to start
|
||||||
|
* @return Redirection to apply
|
||||||
|
*/
|
||||||
|
@GetMapping("/start")
|
||||||
|
public String start(@RequestParam("id") long id) {
|
||||||
|
|
||||||
|
Server myServer = serverDao.findById(id);
|
||||||
|
|
||||||
|
if (myServer != null) {
|
||||||
|
ScanLogThread scan = new ScanLogThread(myServer, serverService);
|
||||||
|
threadPool.getServerExecutor().execute(scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "redirect:/serverList";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the VAT scrapping for the certificates not yet scrapped in the database
|
||||||
|
* Redirects to the home page
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return Redirection to apply
|
||||||
|
*/
|
||||||
|
@GetMapping("/relaunchvat")
|
||||||
|
public String relaunchvat() {
|
||||||
|
|
||||||
|
ResumeVATScrapThread thread = new ResumeVATScrapThread(vatScrapper);
|
||||||
|
threadPool.getServerExecutor().execute(thread);
|
||||||
|
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the application with an exit code = 0
|
||||||
|
*
|
||||||
|
* @author Jules Dejaeghere
|
||||||
|
* @return (Nonsense) redirection to apply
|
||||||
|
*/
|
||||||
|
@GetMapping("/shutdown")
|
||||||
|
public String shutdown() {
|
||||||
|
logger.warn("STOPPING remaining threads, this may take some time.");
|
||||||
|
System.exit(0);
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
code/src/main/resources/application.properties
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/db
|
||||||
|
spring.datasource.username=user
|
||||||
|
spring.datasource.password=password
|
||||||
|
spring.jpa.show-sql=false
|
||||||
|
|
||||||
|
## Hibernate Properties
|
||||||
|
# The SQL dialect makes Hibernate generate better SQL for the chosen database
|
||||||
|
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
|
||||||
|
|
||||||
|
# Hibernate ddl auto (create, create-drop, validate, update)
|
||||||
|
spring.jpa.hibernate.ddl-auto = update
|
||||||
|
|
||||||
|
logging.level.org.springframework.web = INFO
|
||||||
|
server.port = 8090
|
||||||
|
|
||||||
|
# Thymeleaf properties
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
|
|
||||||
|
# Number of thread to create
|
||||||
|
threads-decode = 3
|
||||||
|
threads-slice = 3
|
||||||
|
threads-scrap = 3
|
164
code/src/main/resources/templates/data.html
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>Data | CT-Application</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>CT-Application</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/}">Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/serverList}">Servers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/status}">Status</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" th:href="@{/data}">Data</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav_item">
|
||||||
|
<a class="nav-link" th:href="@{/graphs}">Graphs</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-10">
|
||||||
|
<figure class="figure">
|
||||||
|
<h2>Certificates currently in the database</h2>
|
||||||
|
<figcaption class="figure-caption">
|
||||||
|
About <span th:text="${pageSize * nbPages}">520</span> results
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<a th:if="${!vatonly}" th:href="@{/data(size=${pageSize}, page=${1}, vatonly=${true})}">
|
||||||
|
<button type="button" class="btn btn-danger">Only with VAT</button></a>
|
||||||
|
|
||||||
|
<a th:if="${vatonly}" th:href="@{/data(size=${pageSize}, page=${1}, vatonly=${false})}">
|
||||||
|
<button type="button" class="btn btn-success">Only with VAT</button></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-hover table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Id</th>
|
||||||
|
<th scope="col">Subject</th>
|
||||||
|
<th scope="col">Issuer</th>
|
||||||
|
<th scope="col">Not before</th>
|
||||||
|
<th scope="col">Not after</th>
|
||||||
|
<th scope="col">Signature algorithm</th>
|
||||||
|
<th scope="col">VAT number</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="certificate : ${certificatePage}">
|
||||||
|
<td scope="row" th:text="${certificate.getId()}">ID</td>
|
||||||
|
<td th:text="${certificate.getSubject()}">Subject</td>
|
||||||
|
<td th:text="${certificate.getIssuer()}">Issuer</td>
|
||||||
|
<td th:text="${certificate.getNotBefore()}">Not before</td>
|
||||||
|
<td th:text="${certificate.getNotAfter()}">Not after</td>
|
||||||
|
<td th:text="${certificate.getSignatureAlg()}">Algorithm</td>
|
||||||
|
<td th:if="${certificate.getVAT() == null}">-</td>
|
||||||
|
<td th:if="${certificate.getVAT() != null}">
|
||||||
|
<a th:text="${certificate.getVAT()}"
|
||||||
|
th:href="${'https://kbopub.economie.fgov.be/kbopub/zoeknummerform.html?nummer='
|
||||||
|
+ certificate.getVAT().substring(2) +'&actionLu=Recherche'}"
|
||||||
|
target="_blank">VAT</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row h-100 justify-content-center align-items-center">
|
||||||
|
<nav th:if="${nbCert / pageSize > 0 and nbCert / pageSize < 4}" aria-label="Page navigation">
|
||||||
|
<ul class="pagination">
|
||||||
|
|
||||||
|
<li th:each="pageNumber : ${pageNumbers}" class="page-item">
|
||||||
|
<a th:href="@{/data(size=${pageSize}, page=${pageNumber}, vatonly=${vatonly})}"
|
||||||
|
th:text="${pageNumber}" class="page-link"></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<nav th:if="${nbPages > 0 and nbPages > 3}" aria-label="Page navigation">
|
||||||
|
<ul class="pagination">
|
||||||
|
|
||||||
|
<li th:if="${pageNumber > 1}" class="page-item">
|
||||||
|
<a class="page-link" th:href="@{/data(size=${pageSize}, page=${pageNumber}, vatonly=${vatonly})}" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li th:if="${pageNumber == 1}" class="page-item" disabled>
|
||||||
|
<a class="page-link" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
|
||||||
|
<li th:if="${pageNumber > 1}" class="page-item">
|
||||||
|
<a th:href="@{/data(size=${pageSize}, page=${pageNumber - 1}, vatonly=${vatonly})}"
|
||||||
|
th:text="${pageNumber - 1}" class="page-link"></a></li>
|
||||||
|
|
||||||
|
<li class="page-item active">
|
||||||
|
<a th:href="@{/data(size=${pageSize}, page=${pageNumber}, vatonly=${vatonly})}"
|
||||||
|
th:text="${pageNumber}" class="page-link"></a></li>
|
||||||
|
|
||||||
|
<li th:if="${nbPages > pageNumber}" class="page-item">
|
||||||
|
<a th:href="@{/data(size=${pageSize}, page=${pageNumber + 1}, vatonly=${vatonly})}"
|
||||||
|
th:text="${pageNumber + 1}" class="page-link"></a></li>
|
||||||
|
|
||||||
|
|
||||||
|
<li th:if="${nbPages > pageNumber}" class="page-item">
|
||||||
|
<a class="page-link" th:href="@{/data(size=${pageSize}, page=${pageNumber + 1}, vatonly=${vatonly})}" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li th:if="${!(nbPages > pageNumber)}" class="page-item" disabled>
|
||||||
|
<a class="page-link" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
199
code/src/main/resources/templates/graphs.html
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.css" integrity="sha256-IvM9nJf/b5l2RoebiFno92E5ONttVyaEEsdemDC6iQA=" crossorigin="anonymous" />
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.js" integrity="sha256-nZaxPHA2uAaquixjSDX19TmIlbRNCOrf5HO1oHl5p70=" crossorigin="anonymous"></script>
|
||||||
|
<title>Graphs | CT-Application</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>CT-Application</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/}">Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/serverList}">Servers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/status}">Status</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/data}">Data</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav_item">
|
||||||
|
<a class="nav-link active" th:href="@{/graphs}">Graphs</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<h2>Statistics</h2>
|
||||||
|
<figcaption class="figure-caption">
|
||||||
|
About <span th:text="${count}">520</span> certificates in the database
|
||||||
|
</figcaption>
|
||||||
|
|
||||||
|
<div th:if="${!(dataIssuer.isEmpty() || labelsIssuer.isEmpty()) || !(vatCount.isEmpty()) || !(dataAlg.isEmpty() || labelsAlg.isEmpty())}">
|
||||||
|
<div class="row">
|
||||||
|
<div th:if="${!(dataIssuer.isEmpty() || labelsIssuer.isEmpty())}" class="col-lg-6">
|
||||||
|
<canvas id="issuers"></canvas>
|
||||||
|
<br/>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var ctx = document.getElementById('issuers').getContext('2d');
|
||||||
|
var issuers = new Chart(ctx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: /*[[${labelsIssuer}]]*/ ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
||||||
|
datasets: [{
|
||||||
|
data: /*[[${dataIssuer}]]*/ [1, 1, 1, 1, 1, 1],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.5)',
|
||||||
|
'rgba(54, 162, 235, 0.5)',
|
||||||
|
'rgba(255, 206, 86, 0.5)',
|
||||||
|
'rgba(75, 192, 192, 0.5)',
|
||||||
|
'rgba(153, 102, 255, 0.5)',
|
||||||
|
'rgba(255, 159, 64, 0.5)'
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(255, 99, 132, 1)',
|
||||||
|
'rgba(54, 162, 235, 1)',
|
||||||
|
'rgba(255, 206, 86, 1)',
|
||||||
|
'rgba(75, 192, 192, 1)',
|
||||||
|
'rgba(153, 102, 255, 1)',
|
||||||
|
'rgba(255, 159, 64, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend:{display:false},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Most popular issuers',
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${!(dataAlg.isEmpty() || labelsAlg.isEmpty())}" class="col-lg-6">
|
||||||
|
<canvas id="alg"></canvas>
|
||||||
|
<br/>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var ctx = document.getElementById('alg').getContext('2d');
|
||||||
|
var alg = new Chart(ctx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: /*[[${labelsAlg}]]*/ ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
|
||||||
|
datasets: [{
|
||||||
|
data: /*[[${dataAlg}]]*/ [1, 1, 1, 1, 1, 1],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.5)',
|
||||||
|
'rgba(54, 162, 235, 0.5)',
|
||||||
|
'rgba(255, 206, 86, 0.5)',
|
||||||
|
'rgba(75, 192, 192, 0.5)',
|
||||||
|
'rgba(153, 102, 255, 0.5)',
|
||||||
|
'rgba(255, 159, 64, 0.5)'
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(255, 99, 132, 1)',
|
||||||
|
'rgba(54, 162, 235, 1)',
|
||||||
|
'rgba(255, 206, 86, 1)',
|
||||||
|
'rgba(75, 192, 192, 1)',
|
||||||
|
'rgba(153, 102, 255, 1)',
|
||||||
|
'rgba(255, 159, 64, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend:{display:false},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Most popular signature algorithms',
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${!(vatCount.isEmpty())}" class="col-lg-6">
|
||||||
|
<canvas id="vatChart"></canvas>
|
||||||
|
<br/>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var ctx = document.getElementById('vatChart').getContext('2d');
|
||||||
|
var vatChart = new Chart(ctx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: ['Exists', 'Not found', 'Not yet searched'],
|
||||||
|
datasets: [{
|
||||||
|
data: /*[[${vatCount}]]*/ [1, 1, 1],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.5)',
|
||||||
|
'rgba(54, 162, 235, 0.5)',
|
||||||
|
'rgba(255, 206, 86, 0.5)'
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(255, 99, 132, 1)',
|
||||||
|
'rgba(54, 162, 235, 1)',
|
||||||
|
'rgba(255, 206, 86, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
legend:{display:false},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'VAT search status',
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${(dataIssuer.isEmpty() || labelsIssuer.isEmpty()) && vatCount.isEmpty() && (dataAlg.isEmpty() || labelsAlg.isEmpty())}" class="row d-flex justify-content-center">
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="alert alert-primary text-center" role="alert">
|
||||||
|
No data to show
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
71
code/src/main/resources/templates/index.html
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>Home | CT-Application</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>CT-Application</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text">Manage servers currently in the database and add new servers.</p>
|
||||||
|
<a th:href="@{/serverList}" class="btn btn-primary">Servers</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text">Get the status of the different parts of the application.</p>
|
||||||
|
<a th:href="@{/status}" class="btn btn-primary">Status</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text">Display data collected by the application.</p>
|
||||||
|
<a th:href="@{/data}" class="btn btn-primary">Data</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text">Display statistics about data currently in the database</p>
|
||||||
|
<a th:href="@{/graphs}" class="btn btn-primary">Graphs</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
92
code/src/main/resources/templates/serverList.html
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>Servers | CT-Application</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>CT-Application</h1>
|
||||||
|
</div>
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/}">Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" th:href="@{/serverList}">Servers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/status}">Status</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/data}">Data</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav_item">
|
||||||
|
<a class="nav-link" th:href="@{/graphs}">Graphs</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8">
|
||||||
|
<h2>Existing servers</h2>
|
||||||
|
<table class="table table-hover table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Id</th>
|
||||||
|
<th scope="col">Nickname</th>
|
||||||
|
<th scope="col">URL</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="server : ${servers}">
|
||||||
|
<th scope="row" th:text="${server.getId()}">Id</th>
|
||||||
|
<td th:text="${server.getNickname()}">Nickname</td>
|
||||||
|
<td><a th:href="${server.getUrl()}" target="_blank"><span th:text="${server.getUrl()}">URL</span></a></td>
|
||||||
|
<td><a th:href="@{/start(id=${server.getId()})}" class="btn btn-light btn-sm">Start</a> </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<h2>Add new server</h2>
|
||||||
|
<form th:action="@{/serverList}" th:object="${serverForm}" method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="nickname">Nickname</label>
|
||||||
|
<input type="text" class="form-control" th:field="*{nickname}" id="nickname" aria-describedby="emailHelp">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="url">URL</label>
|
||||||
|
<input type="url" class="form-control" id="url" th:field="*{url}">
|
||||||
|
</div>
|
||||||
|
<input type="submit" class="btn btn-primary" value="Create">
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
<div class="alert alert-danger" role="alert" th:if="${errorMessage}" th:utext="${errorMessage}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
153
code/src/main/resources/templates/status.html
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>Status | CT-Application</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>CT-Application</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/}">Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/serverList}">Servers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" th:href="@{/status}">Status</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{/data}">Data</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav_item">
|
||||||
|
<a class="nav-link" th:href="@{/graphs}">Graphs</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5>Server handling</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">Manage server launching and start downloading certificates from it.</li>
|
||||||
|
<li class="list-group-item" th:text="${server}">status</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5>Downloader</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">Download certificates from launched servers.</li>
|
||||||
|
<li class="list-group-item" th:text="${slice}">status</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5>Decoder</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">Decode downloaded certificates to store them in the database.</li>
|
||||||
|
<li class="list-group-item" th:text="${decode}">status</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5>VAT scrapper</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">Browser websites to find VAT numbers.</li>
|
||||||
|
<li class="list-group-item" th:text="${vat}">status</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<div class="card border-danger mb-3" >
|
||||||
|
<div class="card-body text-danger">
|
||||||
|
<h5 class="card-title">Danger Zone</h5>
|
||||||
|
<p class="card-text">To stop the application, it is recommended to first stop the downloading part
|
||||||
|
(<em>Server Handling</em> and <em>Downloader</em>). Then other parts can be safely stopped.</p>
|
||||||
|
|
||||||
|
<div th:if="${server == 'Running' and slice == 'Running'}">
|
||||||
|
<a th:href="@{/stopdown}"><button type="button" class="btn btn-primary">Stop downloading part</button></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${server == 'Closing' or slice == 'Closing'}">
|
||||||
|
<button type="button" class="btn btn-primary" disabled>Downloading pat is stopping</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${server == 'Closed' and slice == 'Closed' and decode == 'Running' and vat == 'Running'}">
|
||||||
|
<a th:href="@{/stopprocess}"><button type="button" class="btn btn-primary">Stop processing part</button></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${server == 'Closed' and slice == 'Closed' and (decode == 'Closing' or vat == 'Closing')}">
|
||||||
|
<button type="button" class="btn btn-primary" disabled>Processing part is stopping</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${server == 'Closed' and slice == 'Closed' and decode == 'Closed' and vat == 'Closed'}">
|
||||||
|
<a th:href="@{/shutdown}"><button type="button" class="btn btn-danger">SHUTDOWN Application</button></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:if="${vat == 'Running'}" class="col-lg-3">
|
||||||
|
<div class="card border-warning mb-3" >
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title warning-text">Relaunch VAT scrapper</h5>
|
||||||
|
<p class="card-text">If the VAT scrapper was stopped in a previous execution, relaunch it from here.</p>
|
||||||
|
<a class="btn btn-outline-dark" href="/relaunchvat">Relaunch</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
31
code/src/main/resources/templates/theme.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>Page | CT-Application</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h1>CT-Application</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
128
code/src/test/java/be/unamur/ct/CertificateRepositoryTest.java
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.CertificateDao;
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@DataJpaTest
|
||||||
|
public class CertificateRepositoryTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestEntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateDao certificateDao;
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupDatabase(){
|
||||||
|
Certificate cert = new Certificate();
|
||||||
|
cert.setSubject("www.test.com");
|
||||||
|
cert.setVatSearched(true);
|
||||||
|
cert.setVAT("BE0123456789");
|
||||||
|
cert.setIssuer("AddTrust External CA Root");
|
||||||
|
cert.setNotAfter(new Date());
|
||||||
|
cert.setNotBefore(new Date());
|
||||||
|
entityManager.persist(cert);
|
||||||
|
|
||||||
|
Certificate cert2 = new Certificate();
|
||||||
|
cert2.setSubject("www.test.org");
|
||||||
|
cert2.setVatSearched(true);
|
||||||
|
entityManager.persist(cert2);
|
||||||
|
|
||||||
|
Certificate cert3 = new Certificate();
|
||||||
|
cert3.setSubject("www.example.com");
|
||||||
|
cert3.setVatSearched(true);
|
||||||
|
cert3.setVAT("BE0987654321");
|
||||||
|
entityManager.persist(cert3);
|
||||||
|
|
||||||
|
Certificate cert4 = new Certificate();
|
||||||
|
cert4.setSubject("www.example.org");
|
||||||
|
entityManager.persist(cert4);
|
||||||
|
|
||||||
|
|
||||||
|
entityManager.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindAllOrderedById(){
|
||||||
|
List<Certificate> found = certificateDao.findAllByOrderByIdAsc(PageRequest.of(0, Integer.MAX_VALUE));
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(found.size() == 4);
|
||||||
|
assertTrue(found.get(0).getId() < found.get(1).getId());
|
||||||
|
|
||||||
|
Certificate firstFound = found.get(0);
|
||||||
|
|
||||||
|
assertThat(firstFound.getSubject()).isEqualTo("www.test.com");
|
||||||
|
assertTrue(firstFound.isVatSearched());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindAllVATNotNullOrderedById(){
|
||||||
|
List<Certificate> found = certificateDao.findAllByVATNotNullOrderByIdAsc(PageRequest.of(0, Integer.MAX_VALUE));
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(found.size() == 2);
|
||||||
|
|
||||||
|
for(Certificate c : found){
|
||||||
|
assertNotNull(c.getVAT());
|
||||||
|
assertTrue(c.isVatSearched());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(found.get(0).getId() < found.get(1).getId());
|
||||||
|
|
||||||
|
assertThat(found.get(0).getVAT()).isEqualTo("BE0123456789");
|
||||||
|
assertThat(found.get(1).getVAT()).isEqualTo("BE0987654321");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCountByVATMethods(){
|
||||||
|
int vatNullAndNotSearched = certificateDao.countByVATIsNullAndVatSearched(false);
|
||||||
|
int vatNullAndSearched = certificateDao.countByVATIsNullAndVatSearched(true);
|
||||||
|
|
||||||
|
int vatNotNullAndSearched = certificateDao.countByVATIsNotNullAndVatSearched(true);
|
||||||
|
int vatNotNullAndNotSearched = certificateDao.countByVATIsNotNullAndVatSearched(false);
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(vatNullAndNotSearched == 1);
|
||||||
|
assertTrue(vatNullAndSearched == 1);
|
||||||
|
|
||||||
|
assertTrue(vatNotNullAndNotSearched == 0);
|
||||||
|
assertTrue(vatNotNullAndSearched == 2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindByVatSearched(){
|
||||||
|
List<Certificate> vatSearched = certificateDao.findByVatSearched(true);
|
||||||
|
List<Certificate> vatNotSearched = certificateDao.findByVatSearched(false);
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(vatNotSearched.size() == 1);
|
||||||
|
assertTrue(vatSearched.size() == 3);
|
||||||
|
|
||||||
|
for(Certificate c : vatSearched){
|
||||||
|
assertFalse(vatNotSearched.contains(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
code/src/test/java/be/unamur/ct/CertificateServiceTest.java
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.CertificateDao;
|
||||||
|
import be.unamur.ct.data.service.CertificateService;
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
public class CertificateServiceTest {
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
static class CertificateServiceTestContextConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CertificateService certificateService() {
|
||||||
|
return new CertificateService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private CertificateDao certificateDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CertificateService certificateService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupMock(){
|
||||||
|
Mockito.when(certificateDao.countByVATIsNotNullAndVatSearched(true))
|
||||||
|
.thenReturn(2);
|
||||||
|
Mockito.when(certificateDao.countByVATIsNullAndVatSearched(true))
|
||||||
|
.thenReturn(1);
|
||||||
|
Mockito.when(certificateDao.countByVATIsNullAndVatSearched(false))
|
||||||
|
.thenReturn(1);
|
||||||
|
|
||||||
|
List<Object[]> issuer = new ArrayList<>();
|
||||||
|
issuer.add(new Object[]{"Bob", BigInteger.valueOf(1)});
|
||||||
|
issuer.add(new Object[]{"Alice", BigInteger.valueOf(3)});
|
||||||
|
|
||||||
|
Mockito.when(certificateDao.distinctIssuer())
|
||||||
|
.thenReturn(issuer);
|
||||||
|
|
||||||
|
List<Object[]> algo = new ArrayList<>();
|
||||||
|
algo.add(new Object[]{"Alg1", BigInteger.valueOf(3)});
|
||||||
|
algo.add(new Object[]{"Alg2", BigInteger.valueOf(1)});
|
||||||
|
|
||||||
|
Mockito.when(certificateDao.distinctAlgorithm())
|
||||||
|
.thenReturn(algo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVatGraphData(){
|
||||||
|
|
||||||
|
ArrayList<Integer> result = certificateService.vatGraphData();
|
||||||
|
|
||||||
|
|
||||||
|
ArrayList<Integer> expected = new ArrayList<>();
|
||||||
|
expected.add(2);
|
||||||
|
expected.add(1);
|
||||||
|
expected.add(1);
|
||||||
|
|
||||||
|
assertThat(result.size()).isEqualTo(expected.size());
|
||||||
|
|
||||||
|
for(int i = 0; i < result.size(); i++){
|
||||||
|
assertThat(result.get(i)).isEqualTo(expected.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIssuerGraphData(){
|
||||||
|
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> result = certificateService.issuerGraphData();
|
||||||
|
|
||||||
|
|
||||||
|
ArrayList<BigInteger> num = new ArrayList<>();
|
||||||
|
num.add(BigInteger.valueOf(3));
|
||||||
|
num.add(BigInteger.valueOf(1));
|
||||||
|
|
||||||
|
ArrayList<String> labels = new ArrayList<>();
|
||||||
|
labels.add("Alice");
|
||||||
|
labels.add("Bob");
|
||||||
|
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> expected = new Pair<>(num, labels);
|
||||||
|
|
||||||
|
checkList(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAlgorithmGraphData(){
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> result = certificateService.algorithmGraphData();
|
||||||
|
|
||||||
|
|
||||||
|
ArrayList<BigInteger> num = new ArrayList<>();
|
||||||
|
num.add(BigInteger.valueOf(3));
|
||||||
|
num.add(BigInteger.valueOf(1));
|
||||||
|
|
||||||
|
ArrayList<String> labels = new ArrayList<>();
|
||||||
|
labels.add("Alg1");
|
||||||
|
labels.add("Alg2");
|
||||||
|
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> expected = new Pair<>(num, labels);
|
||||||
|
|
||||||
|
checkList(result, expected);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkList(Pair<ArrayList<BigInteger>, ArrayList<String>> result,
|
||||||
|
Pair<ArrayList<BigInteger>, ArrayList<String>> expected) {
|
||||||
|
|
||||||
|
assertThat(result.getValue0().size()).isEqualTo(expected.getValue0().size());
|
||||||
|
assertThat(result.getValue1().size()).isEqualTo(expected.getValue1().size());
|
||||||
|
|
||||||
|
for (int i = 0; i < result.getValue0().size(); i++) {
|
||||||
|
assertThat(result.getValue0().get(i)).isEqualTo(expected.getValue0().get(i));
|
||||||
|
assertThat(result.getValue1().get(i)).isEqualTo(expected.getValue1().get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
code/src/test/java/be/unamur/ct/DecodeServiceTest.java
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.decode.model.Certificate;
|
||||||
|
import be.unamur.ct.decode.service.DecodeService;
|
||||||
|
import be.unamur.ct.download.model.LogEntry;
|
||||||
|
import be.unamur.ct.scrap.service.VATScrapper;
|
||||||
|
import org.bouncycastle.asn1.x500.RDN;
|
||||||
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
|
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The database specified in the application.properties file should be running in order to run these test.
|
||||||
|
* These tests will initialize the complete ApplicationContext to run, including the database
|
||||||
|
* No changes will be made to the database, the application will only try to connect
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
public class DecodeServiceTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DecodeService decodeService;
|
||||||
|
|
||||||
|
private LogEntry logEntry;
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before(){
|
||||||
|
logEntry = new LogEntry();
|
||||||
|
logEntry.setId(1);
|
||||||
|
logEntry.setLeaf("AAAAAAFjGAu0yAAAAAbPMIIGyzCCBbOgAwIBAgIQZlFx8QTMtgcUntq9QFCHWjANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTAeFw0xODA0MzAwMDAwMDBaFw0yMDA1MDkyMzU5NTlaMFMxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDEUMBIGA1UECxMLUG9zaXRpdmVTU0wxGDAWBgNVBAMTD3d3dy52cHJtZWRpYS5iZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALR+M5+PjT5WBczqcHQhsA/qzMXpKnuf2FDzyGXjNnAQ45SdRkkYJDr/uEBTyufrF0x3Rdg4YRnP2qSs4EqoUmMd0rXdPO4dd/DZqtrH0qn8O+P5nWUi8fQcWBrYp3W0wTRScu0YaBUVTv67IvExFNEpNpXM4tt45h7SI8sTuJLX/ORVLLlCgq+GrfFDaUfZzpKWIygJsO+164usNfzMINF8RabJZdz63hrW0x4NP9MbQ443Dj3iRexOdCWgVD4jcw3zJsC4T1K+KF43Vux8iHvsa1EtcWW63fsa5oxUlZqAWzz1OmU7bjQVKqE2nD947q3fsUQv4IL4Uc4yW/dEmKECAwEAAaOCA1swggNXMB8GA1UdIwQYMBaAFJCvajqUWgvYkOoSVnPfQ7Q6KNrnMB0GA1UdDgQWBBTydqWsNyXXz7CtSLVKTNBYbfA37TAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZngQwBAgEwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5jb21vZG9jYS5jb20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNybDCBhQYIKwYBBQUHAQEEeTB3ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9SU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wJwYDVR0RBCAwHoIPd3d3LnZwcm1lZGlhLmJlggt2cHJtZWRpYS5iZTCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHcA7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/csAAAFjGAtWrQAABAMASDBGAiEAkTzWalbVsj+WizHWqUv3swNa34ArzVf4B+0RLRFBUdsCIQCfnzMpoA9toBeQfLsfpW0l/KjAFYoDhZ/Wefrvb1vZkQB2AF6nc/nfVsDntTZIfdBJ4DJ6kZoMhKESEoQYdZaBcUVYAAABYxgLUdYAAAQDAEcwRQIgLvp+uLvdsQaqqViULf9sg7dzs6PDh74cNoStqf0Z2hcCIQD5TPPpUXFR1AC93mwe2Ec5BG1K4ukf9zEd5pbmUSP9FAB1AFWB1MIWkDYBSuoLm1c8U/DA5Dh4cCUIFy+jqh0HE9MMAAABYxgLT8wAAAQDAEYwRAIgGQ4gUsiFQ8cgLAFB8CfhW9x/mpoDRT1gtT4DD870PzwCID9UOYUG9UR1dblTzxPjmt5KDrLO/qTOavxkwatEm696MA0GCSqGSIb3DQEBCwUAA4IBAQBmmJy0hnRpFUYEQqJMgIHrS1lSliraDlHgtmoqQhN8hJ5r20XUxlXf2yvTLvM6Dx5pJSCZGn+bqWDBqXXpLZvH/dmrgCo7hZsNnb0j9t8kEcltHGIb6kMcjKYrXAtiuwPlqKcS6eHn3qzcKSeirQza8WnoMmO0TVmxteXPiL8Bd7ibsPOZnp/vYhu49B6YLDhYB2Kqc4c53LwZ0dngUp7u1R4uXrQUH8AZ062swUSZT9+tfbMDKhJuuFmn6fk/Q3tYnvdOOw5bCf7f7j15OIXhJZKsiAExbyGymzFkGzGi5cHunJKrJJJs0Czpz3RADckeLCTxOKH/BaRGv1uTxz1RAAA=");
|
||||||
|
logEntry.setData("AAvuAAYMMIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEyMDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28ShbXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0nc13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQYMBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcBAQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2pmj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBpsP0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMYdVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxGV/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQXj4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5AplBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf+AZxAeKCINT+b72xAAXcMIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHBNVOFBkpdn627G190");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchRootAndSetAttributes() throws IOException {
|
||||||
|
// Get an object from the previously saved list and extract certificate
|
||||||
|
String leaf = logEntry.getLeaf();
|
||||||
|
String extra = logEntry.getData();
|
||||||
|
byte[] leafBin = Base64.decode(leaf);
|
||||||
|
|
||||||
|
// Get certificate type (X.509 or PreCert)
|
||||||
|
int l = (leafBin[14] & 0xFF) | ((leafBin[13] & 0xFF) << 8) | ((leafBin[12] & 0x0F) << 16);
|
||||||
|
|
||||||
|
// Extract only interesting part from certificate and create a X509CertificateHolder Object from it
|
||||||
|
byte[] certBin = Arrays.copyOfRange(leafBin, 15, l + 15);
|
||||||
|
X509CertificateHolder certX = new X509CertificateHolder(certBin);
|
||||||
|
|
||||||
|
|
||||||
|
String cns;
|
||||||
|
RDN cn;
|
||||||
|
|
||||||
|
// Get Subject
|
||||||
|
cn = certX.getSubject().getRDNs(BCStyle.CN)[0];
|
||||||
|
cns = IETFUtils.valueToString(cn.getFirst().getValue());
|
||||||
|
|
||||||
|
|
||||||
|
// Create certificate
|
||||||
|
Certificate certificate = new Certificate(cns);
|
||||||
|
|
||||||
|
// Get root CA
|
||||||
|
String issuer = decodeService.searchRoot(extra);
|
||||||
|
certificate.setIssuer(issuer);
|
||||||
|
|
||||||
|
certificate = decodeService.setAttributes(certificate, certX);
|
||||||
|
|
||||||
|
assertThat(certificate.getIssuer()).isEqualTo("COMODO RSA Certification Authority");
|
||||||
|
assertThat(certificate.getSubject()).isEqualTo("www.vprmedia.be");
|
||||||
|
assertThat(certificate.getSignatureAlg()).isEqualTo("SHA256WITHRSA");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
code/src/test/java/be/unamur/ct/ServerRepositoryTest.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.ServerDao;
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@DataJpaTest
|
||||||
|
public class ServerRepositoryTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestEntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerDao serverDao;
|
||||||
|
|
||||||
|
private Server srv1, srv2;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupDatabase(){
|
||||||
|
|
||||||
|
srv1 = new Server("http://www.test-server.com/", "First Test");
|
||||||
|
srv2 = new Server("http://www.test.com/", "Second Test");
|
||||||
|
srv1 = entityManager.persist(srv1);
|
||||||
|
srv2 = entityManager.persist(srv2);
|
||||||
|
|
||||||
|
entityManager.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindById(){
|
||||||
|
Server s1 = serverDao.findById(srv1.getId());
|
||||||
|
Server s2 = serverDao.findById(-10);
|
||||||
|
|
||||||
|
|
||||||
|
assertNotNull(s1);
|
||||||
|
assertNull(s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExistsByUrl(){
|
||||||
|
boolean exists1 = serverDao.existsByUrl("http://www.test.com/");
|
||||||
|
boolean exists2 = serverDao.existsByUrl("http://www.not-found.com/");
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(exists1);
|
||||||
|
assertFalse(exists2);
|
||||||
|
}
|
||||||
|
}
|
77
code/src/test/java/be/unamur/ct/ServerServiceTest.java
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import be.unamur.ct.download.service.ServerService;
|
||||||
|
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||||
|
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The database specified in the application.properties file should be running in order to run these test.
|
||||||
|
* These tests will initialize the complete ApplicationContext to run, including the database
|
||||||
|
* No changes will be made to the database, the application will only try to connect
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
public class ServerServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerService serverService;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WireMockRule wireMockRule = new WireMockRule(options().port(8080));
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerService.class);
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws IOException {
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
server.setUrl("http://localhost:8080/");
|
||||||
|
server.setNickname("Local test server");
|
||||||
|
server.setId(1);
|
||||||
|
|
||||||
|
// Creating small test web server to serve web pages to scrap
|
||||||
|
InputStream simpleInput = getClass().getClassLoader().getResourceAsStream("json/sth.json");
|
||||||
|
String simpleHtml = new String(IOUtils.toByteArray(simpleInput), Charset.forName("UTF-8"));
|
||||||
|
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/ct/v1/get-sth"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withBody(simpleHtml)
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckSize(){
|
||||||
|
long size;
|
||||||
|
size = serverService.checkSize(server);
|
||||||
|
|
||||||
|
assertThat(size).isEqualTo(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
90
code/src/test/java/be/unamur/ct/SliceRepositoryTest.java
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.data.dao.ServerDao;
|
||||||
|
import be.unamur.ct.data.dao.SliceDao;
|
||||||
|
import be.unamur.ct.download.model.Server;
|
||||||
|
import be.unamur.ct.download.model.Slice;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@DataJpaTest
|
||||||
|
public class SliceRepositoryTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TestEntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SliceDao sliceDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ServerDao serverDao;
|
||||||
|
|
||||||
|
private Slice slice1, slice2, slice3;
|
||||||
|
private Server server;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupDatabase(){
|
||||||
|
|
||||||
|
server = new Server("http://test.com/", "Test Server");
|
||||||
|
|
||||||
|
server = entityManager.persist(server);
|
||||||
|
|
||||||
|
slice1 = new Slice(0, 99, 0, server);
|
||||||
|
slice2 = new Slice(100, 199, 100, server);
|
||||||
|
slice3 = new Slice(200, 299, 200, server);
|
||||||
|
|
||||||
|
slice1 = entityManager.persist(slice1);
|
||||||
|
slice2 = entityManager.persist(slice2);
|
||||||
|
slice3 = entityManager.persist(slice3);
|
||||||
|
|
||||||
|
entityManager.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExistsAndDeleteById(){
|
||||||
|
boolean s1 = sliceDao.existsById(slice1.getId());
|
||||||
|
boolean none = sliceDao.existsById(199);
|
||||||
|
|
||||||
|
assertTrue(s1);
|
||||||
|
assertFalse(none);
|
||||||
|
|
||||||
|
|
||||||
|
sliceDao.deleteById(slice1.getId());
|
||||||
|
s1 = sliceDao.existsById(slice1.getId());
|
||||||
|
|
||||||
|
assertFalse(s1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindByServerOrderByEndSliceDesc(){
|
||||||
|
Server s = serverDao.findById(server.getId());
|
||||||
|
List<Slice> slices = sliceDao.findByServerOrderByEndSliceDesc(s);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<Long> endSlice = new ArrayList<>();
|
||||||
|
for(Slice slice : slices){
|
||||||
|
endSlice.add(slice.getEndSlice());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean sorted = Ordering.natural().reverse().isOrdered(endSlice);
|
||||||
|
|
||||||
|
assertTrue(sorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
156
code/src/test/java/be/unamur/ct/VATScrapperTest.java
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package be.unamur.ct;
|
||||||
|
|
||||||
|
|
||||||
|
import be.unamur.ct.scrap.service.VATScrapper;
|
||||||
|
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||||
|
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The database specified in the application.properties file should be running in order to run these test.
|
||||||
|
* These tests will initialize the complete ApplicationContext to run, including the database
|
||||||
|
* No changes will be made to the database, the application will only try to connect
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
public class VATScrapperTest {
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
static class VATScrapperTestContextConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public VATScrapper vatScrapper() {
|
||||||
|
return new VATScrapper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VATScrapper vatScrapper;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WireMockRule wireMockRule = new WireMockRule(options().port(8080));
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws IOException {
|
||||||
|
|
||||||
|
// Creating small test web server to serve web pages to scrap
|
||||||
|
InputStream simpleInput = getClass().getClassLoader().getResourceAsStream("html/simple/index.html");
|
||||||
|
String simpleHtml = new String(IOUtils.toByteArray(simpleInput), Charset.forName("UTF-8"));
|
||||||
|
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withBody(simpleHtml)
|
||||||
|
.withHeader("Content-Type", "text/html; charset=UTF-8")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
InputStream complexInput;
|
||||||
|
String complexHtml;
|
||||||
|
|
||||||
|
for(String s : new String[]{"index", "page-1", "page-2"}) {
|
||||||
|
complexInput = getClass().getClassLoader().getResourceAsStream("html/complex/" + s + ".html");
|
||||||
|
complexHtml = new String(IOUtils.toByteArray(complexInput), Charset.forName("UTF-8"));
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/complex/" + s))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withBody(complexHtml)
|
||||||
|
.withHeader("Content-Type", "text/html; charset=UTF-8"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String s : new String[]{"wrong-checksum", "wrong-format"}) {
|
||||||
|
complexInput = getClass().getClassLoader().getResourceAsStream("html/wrong/" + s + ".html");
|
||||||
|
complexHtml = new String(IOUtils.toByteArray(complexInput), Charset.forName("UTF-8"));
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/wrong/" + s))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withBody(complexHtml)
|
||||||
|
.withHeader("Content-Type", "text/html; charset=UTF-8"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchPage() throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
String vat;
|
||||||
|
|
||||||
|
// Easy case: VAT on first page
|
||||||
|
vat = vatScrapper.searchPage(new URL("http://localhost:8080/"), 0, new HashSet<>());
|
||||||
|
assertThat(vat).isEqualTo("BE0542703815");
|
||||||
|
|
||||||
|
|
||||||
|
// VAT not on first page and depth too small
|
||||||
|
vat = vatScrapper.searchPage(new URL("http://localhost:8080/complex/index"), 0, new HashSet<>());
|
||||||
|
assertNull(vat);
|
||||||
|
|
||||||
|
|
||||||
|
// VAT not on first page and depth large enough to find it on second page
|
||||||
|
vat = vatScrapper.searchPage(new URL("http://localhost:8080/complex/index"), 3, new HashSet<>());
|
||||||
|
assertThat(vat).isEqualTo("BE0542703815");
|
||||||
|
|
||||||
|
// VAT not found because of wrong format
|
||||||
|
vat = vatScrapper.searchPage(new URL("http://localhost:8080/wrong/wrong-format"), 0, new HashSet<>());
|
||||||
|
assertNull(vat);
|
||||||
|
|
||||||
|
// VAT not found because of wrong checksum
|
||||||
|
vat = vatScrapper.searchPage(new URL("http://localhost:8080/wrong/wrong-checksum"), 0, new HashSet<>());
|
||||||
|
assertNull(vat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalizeVAT(){
|
||||||
|
String[] raw = {"BE0123 456 346", "BE0123-456-346", "BE0123.456.346",
|
||||||
|
"0123 456 346", "0123456346", "0123.456.346"};
|
||||||
|
|
||||||
|
String normalized;
|
||||||
|
for(String vat : raw){
|
||||||
|
normalized = vatScrapper.normalizeVAT(vat);
|
||||||
|
assertThat(normalized).isEqualTo("BE0123456346");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsValidVAT(){
|
||||||
|
String[] valid = {"BE0666679317", "BE0457741515", "BE0843370953"};
|
||||||
|
String[] invalid = {"BE0666679300", "BE0457741542", "BE0843370973"};
|
||||||
|
|
||||||
|
for(String vat : valid){
|
||||||
|
assertTrue(vatScrapper.isValidVAT(vat));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String vat : invalid){
|
||||||
|
assertFalse(vatScrapper.isValidVAT(vat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
code/src/test/resources/html/complex/index.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Test page HTML</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>No VAT number on this page</h1>
|
||||||
|
<p>Look in pages linked</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="/complex/page-1">Page 1</a> </li>
|
||||||
|
<li><a href="/complex/page-2">Page 2</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
10
code/src/test/resources/html/complex/page-1.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Page-1 | Test page HTML</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>BE0542.703.815</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
code/src/test/resources/html/complex/page-2.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Page-2 | Test page HTML</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>No VAT here</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
code/src/test/resources/html/simple/index.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Test page HTML</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>A valid VAT number is here</h1>
|
||||||
|
<p>BE0542.703.815</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
code/src/test/resources/html/wrong/wrong-checksum.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Test page HTML</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>An invalid VAT number is here</h1>
|
||||||
|
<p>The checksum is not valid</p>
|
||||||
|
<p>BE0542.703.812</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
code/src/test/resources/html/wrong/wrong-format.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Test page HTML</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>An invalid VAT number is here</h1>
|
||||||
|
<p>The format is invalid: there must be a zero just after BE</p>
|
||||||
|
<p>BE5542.703.815</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
code/src/test/resources/json/log.json
Normal file
1
code/src/test/resources/json/sth.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"tree_size":3,"timestamp":1583354829162,"sha256_root_hash":"Kv/JaTq/Hn6yFwoReB7pd3aSP9/2swNE4vbUyOoJJvc=","tree_head_signature":"BAMASDBGAiEAyR0cUspkmmbg1c4RA+efexzBwaaF6ylMC0ttzn2u2/ICIQD+jUW3TObnvUt4iEQC3ch8FhEUtXS5plYMCEjevObcyw=="}
|
BIN
doc/programmerguide/Guide_du_programmeur.pdf
Normal file
498
doc/programmerguide/source/img/logo_.svg
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="735.51678mm"
|
||||||
|
height="175.31902mm"
|
||||||
|
viewBox="0 0 2606.1618 621.20911"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="horizontal_gris.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<clipPath
|
||||||
|
id="clipPath18"
|
||||||
|
clipPathUnits="userSpaceOnUse">
|
||||||
|
<path
|
||||||
|
id="path20"
|
||||||
|
d="m 0,1190.551 841.89,0 L 841.89,0 0,0 0,1190.551 Z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient132"
|
||||||
|
spreadMethod="pad"
|
||||||
|
gradientTransform="matrix(148.81982,0,0,-148.81982,417.40088,681.375)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
y2="0"
|
||||||
|
x2="1"
|
||||||
|
y1="0"
|
||||||
|
x1="0">
|
||||||
|
<stop
|
||||||
|
id="stop134"
|
||||||
|
offset="0"
|
||||||
|
style="stop-opacity:1;stop-color:#ada9ab" />
|
||||||
|
<stop
|
||||||
|
id="stop136"
|
||||||
|
offset="0.225006"
|
||||||
|
style="stop-opacity:1;stop-color:#ffffff" />
|
||||||
|
<stop
|
||||||
|
id="stop138"
|
||||||
|
offset="1"
|
||||||
|
style="stop-opacity:1;stop-color:#ffffff" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient154"
|
||||||
|
spreadMethod="pad"
|
||||||
|
gradientTransform="matrix(148.82031,0,0,-148.82031,258.65869,681.37598)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
y2="0"
|
||||||
|
x2="1"
|
||||||
|
y1="0"
|
||||||
|
x1="0">
|
||||||
|
<stop
|
||||||
|
id="stop156"
|
||||||
|
offset="0"
|
||||||
|
style="stop-opacity:1;stop-color:#ffffff" />
|
||||||
|
<stop
|
||||||
|
id="stop158"
|
||||||
|
offset="1"
|
||||||
|
style="stop-opacity:1;stop-color:#ada9ab" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
y2="0"
|
||||||
|
x2="1"
|
||||||
|
y1="0"
|
||||||
|
x1="0"
|
||||||
|
spreadMethod="pad"
|
||||||
|
gradientTransform="matrix(148.81982,0,0,-148.81982,417.40088,681.375)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="linearGradient3063"
|
||||||
|
xlink:href="#linearGradient132"
|
||||||
|
inkscape:collect="always" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient132"
|
||||||
|
id="linearGradient4539"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(148.81982,0,0,-148.81982,417.40088,681.375)"
|
||||||
|
spreadMethod="pad"
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.24748737"
|
||||||
|
inkscape:cx="456.8503"
|
||||||
|
inkscape:cy="251.57711"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:window-width="1855"
|
||||||
|
inkscape:window-height="1176"
|
||||||
|
inkscape:window-x="65"
|
||||||
|
inkscape:window-y="24"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-16.188393,545.42097"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide4715" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="730.31432,176.46276"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4717" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="981.4026,473.08417"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4719" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="2606.1676,422.93998"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4721" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-17.801346,-224.09526)">
|
||||||
|
<g
|
||||||
|
id="g4723"
|
||||||
|
transform="matrix(3.055036,0,0,3.055036,-1451.7076,-3736.4861)">
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1035.605,1321.2135)"
|
||||||
|
id="g30">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path32"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 32.99,0 0,-9.709 -20.41,0 0,-12.422 20.473,0 0,-9.713 -20.473,0 0,-14.011 20.459,0 0,-9.711 L 0,-55.566 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,802.58505,1321.2135)"
|
||||||
|
id="g34">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path36"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 13.057,0 0,-37.496 c 0,-5.572 2.068,-8.994 7.323,-8.994 5.255,0 7.323,3.422 7.323,8.994 l 0,37.496 13.056,0 0,-35.424 c 0,-13.777 -7.88,-21.099 -20.379,-21.099 C 7.881,-56.523 0,-49.201 0,-35.424 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,868.65805,1321.2135)"
|
||||||
|
id="g38">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path40"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 15.283,0 16.163,-38.531 0.16,0 0,38.531 11.464,0 0,-55.566 -15.444,0 -16.003,40.203 -0.159,0 0,-40.203 L 0,-55.566 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path42"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 938.31342,1321.2132 16.32125,0 0,69.4575 -16.32125,0 0,-69.4575 z" />
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,964.9813,1321.2135)"
|
||||||
|
id="g44">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path46"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 14.015,0 9.791,-40.523 0.159,0 L 33.995,0 47.369,0 30.57,-55.566 l -14.009,0 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1107.0155,1350.8643)"
|
||||||
|
id="g48">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path50"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 4.062,0 c 5.017,0 7.646,3.342 7.646,7.32 0,2.309 -0.72,7.008 -7.727,7.008 L 0,14.328 0,0 Z m -12.573,23.721 20.378,0 c 9.155,0 16.957,-4.059 16.957,-14.328 0,-2.309 -0.32,-11.465 -11.066,-13.614 l 0,-0.16 c 4.062,-0.478 5.655,-2.705 7.723,-9.316 l 5.73,-18.149 -13.693,0 -4.298,14.885 c -2.147,7.565 -3.899,7.565 -8.677,7.565 l 0,-22.45 -13.054,0 0,55.567 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1191.1013,1335.3418)"
|
||||||
|
id="g52">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path54"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c -3.103,1.594 -7.479,2.865 -10.985,2.865 -4.217,0 -7.402,-1.91 -7.402,-6.049 0,-10.109 21.891,-5.414 21.891,-25.158 0,-10.512 -8.357,-16.879 -19.424,-16.879 -6.768,0 -12.577,1.592 -14.966,2.309 l 0.716,10.83 c 3.9,-1.516 7.324,-3.426 12.181,-3.426 4.139,0 8.12,2.072 8.12,6.61 0,10.746 -21.895,5.494 -21.895,25.312 0,1.676 0.638,15.846 19.266,15.846 5.097,0 8.283,-0.877 13.06,-1.912 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path56"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 1208.6497,1321.2132 16.3187,0 0,69.4575 -16.3187,0 0,-69.4575 z" />
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1251.2392,1333.7501)"
|
||||||
|
id="g58">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path60"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 -12.104,0 0,10.029 37.261,0 0,-10.029 -12.102,0 0,-45.537 L 0,-45.537 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1292.7652,1321.2135)"
|
||||||
|
id="g62">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path64"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 32.991,0 0,-9.709 -20.412,0 0,-12.422 20.475,0 0,-9.713 -20.475,0 0,-14.011 20.461,0 0,-9.711 -33.04,0 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
style="fill:#69be28;fill-opacity:1"
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,818.90467,1465.0916)"
|
||||||
|
id="g66">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path68"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 4.142,0 c 9.472,0 13.691,7.484 13.691,17.594 0,11.465 -4.379,18.549 -14.488,18.549 L 0,36.143 0,0 Z m -13.056,45.857 19.743,0 c 12.499,0 24.842,-7.326 24.842,-28.263 0,-19.188 -12.501,-27.307 -26.514,-27.307 l -18.071,0 0,55.57 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
style="fill:#69be28;fill-opacity:1"
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,868.66054,1407.7698)"
|
||||||
|
id="g70">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path72"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 32.086,0 0,-9.715 -19.507,0 0,-12.418 18.549,0 0,-9.713 -18.549,0 0,-14.011 20.462,0 0,-9.713 L 0,-55.57 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,985.84555,1407.7698)"
|
||||||
|
id="g74">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path76"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 15.287,0 16.159,-38.531 0.16,0 0,38.531 11.462,0 0,-55.57 -15.443,0 -15.999,40.205 -0.16,0 0,-40.205 L 0,-55.57 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1084.1397,1421.1072)"
|
||||||
|
id="g78">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path80"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 -0.159,0 -6.606,-23.086 12.498,0 L 0,0 Z m 8.279,-32.48 -17.433,0 -3.822,-12.42 -12.577,0 18.47,55.57 14.333,0 17.827,-55.57 -13.372,0 -3.426,12.42 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1185.0258,1422.3011)"
|
||||||
|
id="g82">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path84"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 -0.163,0 -12.498,-43.945 -8.598,0 L -33.28,0 l -0.165,0 0,-43.945 -11.937,0 0,55.57 19.66,0 9.001,-35.748 0.159,0 9.711,35.748 18.789,0 0,-55.57 L 0,-43.945 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1216.4407,1407.7698)"
|
||||||
|
id="g86">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path88"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 13.055,0 0,-37.496 c 0,-5.576 2.067,-8.998 7.324,-8.998 5.256,0 7.324,3.422 7.324,8.998 l 0,37.496 13.057,0 0,-35.432 c 0,-13.767 -7.881,-21.093 -20.381,-21.093 C 7.881,-56.525 0,-49.199 0,-35.432 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1300.1357,1437.4256)"
|
||||||
|
id="g90">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path92"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 4.062,0 c 5.015,0 7.641,3.344 7.641,7.326 0,2.307 -0.718,7.002 -7.724,7.002 L 0,14.328 0,0 Z m -12.579,23.725 20.385,0 c 9.154,0 16.955,-4.061 16.955,-14.332 0,-2.309 -0.32,-11.465 -11.067,-13.614 l 0,-0.16 c 4.061,-0.476 5.656,-2.709 7.723,-9.312 l 5.732,-18.153 -13.693,0 -4.299,14.889 c -2.149,7.561 -3.9,7.561 -8.678,7.561 l 0,-22.45 -13.058,0 0,55.571 z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path94"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 1292.8197,1296.4107 41.2625,0 0,12.4012 -41.2625,0 0,-12.4012 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g4688"
|
||||||
|
transform="matrix(1.3744138,0,0,1.3744138,-1085.2821,-902.41136)">
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
d="m 1062.1322,952.65932 c 0,-43.02375 -34.8775,-77.90125 -77.89878,-77.90125 l -181.64875,0 0,268.75753 c 0,43.0237 34.75375,77.9037 77.77875,77.9037 l 103.87,0 c 0,0 63.11998,-3.2987 77.89878,50.1888 l 0,-318.94878 z"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
id="path24"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1074.5345,952.65932)"
|
||||||
|
id="g26">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path28"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c 0,34.419 27.902,62.321 62.319,62.321 l 145.319,0 0,-215.006 c 0,-34.419 -27.804,-62.323 -62.223,-62.323 l -83.096,0 c 0,0 -50.496,2.639 -62.319,-40.151 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1334.0823,1137.9145)"
|
||||||
|
id="g96">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path98"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c -16.391,20.003 -26.234,45.578 -26.234,73.456 0,27.877 9.842,53.448 26.234,73.45 l 0,18.771 c -24.02,-23.38 -38.947,-56.055 -38.947,-92.222 0,-35.481 14.371,-67.599 37.597,-90.88 C -0.471,-13.248 0,-8.92 0,-4.481 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1211.2283,900.0727)"
|
||||||
|
id="g100">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path102"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c 8.567,-10.133 20.848,-6.513 35.058,-12.858 10.157,-4.544 10.116,-14.222 16.508,-25.126 3.342,-5.681 7.633,-10.559 12.303,-14.935 1.436,2.774 2.957,5.497 4.569,8.16 -9.907,12.58 -9.933,28.571 -24.113,38.548 -5.887,4.15 -11.225,4.696 -20.274,10.345 16.172,-3.407 29.166,-1.68 41.527,-12.6 6.584,-5.828 9.285,-14.867 13.895,-20.626 2.181,2.686 4.457,5.29 6.828,7.806 -7.838,12.871 -14,28.046 -26.833,34.206 -11.326,5.436 -28.096,-2.97 -42.285,2.302 -2.535,0.941 -4.454,2.745 -6.089,5.03 l -21.176,0 C -7.554,13.229 -5.117,6.059 0,0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1219.6779,1039.8481)"
|
||||||
|
id="g104">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path106"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="M 0,0 18.524,10.663 C 27.227,9.825 34.687,4.158 41.774,2.17 41.953,5.66 42.26,9.117 42.69,12.535 28.834,18.322 14.967,26.926 1.01,24.37 -11.351,22.112 -19.973,5.453 -34.556,1.379 c -9.553,-2.666 -19.224,8.859 -31.23,10.132 -16.737,1.788 -26.443,-8.678 -41.832,-16.509 10.854,0.893 15.588,4.029 22.918,4.166 18.509,0.349 28.132,-16.009 45.188,-20.205 12.894,-3.165 20.695,6.982 35.932,10.208 10.885,2.287 16.538,-5.558 28.116,-10.616 6.022,-2.631 12.325,-4.047 18.651,-4.851 -0.468,3.068 -0.826,6.172 -1.089,9.302 C 26.741,-12.633 17.314,0.262 0,0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1102.4269,1157.8315)"
|
||||||
|
id="g108">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path110"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 146.576,34.475 c -3.252,7.605 -5.851,15.558 -7.721,23.788 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1259.2752,1174.2457)"
|
||||||
|
id="g112">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path114"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c -5.744,-4.315 -7.926,-9.239 -16.084,-16.099 8.23,14.328 10.611,27.216 24.816,35.606 7.52,4.428 16.876,4.239 23.773,6.79 -1.892,2.9 -3.681,5.872 -5.359,8.916 -14.611,-3.448 -30.867,-4.649 -40.648,-14.903 -8.669,-9.087 -5.852,-27.639 -15.247,-39.514 -6.165,-7.77 -20.769,-4.135 -31.228,-10.164 -4.113,-2.365 -7.202,-5.19 -9.694,-8.371 l 54.554,0 c 8.099,6.033 9.607,16.896 18.593,26.865 7.456,8.254 16.649,5.225 28.987,7.936 6.43,1.42 12.377,3.979 17.976,7.062 -2.205,2.219 -4.335,4.508 -6.386,6.87 C 29.061,5.494 13.858,10.372 0,0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1198.7575,957.01607)"
|
||||||
|
id="g116">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path118"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 0,-14.211 61.83,-25.944 c 1.883,8.231 4.494,16.182 7.762,23.787 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.25,0,0,-1.25,552.78342,1909.4007)"
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
id="g120">
|
||||||
|
<g
|
||||||
|
id="g122">
|
||||||
|
<g
|
||||||
|
id="g128">
|
||||||
|
<g
|
||||||
|
id="g130">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path140"
|
||||||
|
style="fill:url(#linearGradient4539);stroke:none"
|
||||||
|
d="m 417.401,765.808 0,-74.132 c 5.042,23.082 23.477,41.205 48.751,41.205 l 0,0 c 31.129,0 50.461,-23.925 50.461,-54.069 l 0,0 0,-94.17 25.28,0 c 13.967,0.107 24.328,9.298 24.328,25.583 l 0,0 0,71.205 c 0,55.716 -45.341,96.678 -100.069,96.678 l 0,0 c -17.612,0 -34.274,-4.44 -48.751,-12.3" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
transform="matrix(-1.25,0,0,1.25,1583.8913,205.95708)"
|
||||||
|
id="g3053">
|
||||||
|
<g
|
||||||
|
id="g3055">
|
||||||
|
<g
|
||||||
|
id="g3057">
|
||||||
|
<g
|
||||||
|
id="g3059">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 417.401,765.808 0,-74.132 c 5.042,23.082 23.477,41.205 48.751,41.205 l 0,0 c 31.129,0 50.461,-23.925 50.461,-54.069 l 0,0 0,-94.17 25.28,0 c 13.967,0.107 24.328,9.298 24.328,25.583 l 0,0 0,71.205 c 0,55.716 -45.341,96.678 -100.069,96.678 l 0,0 c -17.612,0 -34.274,-4.44 -48.751,-12.3"
|
||||||
|
style="fill:url(#linearGradient3063);stroke:none"
|
||||||
|
id="path3061" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 22 KiB |
357
doc/programmerguide/source/main.tex
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
\documentclass{article}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[french]{babel}
|
||||||
|
\usepackage[hyphens]{url}
|
||||||
|
\usepackage{lmodern}
|
||||||
|
\usepackage[top=4cm, bottom=4cm, left=4cm, right=4cm]{geometry}
|
||||||
|
\usepackage{minted}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{svg}
|
||||||
|
\usepackage{dirtree}
|
||||||
|
\usepackage[hidelinks]{hyperref}
|
||||||
|
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\begin{titlepage}
|
||||||
|
|
||||||
|
\newcommand{\HRule}{\rule{\linewidth}{0.5mm}}
|
||||||
|
\center
|
||||||
|
\HRule \\[0.4cm]
|
||||||
|
{\huge \bfseries Guide du programmeur\\[0.4cm] CT-Application}\\[0.4cm]
|
||||||
|
\HRule \\[1.5cm]
|
||||||
|
|
||||||
|
|
||||||
|
\begin{minipage}{0.4\textwidth}
|
||||||
|
\begin{flushleft} \large
|
||||||
|
\emph{Auteur:}\\
|
||||||
|
Jules \textsc{Dejaeghere}
|
||||||
|
\end{flushleft}
|
||||||
|
\end{minipage}
|
||||||
|
~
|
||||||
|
\begin{minipage}{0.4\textwidth}
|
||||||
|
\begin{flushright} \large
|
||||||
|
\emph{Promoteur:} \\
|
||||||
|
Pr Jean-Noël \textsc{Colin}
|
||||||
|
\end{flushright}
|
||||||
|
\end{minipage}\\[2cm]
|
||||||
|
|
||||||
|
|
||||||
|
{\large
|
||||||
|
Version 1.0.0\\
|
||||||
|
\medskip
|
||||||
|
Année académique: 2019-2020}\\[2cm]
|
||||||
|
|
||||||
|
\includesvg[height=2cm]{img/logo_.svg}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
\end{titlepage}
|
||||||
|
|
||||||
|
|
||||||
|
\tableofcontents
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\section{Introduction}
|
||||||
|
|
||||||
|
Ce document constitue la documentation technique de l'application développée par Jules \textsc{Dejaeghere} dans le cadre du cours \emph{INFOB318 - Projet individuel}, dispensé à l'Université de Namur par le Professeur Vincent \textsc{Englebert} au cours de l'année académique 2019-2020. Ce projet a été proposé par le Professeur Jean-Noël \textsc{Colin}.
|
||||||
|
|
||||||
|
Ce document vise à fournir une description du contexte de développement de l'application ainsi que les clefs de l'architecture du logiciel pour permettre sa maintenance, son évolution ou sa reprise.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Description conceptuelle}
|
||||||
|
|
||||||
|
La description du projet fournie par le Professeur \textsc{Colin} est la suivante.
|
||||||
|
|
||||||
|
\begin{quotation}
|
||||||
|
L'objectif du projet est de constituer une base de données de noms de domaine enrichie d'informations disponibles publiquement ou calculées à partir de données publiques. Le workflow partira de la base de données \emph{Certificate Transparency Logs} (\url{https://www.certificate-transparency.org/what-is-ct}), et complétera avec les éléments suivants, à extraire des sources appropriées:
|
||||||
|
\begin{itemize}
|
||||||
|
\item comparaison des certificats de CT avec ceux réellement utilisés sur le site web
|
||||||
|
\item statistiques sur les données des certificats (palmarès des CA, types de certificats...)
|
||||||
|
\item validation du numéro de TVA du registrant
|
||||||
|
\item extraction des données personnelles et adresses du site web
|
||||||
|
\end{itemize}
|
||||||
|
Ces données seront ensuite intégrées à un outil utilisant l'IA pour détecter des noms de domaine malicieux et en bloquer l'accès aussi vite que possible. Le développement sera réalisé en Java et la base de données sera PostgreSQL.
|
||||||
|
\end{quotation}
|
||||||
|
|
||||||
|
Cette description ainsi que les différents échanges avec le Professeur au cours du projet ont constitué les lignes directrices du développement de l'application.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Contexte du développement}
|
||||||
|
|
||||||
|
L'application a été développée par Jules \textsc{Dejaeghere}, étudiant en bachelier en informatique à l'Université de Namur, entre septembre 2019 et mars 2020. Un des objectifs lors du développement de l'application était d'arriver à produire une application de taille raisonnable mais fonctionnelle plutôt qu'une application plus ambitieuse et non fonctionnelle. Cette application peut donc être améliorée par l'ajout de nouvelles fonctionnalités ou des considérations techniques plus poussées. Plusieurs pistes d'amélioration de l'application seront proposées au cours de ce document. Ces pistes constituent un point de départ intéressant dans le cadre de la poursuite du développement.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Sources et documentation de l'application}
|
||||||
|
|
||||||
|
Actuellement, le code source de l'application est hébergé sur un dépôt GitHub géré par la Faculté d'Informatique de l'Université de Namur. Ce dépôt est actuellement privé et se trouve à l'adresse suivante: \url{https://github.com/UNamurCSFaculty/1920_INFOB318_CT}.
|
||||||
|
|
||||||
|
Ce dépôt contient les fichiers source de l'application ainsi que la documentation technique et de l'utilisateur. Une version compilée du logiciel ainsi que le planning du projet se trouvent également dans le dépôt.
|
||||||
|
|
||||||
|
Ce document reprend la documentation technique uniquement. Pour déployer l'application et utiliser les fonctionnalités de cette dernière, le \emph{Guide de l'utilisateur} détaille les différentes étapes à suivre.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Environnement de développement}
|
||||||
|
|
||||||
|
L'application est développée en Java, comme mentionné dans la description du projet. Pour supporter le développement et sur conseil du Professeur \textsc{Colin}, le framework Spring Boot a été utilisé.
|
||||||
|
|
||||||
|
L'environnement de développement de JetBrains pour Java, \emph{IntelliJ IDEA 2019.3.3 (Ultimate Edition)}, a permis de faire fonctionner les différents outils utilisés lors du développement de l'application: Maven pour gérer le cycle de vie, JUnit pour développer des tests unitaires ainsi que le framework Spring Boot.
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Compiler l'application}
|
||||||
|
|
||||||
|
La compilation et la gestion des dépendances est prise en charge par Maven. Grâce à IntelliJ, il est possible de lancer la compilation directement depuis l'environnement de développement, à l'aide du module Maven, et de faire abstraction des commandes exécutées par Maven.
|
||||||
|
|
||||||
|
Il est également possible de compiler l'application grâce aux commandes Maven. Une fois dans le répertoire contenant le fichier \path{pom.xml}, exécuter les commandes suivantes:
|
||||||
|
|
||||||
|
\begin{minted}{bash}
|
||||||
|
mvn clean
|
||||||
|
mvn install
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Dans les deux cas, l'application compilée avec les dépendances se trouve dans le répertoire \path{target}.
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Exécuter les tests unitaires}
|
||||||
|
|
||||||
|
Au cours du développement de l'application, plusieurs classes de test ont été développées. L'outil utilisé pour les tests unitaires est JUnit. Tout comme Maven, il est possible de l'utiliser directement depuis l'environnement de développement pour lancer des tests unitaires. En sélectionnant un fichier de test, il est possible de le lancer directement depuis IntelliJ.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Développement}
|
||||||
|
|
||||||
|
\subsection{Fonctionnement général de l'application et librairies utilisées}
|
||||||
|
|
||||||
|
Au cours de son exécution, l'application télécharge des entrées de logs depuis des serveurs de logs du projet Certificate Transparency, décode les logs, parcourt les sites web repris dans les certificats à la recherche d'un numéro de TVA et sauvegarde les données dans la base de données. Ces différentes étapes seront expliquées dans les prochaines parties.
|
||||||
|
|
||||||
|
Pour appuyer les explications suivantes, la figure \ref{structure-fig} présente l'agencement des paquets et des fichiers dans le package \path{be.unamur.ct}. Les paquets sont découpés de manière à refléter le fonctionnement de l'application. Chaque paquet principal est décomposé en sous-paquets pour y répartir les classes en fonction de leur utilité.
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\item[\path{exceptions}:] contient les exceptions qui concernent le paquet en question
|
||||||
|
\item[\path{model}:] contient les définitions d'objets Java utilisées dans le paquet
|
||||||
|
\item[\path{service}:] contient les fonctions principales de l'application
|
||||||
|
\item[\path{thread}:] contient la définition des threads que le paquet peut soumettre à des Executors
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\dirtree{%
|
||||||
|
.1 be.unamur.ct.
|
||||||
|
.2 CtApplication.java.
|
||||||
|
.2 data.
|
||||||
|
.3 dao.
|
||||||
|
.4 CertificateDao.java.
|
||||||
|
.4 ServerDao.java.
|
||||||
|
.4 SliceDao.java.
|
||||||
|
.3 service.
|
||||||
|
.4 CertificateService.java.
|
||||||
|
.2 decode.
|
||||||
|
.3 exceptions.
|
||||||
|
.4 NotAValidDomainException.java.
|
||||||
|
.3 model.
|
||||||
|
.4 Certificate.java.
|
||||||
|
.3 service.
|
||||||
|
.4 DecodeService.java.
|
||||||
|
.3 thread.
|
||||||
|
.4 DecodeEntryThread.java.
|
||||||
|
.2 download.
|
||||||
|
.3 model.
|
||||||
|
.4 LogEntry.java.
|
||||||
|
.4 LogList.java.
|
||||||
|
.4 Server.java.
|
||||||
|
.4 Slice.java.
|
||||||
|
.3 service.
|
||||||
|
.4 ServerService.java.
|
||||||
|
.3 thread.
|
||||||
|
.4 ScanLogThread.java.
|
||||||
|
.4 SearchSliceThread.java.
|
||||||
|
.2 scrap.
|
||||||
|
.3 service.
|
||||||
|
.4 VATScrapper.java.
|
||||||
|
.3 thread.
|
||||||
|
.4 ResumeVATScrapThread.java.
|
||||||
|
.4 VATScrapperThread.java.
|
||||||
|
.2 thread.
|
||||||
|
.3 ThreadPool.java.
|
||||||
|
.2 web.
|
||||||
|
.3 controller.
|
||||||
|
.4 WebController.java.
|
||||||
|
}
|
||||||
|
\caption{Arborescence des paquets et des fichiers source}
|
||||||
|
\label{structure-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Télécharger les logs}
|
||||||
|
|
||||||
|
La première étape lors de l'exécution du programme est de télécharger les logs depuis un serveur du projet Certificate Transparency. Ces logs sont téléchargeables via une API qui fournit des données au format JSON.
|
||||||
|
|
||||||
|
Comprendre la manière dont les serveurs de logs fonctionnent n'est pas trivial. Pour y arriver, plusieurs ressources ont été utiles. La première n'est autre que le site de Certificate Transparency (\url{https://www.certificate-transparency.org}). Il permet d'avoir un aperçu général du projet mais propose également une liste des serveurs de log connus. La seconde est un article du site Medium: \emph{Parsing Certificate Transparency Logs Like a Boss} (\url{https://medium.com/cali-dog-security/parsing-certificate-transparency-lists-like-a-boss-981716dc506}). L'article explique en détail comment sont organisés les logs et comment y accéder. De plus, l'article détaille la manière dont il est possible d'extraire les informations des logs téléchargés. Bien que l'auteur illustre ses propos par des scripts Python, il est facile de le transposer en Java.
|
||||||
|
|
||||||
|
Le contenu de paquet \path{download} contient les méthodes nécessaires pour connaître la taille d'un log, découper ces logs en tranches pour les télécharger parallèlement et effectivement télécharger les logs. L'implémentation de ces méthodes s'inspire de l'article présenté précédemment.
|
||||||
|
|
||||||
|
Pour obtenir ces données, la librairie \emph{OkHttp3} est utilisée pour se connecter au serveur et effectuer les requêtes. Une fois ces données au format JSON téléchargées, elles sont temporairement stockées dans un objet à l'aide de la librairie \emph{Jackson}. Les données téléchargées sont encodées en Base64 et devront être décodées pour être utilisables.
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Décoder les logs}
|
||||||
|
|
||||||
|
Une fois téléchargés, les logs doivent être décodés et triés. En effet, les logs sont téléchargés, encodés en Base64, et seuls les logs concernant des certificats belges nous intéressent dans notre cas. Le paquet \path{decode} contient différentes méthodes qui permettent de décoder les données téléchargées et les enregistrer, si elles sont pertinentes, dans un objet Java.
|
||||||
|
|
||||||
|
Ce paquet contient une classe, \mintinline{text}{Certificate}, qui sera utilisée pour stocker les certificats pertinents dans la base de données. Chaque certificat téléchargé sera converti en un objet \mintinline{text}{Certificate} avant d'être enregistré ou abandonné. Si le programme n'arrive pas à construire un objet de ce type sur base du log reçu, il sera abandonné.
|
||||||
|
|
||||||
|
La classe \mintinline{text}{Certificate} est également utilisée par Spring Boot pour stocker les objets dans la base de données.
|
||||||
|
|
||||||
|
Pour décoder les certificats et en extraire les informations nécessaires, la librairie Bouncy Castle a été utilisée.
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering
|
||||||
|
\begin{minted}{java}
|
||||||
|
public class Certificate {
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private String subject;
|
||||||
|
|
||||||
|
private String issuer;
|
||||||
|
private Date notAfter;
|
||||||
|
private Date notBefore;
|
||||||
|
|
||||||
|
private String signatureAlg;
|
||||||
|
private int versionNumber;
|
||||||
|
private String VAT;
|
||||||
|
private boolean vatSearched;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Variables de la classe \mintinline{text}{Certificate}}
|
||||||
|
\label{cert-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Rechercher le numéro de TVA}
|
||||||
|
|
||||||
|
Une fois les certificats belges identifiés et sauvegardés, ceux-ci sont traités à la recherche d'un numéro de TVA sur le site lié au certificat. Le paquet \path{scrap} regroupe toutes les méthodes qui se chargent d'explorer le site web lié à un certificat à la recherche d'un numéro de TVA.
|
||||||
|
|
||||||
|
Pour explorer le site, la méthode principale démarre de l'URL qui est renseignée dans le certificat et parcourt la page. Si aucun numéro de TVA n'est trouvé sur la page, les liens présents sur la page pointant vers le même domaine sont parcourus récursivement de la même manière. Pour casser la récursivité, une limite de profondeur est fournie à la fonction. De même, un ensemble d'URL déjà visitées est tenu à jour de manière à ne pas visiter plusieurs fois la même page.
|
||||||
|
|
||||||
|
Pour reconnaître un numéro de TVA dans une page web, l'application utilise l'expression régulière de la figure \ref{tva-fig}. Le résultat est ensuite passé à une fonction qui se charge de normaliser les numéros de TVA avant de les enregistrer dans la base de données.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\centering
|
||||||
|
\mintinline{java}{"(?i)((BE)?0([. -])?[0-9]\{3\}([. -])?[0-9]\{3\}([. -])?[0-9]\{3\})"}
|
||||||
|
\caption{Expression régulière du numéro de TVA}
|
||||||
|
\label{tva-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Paquets additionnels}
|
||||||
|
|
||||||
|
En plus des paquets déjà présentés, l'application comporte plusieurs paquets qui assurent des fonctions auxiliaires: gérer la persistance des données, gérer le mutli-threading ou encore proposer l'interface web.
|
||||||
|
|
||||||
|
Ces paquets sont décrits dans les sections suivantes.
|
||||||
|
|
||||||
|
\subsubsection{Persistance des données}
|
||||||
|
|
||||||
|
Le paquet \path{data} prend en charge toutes les fonctions relatives à la persistance des données ainsi que certaines fonctions d'agrégation des données pour permettre leur affichage sur l'interface web.
|
||||||
|
|
||||||
|
Ce paquet contient notamment trois interfaces qui étendent la classe \mintinline{java}{JpaRepository}. Ces interfaces sont une abstraction permise par Spring Boot pour communiquer avec la base de données. Cela permet notamment de récupérer les données persistantes à l'aide de méthodes Java, sans toujours nécessiter l'écriture de requêtes SQL.
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Multi-threading}
|
||||||
|
|
||||||
|
Pour permettre à l'application d'utiliser au mieux les ressources physiques de la machine, la classe \path{thread} fournit des méthodes qui permettent à l'application de s'exécuter en plusieurs threads.
|
||||||
|
|
||||||
|
La classe contenue dans ce paquet est une implémentation étendue du pattern design singleton. En effet, cette classe garantit l'existence unique de quatre objets de type \mintinline{java}{ExecutorService}. Un \mintinline{java}{ExecutorService} permet une abstraction quant à l'implémentation d'une file de threads à exécuter. Quatre files de threads existent dans l'application, chacune avec une fonction spécifique, et peuvent être appelées par d'autres classes pour effectuer des tâches lorsqu'un thread se libère. En utilisant des objets de la classe \mintinline{java}{ExecutorService}, cela permet d'éviter de créer un nombre trop important de threads.
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Interface web}
|
||||||
|
|
||||||
|
Le paquet \path{web.controller} implémente les méthodes nécessaires pour fournir l'interface web. Cette interface web a été réalisée à l'aide de Thymeleaf, qui propose une intégration avec Spring Boot. L'interface web fournit principalement des informations relatives à l'état acutel du programme, des statistiques relatives aux données stockées et la possibilité d'ajouter de nouveaux serveurs de logs à consulter.
|
||||||
|
|
||||||
|
Les modèles HTML sont stockés dans le répertoire \path{src/main/ressources/templates}.
|
||||||
|
|
||||||
|
|
||||||
|
\section{Poursuivre le développement}
|
||||||
|
|
||||||
|
Dans l'optique d'une poursuite du développement de l'application, le présent document constitue un point de départ pour comprendre l'agencement des différentes parties du programme. Cette section propose plusieurs pistes pour entamer la poursuite du développement de l'application.
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Améliorer le code existant}
|
||||||
|
|
||||||
|
Pendant le développement de l'application, à plusieurs reprises, un choix entre rapidité d'implémentation et efficacité a dû être posé. Peut-être qu'un regard différent sur ces parties de l'application ou un peu plus de temps permettront de trouver une solution plus efficace et plus élégante.
|
||||||
|
|
||||||
|
Les points où de tels choix ont dû être posés et qui ne semblent pas satisfaisants ont été annotés d'un commentaire \mintinline{java}{// TODO}, suivi d'une brève description du problème.
|
||||||
|
|
||||||
|
\subsubsection{Gestion du multi-threading}
|
||||||
|
|
||||||
|
La première partie où une amélioration est la bienvenue porte sur l'arrêt des threads en charge de la recherche du numéro de TVA sur les sites web contenus dans les certificats. En effet, après observations, les threads ne semblent pas s'arrêter lorsque la méthode \mintinline{java}{interrupt()} du thread est appelée.
|
||||||
|
|
||||||
|
Pour palier à ce problème, la solution implémentée consiste, lors de chaque entrée dans la fonction récursive principale du thread, à vérifier si l'objet \mintinline{java}{ExecutorService} dont est supposé dépendre ce thread n'a pas reçu de demande d'arrêt. Si une demande d'arrêt à été émise pour cet objet, le thread sera alors interrompu. Cela pourrait poser problème dans le cas où la méthode est lancée par un thread qui ne dépend pas de cet objet \mintinline{java}{ExecutorService}. Le code problématique se trouve dans le fichier \path{be/unamur/ct/scrap/service/VATScrapper.java} est repris dans la figure \ref{thread-fig}.
|
||||||
|
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering
|
||||||
|
\begin{minted}{java}
|
||||||
|
/* TODO:
|
||||||
|
* Find a better way to stop the thread
|
||||||
|
* The Thread.interrupt() method doesn't seem to work
|
||||||
|
*/
|
||||||
|
if (threadPool.getVATScrapperExecutor().isShutdown()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Arrêt manuel du thread}
|
||||||
|
\label{thread-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsubsection{Recherche de l'autorité de certification racine}
|
||||||
|
|
||||||
|
La seconde partie qui pourrait faire l'objet d'une amélioration concerne la recherche de l'autorité de certification racine dans une entrée du log. Dans l'entrée d'un log, la chaîne de confiance depuis le certificat concerné jusqu'à l'autorité de certification racine doit être présente. Cependant, aucun autre moyen n'a été trouvé pour extraire le certificat de l'autorité de certification racine que de parcourir cette chaîne de confiance, octet après octet, à partir de la fin, en tentant de lire un certificat après chaque décalage d'un octet.
|
||||||
|
|
||||||
|
Cette chaine de confiance se trouve dans la partie nommée \mintinline{java}{extra_data} des données JSON récupérées du serveur. L'itération est donc opérée sur ces données, comme repris dans la figure \ref{ca-fig}.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\centering
|
||||||
|
\begin{minted}[breaklines]{java}
|
||||||
|
/*
|
||||||
|
* TODO:
|
||||||
|
* find a better way to determine the RootCA, the approach described above is probably not the best
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte[] extraBin = Base64.decode(extra_data);
|
||||||
|
int start = extraBin.length - 5;
|
||||||
|
|
||||||
|
while (start >= 0) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get certificate type (X.509 or PreCert)
|
||||||
|
int id = (extraBin[start + 1] & 0xFF) | ((extraBin[start] & 0xFF) << 8);
|
||||||
|
int l = (extraBin[start + 4] & 0xFF) | ((extraBin[start + 3] & 0xFF) << 8) | ((extraBin[start + 2] & 0x0F) << 16);
|
||||||
|
|
||||||
|
byte[] certBin = Arrays.copyOfRange(extraBin, start + 5, l + start + 5);
|
||||||
|
|
||||||
|
try {
|
||||||
|
X509CertificateHolder certX = new X509CertificateHolder(certBin);
|
||||||
|
RDN cn = certX.getSubject().getRDNs(BCStyle.CN)[0];
|
||||||
|
String cns = IETFUtils.valueToString(cn.getFirst().getValue());
|
||||||
|
return cns;
|
||||||
|
} catch (IOException e) {
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn(e.toString());
|
||||||
|
}
|
||||||
|
start--;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\caption{Recherche de l'autorité de certification racine}
|
||||||
|
\label{ca-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsection{Ajout de nouvelles fonctionnalités}
|
||||||
|
|
||||||
|
Toujours dans l'optique de la poursuite du développement de l'application, plusieurs fonctionnalités additionnelles peuvent être envisagées. Des fonctionnalités de recherche plus poussées sur les sites web pourraient être implémentées. Par exemple, une recherche de numéro de téléphone, de localité ou d'adresse.
|
||||||
|
|
||||||
|
|
||||||
|
\end{document}
|
BIN
doc/userguide/Guide_de_l_utilisateur.pdf
Normal file
BIN
doc/userguide/source/img/add-srv.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
doc/userguide/source/img/data.png
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
doc/userguide/source/img/graphs.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
doc/userguide/source/img/home.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
doc/userguide/source/img/logo.png
Normal file
After Width: | Height: | Size: 117 KiB |
498
doc/userguide/source/img/logo_.svg
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="735.51678mm"
|
||||||
|
height="175.31902mm"
|
||||||
|
viewBox="0 0 2606.1618 621.20911"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="horizontal_gris.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<clipPath
|
||||||
|
id="clipPath18"
|
||||||
|
clipPathUnits="userSpaceOnUse">
|
||||||
|
<path
|
||||||
|
id="path20"
|
||||||
|
d="m 0,1190.551 841.89,0 L 841.89,0 0,0 0,1190.551 Z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient132"
|
||||||
|
spreadMethod="pad"
|
||||||
|
gradientTransform="matrix(148.81982,0,0,-148.81982,417.40088,681.375)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
y2="0"
|
||||||
|
x2="1"
|
||||||
|
y1="0"
|
||||||
|
x1="0">
|
||||||
|
<stop
|
||||||
|
id="stop134"
|
||||||
|
offset="0"
|
||||||
|
style="stop-opacity:1;stop-color:#ada9ab" />
|
||||||
|
<stop
|
||||||
|
id="stop136"
|
||||||
|
offset="0.225006"
|
||||||
|
style="stop-opacity:1;stop-color:#ffffff" />
|
||||||
|
<stop
|
||||||
|
id="stop138"
|
||||||
|
offset="1"
|
||||||
|
style="stop-opacity:1;stop-color:#ffffff" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient154"
|
||||||
|
spreadMethod="pad"
|
||||||
|
gradientTransform="matrix(148.82031,0,0,-148.82031,258.65869,681.37598)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
y2="0"
|
||||||
|
x2="1"
|
||||||
|
y1="0"
|
||||||
|
x1="0">
|
||||||
|
<stop
|
||||||
|
id="stop156"
|
||||||
|
offset="0"
|
||||||
|
style="stop-opacity:1;stop-color:#ffffff" />
|
||||||
|
<stop
|
||||||
|
id="stop158"
|
||||||
|
offset="1"
|
||||||
|
style="stop-opacity:1;stop-color:#ada9ab" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
y2="0"
|
||||||
|
x2="1"
|
||||||
|
y1="0"
|
||||||
|
x1="0"
|
||||||
|
spreadMethod="pad"
|
||||||
|
gradientTransform="matrix(148.81982,0,0,-148.81982,417.40088,681.375)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="linearGradient3063"
|
||||||
|
xlink:href="#linearGradient132"
|
||||||
|
inkscape:collect="always" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient132"
|
||||||
|
id="linearGradient4539"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(148.81982,0,0,-148.81982,417.40088,681.375)"
|
||||||
|
spreadMethod="pad"
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.24748737"
|
||||||
|
inkscape:cx="456.8503"
|
||||||
|
inkscape:cy="251.57711"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:window-width="1855"
|
||||||
|
inkscape:window-height="1176"
|
||||||
|
inkscape:window-x="65"
|
||||||
|
inkscape:window-y="24"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-16.188393,545.42097"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide4715" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="730.31432,176.46276"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4717" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="981.4026,473.08417"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4719" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="2606.1676,422.93998"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide4721" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-17.801346,-224.09526)">
|
||||||
|
<g
|
||||||
|
id="g4723"
|
||||||
|
transform="matrix(3.055036,0,0,3.055036,-1451.7076,-3736.4861)">
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1035.605,1321.2135)"
|
||||||
|
id="g30">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path32"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 32.99,0 0,-9.709 -20.41,0 0,-12.422 20.473,0 0,-9.713 -20.473,0 0,-14.011 20.459,0 0,-9.711 L 0,-55.566 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,802.58505,1321.2135)"
|
||||||
|
id="g34">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path36"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 13.057,0 0,-37.496 c 0,-5.572 2.068,-8.994 7.323,-8.994 5.255,0 7.323,3.422 7.323,8.994 l 0,37.496 13.056,0 0,-35.424 c 0,-13.777 -7.88,-21.099 -20.379,-21.099 C 7.881,-56.523 0,-49.201 0,-35.424 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,868.65805,1321.2135)"
|
||||||
|
id="g38">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path40"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 15.283,0 16.163,-38.531 0.16,0 0,38.531 11.464,0 0,-55.566 -15.444,0 -16.003,40.203 -0.159,0 0,-40.203 L 0,-55.566 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path42"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 938.31342,1321.2132 16.32125,0 0,69.4575 -16.32125,0 0,-69.4575 z" />
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,964.9813,1321.2135)"
|
||||||
|
id="g44">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path46"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 14.015,0 9.791,-40.523 0.159,0 L 33.995,0 47.369,0 30.57,-55.566 l -14.009,0 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1107.0155,1350.8643)"
|
||||||
|
id="g48">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path50"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 4.062,0 c 5.017,0 7.646,3.342 7.646,7.32 0,2.309 -0.72,7.008 -7.727,7.008 L 0,14.328 0,0 Z m -12.573,23.721 20.378,0 c 9.155,0 16.957,-4.059 16.957,-14.328 0,-2.309 -0.32,-11.465 -11.066,-13.614 l 0,-0.16 c 4.062,-0.478 5.655,-2.705 7.723,-9.316 l 5.73,-18.149 -13.693,0 -4.298,14.885 c -2.147,7.565 -3.899,7.565 -8.677,7.565 l 0,-22.45 -13.054,0 0,55.567 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1191.1013,1335.3418)"
|
||||||
|
id="g52">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path54"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c -3.103,1.594 -7.479,2.865 -10.985,2.865 -4.217,0 -7.402,-1.91 -7.402,-6.049 0,-10.109 21.891,-5.414 21.891,-25.158 0,-10.512 -8.357,-16.879 -19.424,-16.879 -6.768,0 -12.577,1.592 -14.966,2.309 l 0.716,10.83 c 3.9,-1.516 7.324,-3.426 12.181,-3.426 4.139,0 8.12,2.072 8.12,6.61 0,10.746 -21.895,5.494 -21.895,25.312 0,1.676 0.638,15.846 19.266,15.846 5.097,0 8.283,-0.877 13.06,-1.912 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path56"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 1208.6497,1321.2132 16.3187,0 0,69.4575 -16.3187,0 0,-69.4575 z" />
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1251.2392,1333.7501)"
|
||||||
|
id="g58">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path60"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 -12.104,0 0,10.029 37.261,0 0,-10.029 -12.102,0 0,-45.537 L 0,-45.537 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1292.7652,1321.2135)"
|
||||||
|
id="g62">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path64"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 32.991,0 0,-9.709 -20.412,0 0,-12.422 20.475,0 0,-9.713 -20.475,0 0,-14.011 20.461,0 0,-9.711 -33.04,0 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
style="fill:#69be28;fill-opacity:1"
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,818.90467,1465.0916)"
|
||||||
|
id="g66">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path68"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 4.142,0 c 9.472,0 13.691,7.484 13.691,17.594 0,11.465 -4.379,18.549 -14.488,18.549 L 0,36.143 0,0 Z m -13.056,45.857 19.743,0 c 12.499,0 24.842,-7.326 24.842,-28.263 0,-19.188 -12.501,-27.307 -26.514,-27.307 l -18.071,0 0,55.57 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
style="fill:#69be28;fill-opacity:1"
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,868.66054,1407.7698)"
|
||||||
|
id="g70">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path72"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 32.086,0 0,-9.715 -19.507,0 0,-12.418 18.549,0 0,-9.713 -18.549,0 0,-14.011 20.462,0 0,-9.713 L 0,-55.57 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,985.84555,1407.7698)"
|
||||||
|
id="g74">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path76"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 15.287,0 16.159,-38.531 0.16,0 0,38.531 11.462,0 0,-55.57 -15.443,0 -15.999,40.205 -0.16,0 0,-40.205 L 0,-55.57 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1084.1397,1421.1072)"
|
||||||
|
id="g78">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path80"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 -0.159,0 -6.606,-23.086 12.498,0 L 0,0 Z m 8.279,-32.48 -17.433,0 -3.822,-12.42 -12.577,0 18.47,55.57 14.333,0 17.827,-55.57 -13.372,0 -3.426,12.42 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1185.0258,1422.3011)"
|
||||||
|
id="g82">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path84"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 -0.163,0 -12.498,-43.945 -8.598,0 L -33.28,0 l -0.165,0 0,-43.945 -11.937,0 0,55.57 19.66,0 9.001,-35.748 0.159,0 9.711,35.748 18.789,0 0,-55.57 L 0,-43.945 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1216.4407,1407.7698)"
|
||||||
|
id="g86">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path88"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 13.055,0 0,-37.496 c 0,-5.576 2.067,-8.998 7.324,-8.998 5.256,0 7.324,3.422 7.324,8.998 l 0,37.496 13.057,0 0,-35.432 c 0,-13.767 -7.881,-21.093 -20.381,-21.093 C 7.881,-56.525 0,-49.199 0,-35.432 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1300.1357,1437.4256)"
|
||||||
|
id="g90">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path92"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 4.062,0 c 5.015,0 7.641,3.344 7.641,7.326 0,2.307 -0.718,7.002 -7.724,7.002 L 0,14.328 0,0 Z m -12.579,23.725 20.385,0 c 9.154,0 16.955,-4.061 16.955,-14.332 0,-2.309 -0.32,-11.465 -11.067,-13.614 l 0,-0.16 c 4.061,-0.476 5.656,-2.709 7.723,-9.312 l 5.732,-18.153 -13.693,0 -4.299,14.889 c -2.149,7.561 -3.9,7.561 -8.678,7.561 l 0,-22.45 -13.058,0 0,55.571 z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path94"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 1292.8197,1296.4107 41.2625,0 0,12.4012 -41.2625,0 0,-12.4012 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g4688"
|
||||||
|
transform="matrix(1.3744138,0,0,1.3744138,-1085.2821,-902.41136)">
|
||||||
|
<path
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
d="m 1062.1322,952.65932 c 0,-43.02375 -34.8775,-77.90125 -77.89878,-77.90125 l -181.64875,0 0,268.75753 c 0,43.0237 34.75375,77.9037 77.77875,77.9037 l 103.87,0 c 0,0 63.11998,-3.2987 77.89878,50.1888 l 0,-318.94878 z"
|
||||||
|
style="fill:#69be28;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
id="path24"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1074.5345,952.65932)"
|
||||||
|
id="g26">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path28"
|
||||||
|
style="fill:#696566;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c 0,34.419 27.902,62.321 62.319,62.321 l 145.319,0 0,-215.006 c 0,-34.419 -27.804,-62.323 -62.223,-62.323 l -83.096,0 c 0,0 -50.496,2.639 -62.319,-40.151 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1334.0823,1137.9145)"
|
||||||
|
id="g96">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path98"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c -16.391,20.003 -26.234,45.578 -26.234,73.456 0,27.877 9.842,53.448 26.234,73.45 l 0,18.771 c -24.02,-23.38 -38.947,-56.055 -38.947,-92.222 0,-35.481 14.371,-67.599 37.597,-90.88 C -0.471,-13.248 0,-8.92 0,-4.481 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1211.2283,900.0727)"
|
||||||
|
id="g100">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path102"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c 8.567,-10.133 20.848,-6.513 35.058,-12.858 10.157,-4.544 10.116,-14.222 16.508,-25.126 3.342,-5.681 7.633,-10.559 12.303,-14.935 1.436,2.774 2.957,5.497 4.569,8.16 -9.907,12.58 -9.933,28.571 -24.113,38.548 -5.887,4.15 -11.225,4.696 -20.274,10.345 16.172,-3.407 29.166,-1.68 41.527,-12.6 6.584,-5.828 9.285,-14.867 13.895,-20.626 2.181,2.686 4.457,5.29 6.828,7.806 -7.838,12.871 -14,28.046 -26.833,34.206 -11.326,5.436 -28.096,-2.97 -42.285,2.302 -2.535,0.941 -4.454,2.745 -6.089,5.03 l -21.176,0 C -7.554,13.229 -5.117,6.059 0,0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1219.6779,1039.8481)"
|
||||||
|
id="g104">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path106"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="M 0,0 18.524,10.663 C 27.227,9.825 34.687,4.158 41.774,2.17 41.953,5.66 42.26,9.117 42.69,12.535 28.834,18.322 14.967,26.926 1.01,24.37 -11.351,22.112 -19.973,5.453 -34.556,1.379 c -9.553,-2.666 -19.224,8.859 -31.23,10.132 -16.737,1.788 -26.443,-8.678 -41.832,-16.509 10.854,0.893 15.588,4.029 22.918,4.166 18.509,0.349 28.132,-16.009 45.188,-20.205 12.894,-3.165 20.695,6.982 35.932,10.208 10.885,2.287 16.538,-5.558 28.116,-10.616 6.022,-2.631 12.325,-4.047 18.651,-4.851 -0.468,3.068 -0.826,6.172 -1.089,9.302 C 26.741,-12.633 17.314,0.262 0,0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1102.4269,1157.8315)"
|
||||||
|
id="g108">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path110"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 146.576,34.475 c -3.252,7.605 -5.851,15.558 -7.721,23.788 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1259.2752,1174.2457)"
|
||||||
|
id="g112">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path114"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 c -5.744,-4.315 -7.926,-9.239 -16.084,-16.099 8.23,14.328 10.611,27.216 24.816,35.606 7.52,4.428 16.876,4.239 23.773,6.79 -1.892,2.9 -3.681,5.872 -5.359,8.916 -14.611,-3.448 -30.867,-4.649 -40.648,-14.903 -8.669,-9.087 -5.852,-27.639 -15.247,-39.514 -6.165,-7.77 -20.769,-4.135 -31.228,-10.164 -4.113,-2.365 -7.202,-5.19 -9.694,-8.371 l 54.554,0 c 8.099,6.033 9.607,16.896 18.593,26.865 7.456,8.254 16.649,5.225 28.987,7.936 6.43,1.42 12.377,3.979 17.976,7.062 -2.205,2.219 -4.335,4.508 -6.386,6.87 C 29.061,5.494 13.858,10.372 0,0" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,1198.7575,957.01607)"
|
||||||
|
id="g116">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path118"
|
||||||
|
style="fill:#807b7d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 0,-14.211 61.83,-25.944 c 1.883,8.231 4.494,16.182 7.762,23.787 L 0,0 Z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.25,0,0,-1.25,552.78342,1909.4007)"
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
inkscape:export-filename="/home/fde/Bureau/g3053.png"
|
||||||
|
id="g120">
|
||||||
|
<g
|
||||||
|
id="g122">
|
||||||
|
<g
|
||||||
|
id="g128">
|
||||||
|
<g
|
||||||
|
id="g130">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path140"
|
||||||
|
style="fill:url(#linearGradient4539);stroke:none"
|
||||||
|
d="m 417.401,765.808 0,-74.132 c 5.042,23.082 23.477,41.205 48.751,41.205 l 0,0 c 31.129,0 50.461,-23.925 50.461,-54.069 l 0,0 0,-94.17 25.28,0 c 13.967,0.107 24.328,9.298 24.328,25.583 l 0,0 0,71.205 c 0,55.716 -45.341,96.678 -100.069,96.678 l 0,0 c -17.612,0 -34.274,-4.44 -48.751,-12.3" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:export-ydpi="42.341129"
|
||||||
|
inkscape:export-xdpi="42.341129"
|
||||||
|
transform="matrix(-1.25,0,0,1.25,1583.8913,205.95708)"
|
||||||
|
id="g3053">
|
||||||
|
<g
|
||||||
|
id="g3055">
|
||||||
|
<g
|
||||||
|
id="g3057">
|
||||||
|
<g
|
||||||
|
id="g3059">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 417.401,765.808 0,-74.132 c 5.042,23.082 23.477,41.205 48.751,41.205 l 0,0 c 31.129,0 50.461,-23.925 50.461,-54.069 l 0,0 0,-94.17 25.28,0 c 13.967,0.107 24.328,9.298 24.328,25.583 l 0,0 0,71.205 c 0,55.716 -45.341,96.678 -100.069,96.678 l 0,0 c -17.612,0 -34.274,-4.44 -48.751,-12.3"
|
||||||
|
style="fill:url(#linearGradient3063);stroke:none"
|
||||||
|
id="path3061" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 22 KiB |
BIN
doc/userguide/source/img/servers.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
doc/userguide/source/img/start-server.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
doc/userguide/source/img/status.png
Normal file
After Width: | Height: | Size: 119 KiB |
295
doc/userguide/source/main.tex
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
\documentclass{article}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[french]{babel}
|
||||||
|
\usepackage{url}
|
||||||
|
\usepackage{lmodern}
|
||||||
|
\usepackage[top=4cm, bottom=4cm, left=4cm, right=4cm]{geometry}
|
||||||
|
\usepackage{minted}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{svg}
|
||||||
|
\usepackage[hidelinks]{hyperref}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\begin{titlepage}
|
||||||
|
|
||||||
|
\newcommand{\HRule}{\rule{\linewidth}{0.5mm}}
|
||||||
|
|
||||||
|
\center
|
||||||
|
|
||||||
|
\HRule \\[0.4cm]
|
||||||
|
{\huge \bfseries Guide de l'utilisateur\\[0.4cm] CT-Application}\\[0.4cm]
|
||||||
|
|
||||||
|
|
||||||
|
\begin{minipage}{0.4\textwidth}
|
||||||
|
\begin{flushleft} \large
|
||||||
|
\emph{Auteur:}\\
|
||||||
|
Jules \textsc{Dejaeghere}
|
||||||
|
\end{flushleft}
|
||||||
|
\end{minipage}
|
||||||
|
~
|
||||||
|
\begin{minipage}{0.4\textwidth}
|
||||||
|
\begin{flushright} \large
|
||||||
|
\emph{Promoteur:} \\
|
||||||
|
Pr Jean-Noël \textsc{Colin}
|
||||||
|
\end{flushright}
|
||||||
|
\end{minipage}\\[2cm]
|
||||||
|
|
||||||
|
|
||||||
|
{\large
|
||||||
|
Version 1.0.0\\
|
||||||
|
\medskip
|
||||||
|
Année académique: 2019-2020}\\[2cm]
|
||||||
|
|
||||||
|
\includesvg[height=2cm]{img/logo_.svg}
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
\end{titlepage}
|
||||||
|
|
||||||
|
|
||||||
|
\tableofcontents
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\section{Introduction}
|
||||||
|
|
||||||
|
CT-Application télécharge des certificats belges depuis les logs du projet Certificate Transparency et visite les sites web correspondant pour trouver des données relatives à l'entreprise à qui appartient le certificat, tel que le numéro de TVA. L'application conserve également des données à propos des certificats téléchargés: le sujet, l'émetteur, la période de validité et l'algorithme de signature.
|
||||||
|
|
||||||
|
Ces données peuvent être utilisées pour identifier les comportements malicieux en ligne. Cependant, CT-Application ne fournit pas de détection de comportements malicieux.
|
||||||
|
|
||||||
|
Comme son nom le suggère, CT-Application repose sur le projet de Google, Certificate Transparency. Ce projet a un objectif bien plus large. Pour obtenir plus d'informations sur le projet Certificate Transparency, consultez leur site web: \url{https://www.certificate-transparency.org}.
|
||||||
|
|
||||||
|
|
||||||
|
%\clearpage
|
||||||
|
\section{Installation}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Exigences}
|
||||||
|
|
||||||
|
Pour lancer CT-Application, assurez vous de remplir les exigences suivantes:
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item Avoir Java 8 JRE installé pour lancer l'application
|
||||||
|
\item Avoir une base de données existante pour stocker les données
|
||||||
|
\item Avoir une connexion Internet stable et rapide pour télécharger les certificats et visiter les sites web
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Comment l'installer?}
|
||||||
|
|
||||||
|
Installer CT-Application est assez rapide. Placez simplement le fichier \path{JAR} et le fichier \path{application.properties} dans le même répertoire.
|
||||||
|
|
||||||
|
L'application est maintenant prête pour être configurée avant la première exécution.
|
||||||
|
|
||||||
|
\subsection{Paramètres de base}
|
||||||
|
|
||||||
|
Tous les paramètres de l'application sont sauvegardés dans le fichier \path{application.properties}. Les paramètres par défaut conviennent pour lancer l'application. Les seuls paramètres à modifier sont ceux concernant la base de données.
|
||||||
|
|
||||||
|
Les paramètres suivants doivent être adaptés avant la première exécution afin de pouvoir stocker les données.
|
||||||
|
|
||||||
|
\begin{minted}{text}
|
||||||
|
spring.datasource.url=jdbc:postgresql://ip:port/db-name
|
||||||
|
spring.datasource.username=user
|
||||||
|
spring.datasource.password=password
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Ces paramètres indiquent à l'application de se connecter à la base de données PostgreSQL nommée \mintinline{text}{db-name} sur l'hôte spécifié par \mintinline{text}{ip:port}. Dans l'exemple, les données de connexion sont \mintinline{text}{user:password}. L'utilisateur spécifié dans les données de connexion doit exister préalablement et avoir les droits en lecture et en écriture sur la base de données.
|
||||||
|
|
||||||
|
Les paramètres suivants ne sont pas obligatoires mais permettent de configurer plus finement l'application.
|
||||||
|
|
||||||
|
\begin{minted}{text}
|
||||||
|
server.port = 8090
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Ce paramètre définit le port sur lequel l'application va s'exécuter. Si le paramètre n'est pas spécifié, le port par défaut sera le \mintinline{text}{8090}.
|
||||||
|
|
||||||
|
\begin{minted}{text}
|
||||||
|
threads-decode = 3
|
||||||
|
threads-scrap = 3
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Ces paramètres définissent le nombre de threads à allouer aux différentes tâches de l'application. Le premier paramètre, \mintinline{text}{threads-decode}, définit le nombre de threads disponibles pour convertir les données téléchargées en objets Java. Le second paramètre, \mintinline{text}{threads-scrap}, définit le nombre de threads disponibles pour visiter les sites web repris dans les certificats téléchargés. Le deuxième paramètre aura généralement une valeur plus élevée que le premier étant donné que le parcours de sites web nécessite plus de ressources et génère de nombreux appels bloquants.
|
||||||
|
|
||||||
|
Le nombre de threads à allouer à chaque tâche dépendra du matériel sur lequel l'application s'exécute et des ressources disponibles pour l'application.
|
||||||
|
|
||||||
|
\subsection{Configuration rapide de PostgreSQL}
|
||||||
|
|
||||||
|
Une solution simple pour configurer une base de données PostgreSQL est d'utiliser Docker. Dans cet exemple, Docker Compose sera utilisé pour créer la base de données. Si Docker Compose n'est pas installé sur la machine, veuillez vous référer à la documentation de Docker Compose pour l'installer: \url{https://docs.docker.com/compose/install/}.
|
||||||
|
|
||||||
|
Une fois Docker Compose installé, créez un fichier nommé \path{docker-compose.yml} dans votre répertoire courant et collez ce qui suit.
|
||||||
|
|
||||||
|
\begin{minted}{yaml}
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: "postgres"
|
||||||
|
container_name: "postgres_db"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=bob
|
||||||
|
- POSTGRES_PASSWORD=secret
|
||||||
|
- POSTGRES_DB=ct
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- /chemin/du/dossier:/var/lib/postgresql/data
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Assurez vous de faire correspondre le chemin du dossier à votre configuration. Une fois terminé, lancez le conteneur PostgreSQL en utilisant Docker Compose.
|
||||||
|
|
||||||
|
\begin{minted}{bash}
|
||||||
|
docker-compose up -d
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Modifiez le fichier \path{application.properties} pour refléter la configuration de la base de données. En supposant que Docker Compose a été installé sur la machine où s'exécutera CT-Application, le fichier devrait ressembler à ce qui suit.
|
||||||
|
|
||||||
|
\begin{minted}{text}
|
||||||
|
spring.datasource.url=jdbc:postgresql://localhost:5432/ct
|
||||||
|
spring.datasource.username=bob
|
||||||
|
spring.datasource.password=secret
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
L'application est maintenant configurée pour utiliser le conteneur Docker précédemment créé comme base de données.
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Bien démarrer}
|
||||||
|
|
||||||
|
Pour lancer l'application, exécuter le fichier \path{JAR} en utilisant la commande suivante:
|
||||||
|
|
||||||
|
\begin{minted}{bash}
|
||||||
|
java -jar CtApplication.jar
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Etant donné que l'application est prévue pour s'exécuter en permanence, il est préférable de lancer l'application en utilisant la commande \mintinline{bash}{screen} pour pouvoir quitter le terminal sans quitter l'application.
|
||||||
|
|
||||||
|
\begin{minted}{bash}
|
||||||
|
screen -S ct
|
||||||
|
java -jar CtApplication.jar
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Une fois que l'application est lancée, elle crée les tables nécessaires dans la base de données configurée et écoute sur le port spécifié.
|
||||||
|
|
||||||
|
\clearpage
|
||||||
|
\section{Utiliser l'interface web}
|
||||||
|
|
||||||
|
Lorsque l'application s'exécute, il est possible d'interagir avec via un navigateur web. En supposant que l'application s'exécute sur la machine locale avec le port par défaut, l'interface web est accessible à l'adresse \url{http://localhost:8090}. Sur la page principale, l'application affiche les quatre parties disponibles via l'interface web: \emph{Servers}, \emph{Status}, \emph{Data} et \emph{Graphs}. Ces quatre parties seront l'objet des sections suivantes.
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\noindent\makebox[\textwidth]{\includegraphics[width=\paperwidth]{img/home.png}}
|
||||||
|
\caption{Page principale de CT-Application}
|
||||||
|
\label{home-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsection{Première exécution}
|
||||||
|
|
||||||
|
Cette section détaille les étapes à suivre pour ajouter le premier serveur à l'application pour permettre le téléchargement de certificats.
|
||||||
|
|
||||||
|
Pour ajouter le premier serveur dans l'application, dirigez vous vers l'onglet \emph{Servers} et créez le serveur en utilisant le formulaire à gauche sur la page, comme présenté dans la figure \ref{add-srv-fig}.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=0.5\textwidth]{img/add-srv.png}
|
||||||
|
\end{center}
|
||||||
|
\caption{Ajouter un serveur}
|
||||||
|
\label{add-srv-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Pour commencer à télécharger les certificats depuis le nouveau serveur, appuyer sur le bouton \emph{Start} en regard du serveur, comme dans la figure \ref{start-srv-fig}. Une fois le serveur lancé, l'application va télécharger et filtrer les certificats présents dans les logs du serveur sélectionné. Plus de détails sur la manière d'afficher les données téléchargées se trouvent dans les sections suivantes.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=\textwidth]{img/start-server.png}
|
||||||
|
\end{center}
|
||||||
|
\caption{Lancer un serveur}
|
||||||
|
\label{start-srv-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Gérer les serveurs}
|
||||||
|
|
||||||
|
Les serveurs sont la source de données pour l'application. Ils peuvent être gérés depuis l'onglet \emph{Servers}. Cet onglet regroupe tous les serveurs actuellement dans la base de données de l'application et permet d'en ajouter ou de les lancer.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\noindent\makebox[\textwidth]{\includegraphics[width=\paperwidth]{img/servers.png}}
|
||||||
|
\caption{Onglet \emph{Servers}}
|
||||||
|
\label{servers-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Pour ajouter un nouveau serveur, choisissez un surnom (\emph{Nickname}) et coller l'\emph{URL}. Le surnom est facultatif. Une fois ajouté, le serveur apparaît à la fin de la liste, sur la droite de la page. Il est nécessaire de lancer le serveur après l'avoir ajouté. Sinon, rien ne sera téléchargé depuis ce serveur.
|
||||||
|
|
||||||
|
Une fois que le bouton \emph{Start} en regard du serveur a été cliqué, tous les logs disponibles sur le serveur au moment du lancement seront téléchargés. L'application arrêtera ensuite de télécharger des logs à partir de ce serveur. Pour vérifier si de nouveaux logs sont disponibles sur le serveur, cliquez à nouveau sur \emph{Start}.
|
||||||
|
|
||||||
|
Un lien vers l'URL du serveur est fourni dans le tableau. Certains fournisseurs de logs, comme Cloudflare, affichent un résumé des données présentes sur le serveur à l'adresse pointée par le lien alors que d'autres fournisseurs ne le font pas.
|
||||||
|
|
||||||
|
Plus de serveurs peuvent être trouvés sur le site de Certificate Transparency: \url{https://www.certificate-transparency.org/known-logs}.
|
||||||
|
|
||||||
|
\subsection{Vérifier le statut}
|
||||||
|
|
||||||
|
|
||||||
|
L'onglet \emph{Status} affiche les informations à propos de l'état actuel des différentes parties du programme. Une fois qu'un serveur est ajouté dans l'application, le processus d'acquisition des données se divise en quatre étapes.
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\item[Server handling -] Découpe les nouveaux serveurs en tranches pour télécharger les données plus rapidement
|
||||||
|
\item[Downloader -] Télécharge effectivement les données depuis le serveur
|
||||||
|
\item[Decoder -] Décode les données téléchargées, conserve uniquement les certificats belges et les enregistre dans la base de données
|
||||||
|
\item[VAT scrapper -] Tente de retrouver un numéro de TVA sur le site renseigné dans le certificat
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
Cette division est visible dans l'onglet \emph{Status}.
|
||||||
|
|
||||||
|
Depuis cet onglet, il est possible d'arrêter les différentes parties de l'application pour arrêter l'exécution du programme. A partir de cet onglet, il est également possible de relancer la recherche de numéros de TVA. Cela s'avère particulièrement utile lorsque l'application a été arrêtée mais que tous les certificats n'ont pas été analysés pour la recherche du numéro de TVA. Relancer cette recherche aura pour effet de parcourir les sites renseignés dans les certificats qui n'ont pas encore été analysés.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\noindent\makebox[\textwidth]{\includegraphics[width=\paperwidth]{img/status.png}}
|
||||||
|
\caption{Onglet \emph{Status}}
|
||||||
|
\label{status-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Consulter les données}
|
||||||
|
|
||||||
|
L'onglet \emph{Data} affiche les informations relatives aux données collectées depuis les différents serveurs de logs qui ont été ajoutés à l'application. Cet onglet donne une vue brute des données. Pour chaque certificat, le sujet, l'émetteur, la période de validité et, s'il a été trouvé, le numéro de TVA sont affichés.
|
||||||
|
|
||||||
|
Si, pour un certificat donné, le numéro de TVA a été trouvé, un lien vers la \emph{Banque-Carrefour des Entreprises} est inclus pour permettre d'obtenir plus de détails à propos de l'entreprise.
|
||||||
|
|
||||||
|
Le bouton \emph{Only with VAT} permet de basculer entre l'affichage complet et l'affichage des certificats avec un numéro de TVA uniquement.
|
||||||
|
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\noindent\makebox[\textwidth]{\includegraphics[width=\paperwidth]{img/data.png}}
|
||||||
|
\caption{Onglet \emph{Data}}
|
||||||
|
\label{data-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Afficher les graphiques}
|
||||||
|
|
||||||
|
L'onglet \emph{Graphs} permet d'afficher un résumé des données actuellement dans la base de données. Trois graphiques sont disponibles: les émetteurs les plus populaires, les algorithmes de signature les plus populaires et un aperçu de l'état de recherche des numéros de TVA.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\noindent\makebox[\textwidth]{\includegraphics[width=\paperwidth]{img/graphs.png}}
|
||||||
|
\caption{Onglet \emph{Graphs}}
|
||||||
|
\label{graphs-fig}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\clearpage
|
||||||
|
|
||||||
|
\section{FAQ}
|
||||||
|
|
||||||
|
\subsection{Ma base de données pose problème, comment puis-je y remédier?}
|
||||||
|
|
||||||
|
Pour comprendre ce qui pose problème, il est possible d'afficher toutes les requêtes SQL dans le terminal où s'exécute l'application. Pour activer cette option, arrêtez l'application, modifiez le fichier \path{application.properties} en y ajoutant la ligne ci-dessous et redémarrez l'application.
|
||||||
|
|
||||||
|
\begin{minted}{text}
|
||||||
|
spring.jpa.show-sql=true
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
Une fois cette option activée, les requêtes SQL seront affichées dans le terminal et aideront peut-être à identifier la cause du problème.
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{L'application met longtemps à s'arrêter, que puis-je faire?}
|
||||||
|
|
||||||
|
Lorsque l'application s'arrête, elle met fin à tous les threads en cours d'exécution et vide la file d'attente des threads. En fonction du nombre de threads en cours d'exécution, cette opération peut prendre un certain temps. Attendre l'arrêt de tous les threads et de l'application est la meilleure chose à faire pour éviter une éventuelle corruption des données.
|
||||||
|
|
||||||
|
\end{document}
|