From 30d08c544bbe74ce35c0c17aced8f132f0bbc0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= Date: Thu, 4 Feb 2021 11:11:43 +0100 Subject: [PATCH 01/17] Interface translation test --- app/frontend/package-lock.json | 160 ++++++++++++------------ app/frontend/src/App.vue | 14 ++- app/frontend/src/router/index.js | 6 +- app/frontend/src/views/analysis.vue | 14 ++- app/frontend/src/views/capture.vue | 15 ++- app/frontend/src/views/generate-ap.vue | 22 ++-- app/frontend/src/views/home.vue | 17 ++- app/frontend/src/views/report.vue | 59 +++++---- app/frontend/src/views/save-capture.vue | 14 ++- app/frontend/src/views/wifi-select.vue | 33 ++--- config.yaml | 1 + server/frontend/app/assets/lang/en.json | 81 ++++++++++++ server/frontend/app/assets/lang/fr.json | 81 ++++++++++++ server/frontend/app/blueprints/misc.py | 16 +++ 14 files changed, 378 insertions(+), 155 deletions(-) create mode 100644 server/frontend/app/assets/lang/en.json create mode 100644 server/frontend/app/assets/lang/fr.json diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json index ebba075..96fc6af 100644 --- a/app/frontend/package-lock.json +++ b/app/frontend/package-lock.json @@ -1806,16 +1806,6 @@ "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "cacache": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", @@ -1842,34 +1832,6 @@ "unique-filename": "^1.1.1" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -1891,25 +1853,6 @@ "path-exists": "^4.0.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1974,16 +1917,6 @@ "minipass": "^3.1.1" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - }, "terser-webpack-plugin": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", @@ -2000,18 +1933,6 @@ "terser": "^4.6.12", "webpack-sources": "^1.4.3" } - }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.1.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", - "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - } } } }, @@ -11336,6 +11257,87 @@ } } }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.1.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", + "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "vue-router": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.3.tgz", diff --git a/app/frontend/src/App.vue b/app/frontend/src/App.vue index de6927f..32c8dea 100644 --- a/app/frontend/src/App.vue +++ b/app/frontend/src/App.vue @@ -28,13 +28,25 @@ diff --git a/app/frontend/src/router/index.js b/app/frontend/src/router/index.js index 642c540..bcf457d 100644 --- a/app/frontend/src/router/index.js +++ b/app/frontend/src/router/index.js @@ -41,12 +41,14 @@ const routes = [ component: () => import('../views/save-capture.vue'), props: true }, - { path: '/analysis', + { + path: '/analysis', name: 'analysis', component: () => import('../views/analysis.vue'), props: true }, - { path: '/report', + { + path: '/report', name: 'report', component: () => import('../views/report.vue'), props: true diff --git a/app/frontend/src/views/analysis.vue b/app/frontend/src/views/analysis.vue index d0e68d9..9ce84ac 100644 --- a/app/frontend/src/views/analysis.vue +++ b/app/frontend/src/views/analysis.vue @@ -1,15 +1,15 @@ @@ -25,7 +25,8 @@ export default { question: true, running: false, check_alerts: false, - long_waiting: false + long_waiting: false, + translation: {} } }, props: { @@ -65,6 +66,9 @@ export default { var capture_token = this.capture_token router.replace({ name: 'save-capture', params: { capture_token: capture_token } }); } + }, + created: function() { + this.translation = window.translation[this.$route.name] } } diff --git a/app/frontend/src/views/capture.vue b/app/frontend/src/views/capture.vue index 10d0f8d..9f01f68 100644 --- a/app/frontend/src/views/capture.vue +++ b/app/frontend/src/views/capture.vue @@ -3,10 +3,10 @@
@@ -29,7 +29,8 @@ export default { loading: false, stats_interval: false, chrono_interval: false, - sparklines: false + sparklines: false, + translation: {} } }, props: { @@ -86,12 +87,14 @@ export default { .catch(error => { console.log(error) }); - } + }, }, created: function() { + this.translation = window.translation[this.$route.name] + // Get the config for the sparklines. this.setup_sparklines() - + // Start the chrono and get the first stats. this.capture_start = Date.now() this.set_chrono(); diff --git a/app/frontend/src/views/generate-ap.vue b/app/frontend/src/views/generate-ap.vue index 39d1fb0..53f5920 100644 --- a/app/frontend/src/views/generate-ap.vue +++ b/app/frontend/src/views/generate-ap.vue @@ -9,29 +9,29 @@

- Network name:
+ {{ translation.network_name }}

{{ ssid_name }}

- Network password:
+ {{ translation.network_password }}

{{ ssid_password }}











- Tap the white frame to generate a new network. + {{ translation.tap_msg }}
-

We generate an ephemeral network for you.

+

{{ translation.generate_ap_msg }}

- Unfortunately, we got some issues
during the AP creation.
+

- Please verify that you've two WiFi interfaces on your device
and try again by restarting it.

+

- +
@@ -54,7 +54,8 @@ export default { interval: false, error: false, reboot_option: false, - attempts: 3 + attempts: 3, + translation: {} } }, methods: { @@ -116,7 +117,7 @@ export default { }); } }, - load_config: function() { + get_config: function() { axios.get(`/api/misc/config`, { timeout: 60000 }) .then(response => { this.reboot_option = response.data.reboot_option @@ -127,7 +128,8 @@ export default { }, }, created: function() { - this.load_config() + this.translation = window.translation[this.$route.name] + this.get_config(); this.generate_ap(); } } diff --git a/app/frontend/src/views/home.vue b/app/frontend/src/views/home.vue index 5714ebd..2dddea2 100644 --- a/app/frontend/src/views/home.vue +++ b/app/frontend/src/views/home.vue @@ -1,8 +1,8 @@ @@ -12,12 +12,16 @@ import router from '../router' export default { name: 'home', props: { saved_ssid: String, iface_out: String, list_ssids: Array, internet: Boolean }, + data() { + return { + translation: {}, + } + }, methods: { next: function() { var saved_ssid = this.saved_ssid var list_ssids = this.list_ssids var internet = this.internet - console.log(this.iface_out) if (this.iface_out.charAt(0) == "e"){ router.push({ name: 'generate-ap' }); } else { @@ -26,7 +30,10 @@ export default { list_ssids: list_ssids, internet:internet } }); } - } + } + }, + created: function() { + this.translation = window.translation[this.$route.name] } } diff --git a/app/frontend/src/views/report.vue b/app/frontend/src/views/report.vue index b9b6708..51a1ba5 100644 --- a/app/frontend/src/views/report.vue +++ b/app/frontend/src/views/report.vue @@ -3,69 +3,69 @@
-

Your device is compromised by
a Stalkerware, please check the report.

- - +

+ +
-

You have {{ nb_translate(alerts.high.length) }} high alert,
your device seems to be compromised.

- - +

+ +
-

An application is sharing your
current geolocation with a third party.

- - +

+ +
-

You have {{ nb_translate(alerts.moderate.length) }} moderate alerts,
your device might be compromised.

- - +

+ +
-

You have only {{ nb_translate(alerts.moderate.low) }} low alerts,
don't hesitate to check them.

- - +

+ +
-

Everything looks fine, zero alerts.

- - +

+ +

-

Report for {{device.name}}

- IP Address: {{device.ip_address}}
Mac Address: {{device.mac_address}} +

{{ translation.report_of }} {{ device.name }}

+ {{ translation.ip_address }} {{device.ip_address}}
{{ translation.mac_address }} {{device.mac_address}}
- +
- +
@@ -100,6 +100,7 @@ export default { data() { return { results: true, + translation: {} } }, props: { @@ -116,7 +117,7 @@ export default { router.push({ name: 'generate-ap' }) }, nb_translate: function(x) { - var nbs = ['zero','one','two','three','four', 'five','six','seven','eight','nine', 'ten', 'eleven'] + var nbs = this.translation["numbers"] try { return nbs[x]; } catch (error) @@ -142,6 +143,12 @@ export default { return false; } } + }, + created: function() { + this.translation = window.translation[this.$route.name] + this.translation["high_msg"] = this.translation["high_msg"].replace("{nb}", this.nb_translate(this.alerts.high.length)) + this.translation["moderate_msg"] = this.translation["moderate_msg"].replace("{nb}", this.nb_translate(this.alerts.moderate.length)) + this.translation["low_msg"] = this.translation["low_msg"].replace("{nb}", this.nb_translate(this.alerts.low.length)) } } diff --git a/app/frontend/src/views/save-capture.vue b/app/frontend/src/views/save-capture.vue index fd989dd..26cb79a 100644 --- a/app/frontend/src/views/save-capture.vue +++ b/app/frontend/src/views/save-capture.vue @@ -6,14 +6,14 @@
-


Please connect a USB key to save your capture.

-


We are saving your capture.

-


You can tap the USB key to start a new capture.

+


{{ translation.please_connect }}

+


{{ translation.we_are_saving }}

+


{{ translation.tap_msg }}

-

The capture download is going to start...


- +

{{ translation.catpure_download }}


+
@@ -144,7 +144,8 @@ export default { usb: false, saved: false, save_usb: false, - init: false + init: false, + translation: {} } }, props: { @@ -194,6 +195,7 @@ export default { } }, created: function() { + this.translation = window.translation[this.$route.name] this.load_config() } } diff --git a/app/frontend/src/views/wifi-select.vue b/app/frontend/src/views/wifi-select.vue index 79b5125..cc16eee 100644 --- a/app/frontend/src/views/wifi-select.vue +++ b/app/frontend/src/views/wifi-select.vue @@ -2,41 +2,41 @@
-

You seem to be already connected to a network.
Do you want to use the current connection?

+

- +
-

You seem to not be connected to Internet.
Please configure the Wi-Fi connection.

+

{{ translation.not_connected }}
{{ translation.please_config }}

- +
- +
@@ -63,7 +63,7 @@ export default { connecting: false, error: false, success: false, - btnval: "Connect to it.", + btnval: "", ssid: "", selected_ssid: false, password: "", @@ -73,7 +73,8 @@ export default { have_internet: false, enter_creds: false, virtual_keyboard: false, - refreshing: false + refreshing: false, + translation: {} } }, props: { @@ -88,10 +89,10 @@ export default { if (response.data.status) { this.success = true this.connecting = false - this.btnval = "Wifi connected!" + this.btnval = this.translation.wifi_connected setTimeout(() => router.push('generate-ap'), 1000); } else { - this.btnval = "Wifi not connected. Please retry." + this.btnval = this.translation["wifi_not_connected"] this.connecting = false } }) @@ -115,7 +116,7 @@ export default { }); } }, - load_config: function() { + get_config: function() { axios.get(`/api/misc/config`, { timeout: 60000 }) .then(response => { this.virtual_keyboard = response.data.virtual_keyboard @@ -146,12 +147,14 @@ export default { this.append_ssids(response.data.networks) }).catch(error => { this.refreshing = false - console.log(error) + console.log(error) }); } }, created: function() { - this.load_config() + this.translation = window.translation[this.$route.name] + this.btnval = this.translation.connect_to_it + this.get_config() this.have_internet = (this.internet) ? true : false this.keyboard = false diff --git a/config.yaml b/config.yaml index 178fec0..a2032ee 100644 --- a/config.yaml +++ b/config.yaml @@ -48,6 +48,7 @@ frontend: remote_access: true sparklines: true virtual_keyboard: true + user_lang: en # NETWORK - # Some elements related to the network configuration, such as diff --git a/server/frontend/app/assets/lang/en.json b/server/frontend/app/assets/lang/en.json new file mode 100644 index 0000000..239008d --- /dev/null +++ b/server/frontend/app/assets/lang/en.json @@ -0,0 +1,81 @@ +{ + "home": { + "welcome_msg": "Welcome to TinyCheck.", + "help_msg": "We are going to help you to check your device.", + "start_btn": "Let's start!" + }, + "analysis": { + "question": "Do you want to analyze the captured communications?", + "no_btn": "No, just save them", + "yes_btn": "Yes, let's do it", + "please_wait_msg": "Please wait during the analysis...", + "some_time_msg": "Yes, it can take some time..." + }, + "capture": { + "intercept_coms_msg": "Intercepting the communications of ", + "stop_btn": "Stop the capture" + }, + "generate-ap": { + "network_name": "Network name", + "network_password": "Network password", + "tap_msg": "Tap the white frame to generate a new network.", + "generate_ap_msg": "We generate an ephemeral network for you.", + "error_msg1": "Unfortunately, we got some issues
during the AP creation.", + "error_msg2": "Please verify that you've two WiFi interfaces on your device
and try again by restarting it.", + "restart_btn": "Restart the device" + }, + "report": { + "show_full_report": "Show the full report", + "start_new_capture": "Start a new capture", + "save_capture": "Save the capture", + "numbers": [ + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "ten", + "eleven" + ], + "stalkerware_msg": "Your device is compromised by
a Stalkerware, please check the report.", + "location_msg": "An application is sharing your
current geolocation with a third party.", + "fine_msg": "Everything looks fine, zero alerts.", + "high_msg": "You have {nb} high alert,
your device seems to be compromised.", + "moderate_msg": "You have {nb} moderate alerts,
your device might be compromised.", + "low_msg": "You have only {nb} low alerts,
don't hesitate to check them.", + "save_report": "Save the report", + "report_of": "Report of", + "ip_address": "IP address", + "mac_address": "MAC address", + "high": "high", + "moderate": "moderate", + "low": "low" + }, + "wifi-select": { + "already_connected_question": "You seem to be already connected to a network.
Do you want to use the current connection?", + "no_btn": "No, use another", + "yes_btn": "Yes, use it.", + "wifi_name": "Wi-Fi name", + "refresh_btn": "Refresh networks list", + "not_connected": "You seem to not be connected to Internet.", + "please_config": "Please configure the Wi-Fi connection.", + "lets_do_btn": "Ok, let's do that.", + "wifi_connected": "Wi-Fi connected", + "wifi_not_connected": "Wi-Fi not connected. Please retry.", + "tap_keyboard": "Tap on the virtual keyboard to start", + "wifi_password": "Wi-Fi password", + "connect_to_it": "Connect to it" + }, + "save-capture": { + "please_connect": "Please connect a USB key to save your capture.", + "we_are_saving": "We are saving your capture.", + "tap_msg": "You can tap the USB key to start a new capture.", + "capture_download": "The capture download is going to start...", + "start_capture_btn": "Start another capture" + } +} \ No newline at end of file diff --git a/server/frontend/app/assets/lang/fr.json b/server/frontend/app/assets/lang/fr.json new file mode 100644 index 0000000..375bcd8 --- /dev/null +++ b/server/frontend/app/assets/lang/fr.json @@ -0,0 +1,81 @@ +{ + "home": { + "welcome_msg": "Bienvenue sur TinyCheck.", + "help_msg": "Nous allons vous accompagner pour analyser votre appareil.", + "start_btn": "Allons-y !" + }, + "analysis": { + "question": "Voulez-vous analyser les communications capturées ?", + "no_btn": "Non, enregistrez-les directement.", + "yes_btn": "Oui, allons-y !", + "please_wait_msg": "Merci d'attendre pendant l'analyse...", + "some_time_msg": "Oui, cela peut prendre du temps..." + }, + "capture": { + "intercept_coms_msg": "Interception des communications de ", + "stop_btn": "Arrêter la capture" + }, + "generate-ap": { + "network_name": "Nom du réseau", + "network_password": "Mot de passe du réseau", + "tap_msg": "Appuyez sur le cadre blanc pour générer un nouveau réseau.", + "generate_ap_msg": "Nous générons pour vous un réseau Wi-Fi éphémère.", + "error_msg1": "Malheureusement, nous rencontrons des problèmes
lors de la création du point d'accès.", + "error_msg2": "Veuillez vérifier que vous disposez de
deux interfaces WiFi sur votre appareil.", + "restart_btn": "Restart the device" + }, + "report": { + "show_full_report": "Afficher le rapport complet", + "start_new_capture": "Lancer une nouvelle capture", + "save_capture": "Sauvegarder la capture", + "numbers": [ + "zéro", + "une", + "deux", + "trois", + "quatre", + "cinq", + "six", + "sept", + "huit", + "neuf", + "dix", + "onze" + ], + "stalkerware_msg": "Votre smartphone est compromis
par un Stalkerware, merci de regarder le rapport.", + "location_msg": "Une application dévoile votre
géo-localisation actelle.", + "fine_msg": "Tout semble OK, vous avez aucue alerte.", + "high_msg": "Vous avez {nb} alerte(s) élevée(s),
votre appareil semble être compromis.", + "moderate_msg": "Vous avez {nb} alerte(s) moyenne(s),
votre appareil peut être compromis.", + "low_msg": "Vous avez uniquement {nb} alerte(s) basse(s)
n'hésitez pas à les lire.", + "save_report": "Sauvegarder le rapport", + "report_of": "Rapport de", + "ip_address": "Adresse IP ", + "mac_address": "Adresse MAC", + "high": "elevee", + "moderate": "moyenne", + "low": "base" + }, + "wifi-select": { + "already_connected_question": "Vous semblez être connecté à un réseau.
Voulez-vous utiliser cette connexion ?", + "no_btn": "Non, une autre", + "yes_btn": "Oui, utilisez celle-là", + "wifi_name": "Nom du réseau", + "refresh_btn": "Rafraichir la liste des réseaux", + "not_connected": "Vous semblez ne pas être connecté à Internet.", + "please_config": "Configurez la connexion Wi-Fi.", + "lets_do_btn": "Oui, allons-y.", + "wifi_connected": "Wi-Fi connecté", + "wifi_not_connected": "Wi-Fi non connecté. Ressayez.", + "tap_keyboard": "Appuez sur cet espace pour écrire", + "wifi_password": "Mot de passe", + "connect_to_it": "Se connecter" + }, + "save-capture": { + "please_connect": "Connectez une clé USB pour enregistrer votre capture.", + "we_are_saving": "Nous enregistrons votre capture.", + "tap_msg": "Vous pouvez appuyer sur l'animation pour lancer une nouvelle capture.", + "capture_download": "Le téléchargement de la capture va se lancer...", + "start_capture_btn": "Lancer une nouvelle capture" + } +} \ No newline at end of file diff --git a/server/frontend/app/blueprints/misc.py b/server/frontend/app/blueprints/misc.py index 6964121..aa76d4a 100644 --- a/server/frontend/app/blueprints/misc.py +++ b/server/frontend/app/blueprints/misc.py @@ -4,6 +4,7 @@ import subprocess as sp from flask import Blueprint, jsonify from app.utils import read_config +import re misc_bp = Blueprint("misc", __name__) @@ -59,3 +60,18 @@ def get_config(): "reboot_option": read_config(("frontend", "reboot_option")), "iface_out": read_config(("network", "out")) }) + + +@app.route("/get-lang", methods=["GET"]) +def get_lang(): + """ + Get the user lang defined in the config.yaml + and retrieve the interface translation. + """ + lang = read_config(("frontend", "user_lang")) + if re.match("^[a-z]{2,3}$", lang): + with open("app/assets/lang/{}.json".format(lang), "r") as f: + return(f.read()) + else: + with open("app/assets/lang/en.json", "r") as f: + return(f.read()) From 96d55c2ecac29a17c33ae68ccdfb54fca50bc0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= Date: Thu, 4 Feb 2021 11:33:33 +0100 Subject: [PATCH 02/17] Analysis translation test --- analysis/classes/report.py | 132 +++++++++++++++++------------ analysis/classes/suricataengine.py | 17 ++-- analysis/classes/zeekengine.py | 121 ++++++++++++-------------- analysis/lang/en.json | 108 +++++++++++++++++++++++ analysis/lang/fr.json | 108 +++++++++++++++++++++++ 5 files changed, 360 insertions(+), 126 deletions(-) create mode 100644 analysis/lang/en.json create mode 100644 analysis/lang/fr.json diff --git a/analysis/classes/report.py b/analysis/classes/report.py index 8a601dc..20a6b67 100644 --- a/analysis/classes/report.py +++ b/analysis/classes/report.py @@ -2,10 +2,13 @@ import weasyprint import os import json import hashlib +import re +import sys from weasyprint import HTML from pathlib import Path from datetime import datetime +from utils import get_config class Report(object): @@ -28,6 +31,14 @@ class Report(object): except: self.capture_sha1 = "N/A" + self.userlang = get_config(("frontend", "userlang")) + + # Load template language + if not re.match("^[a-z]{2}$", self.userlang): + self.userlang = "en" + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "lang/{}.json".format(self.userlang)))as f: + self.template = json.load(f)["report"] + def read_json(self, json_path): """ Read and convert a JSON file. @@ -59,21 +70,35 @@ class Report(object): :return: str """ if len(self.alerts["high"]): - return "
Your device seems to be compromised as you have {} high alert(s).
".format(self.nb_translate(len(self.alerts["high"]))) + msg = "
" + msg += self.template["high_msg"].format( + self.nb_translate(len(self.alerts["high"]))) + msg += "
" + return msg elif len(self.alerts["moderate"]): - return "
You have {} moderate alert(s), your device might be compromised. Please look at them carefully.
".format(self.nb_translate(len(self.alerts["moderate"]))) + msg = "
" + msg += self.template["moderate_msg"].format( + self.nb_translate(len(self.alerts["moderate"]))) + msg += "
" + return msg elif len(self.alerts["low"]): - return "
You have only {} low alert(s), don't hesitate to check them.
".format(self.nb_translate(len(self.alerts["low"]))) + msg = "
" + msg += self.template["low_msg"].format( + self.nb_translate(len(self.alerts["low"]))) + msg += "
" + return msg else: - return "
Everything looks fine, zero alerts. Don't hesitate to check the uncategorized communications, if any.
" + msg = "
" + msg += self.template["none_msg"] + msg += "
" + return msg def nb_translate(self, nb): """ Translate a number in a string. :return: str """ - a = ["one", "two", "three", "four", "five", - "six", "seven", "height", "nine"] + a = self.template["numbers"] return a[nb-1] if nb <= 9 else str(nb) def generate_suspect_conns_block(self): @@ -85,17 +110,17 @@ class Report(object): if not len([c for c in self.conns if c["alert_tiggered"] == True]): return "" - title = "

Suspect communications

" - table = """ - - - - - - - - - """ + title = "

{}

".format(self.template["suspect_title"]) + table = "
ProtocolDomainDst IP AddressDst port
" + table += " " + table += " " + table += " ".format(self.template["protocol"]) + table += " ".format(self.template["domain"]) + table += " ".format(self.template["dst_ip"]) + table += " ".format(self.template["dst_port"]) + table += " " + table += " " + table += "" for rec in self.conns: if rec["alert_tiggered"] == True: @@ -117,17 +142,17 @@ class Report(object): if not len([c for c in self.conns if c["alert_tiggered"] == False]): return "" - title = "

Uncategorized communications

" - table = """
{}{}{}{}
- - - - - - - - - """ + title = "

{}

".format(self.template["uncat_title"]) + table = "
ProtocolDomainDst IP AddressDst port
" + table += " " + table += " " + table += " ".format(self.template["protocol"]) + table += " ".format(self.template["domain"]) + table += " ".format(self.template["dst_ip"]) + table += " ".format(self.template["dst_port"]) + table += " " + table += " " + table += "" for rec in self.conns: if rec["alert_tiggered"] == False: @@ -149,17 +174,17 @@ class Report(object): if not len(self.whitelist): return "" - title = "

Whitelisted communications

" - table = """
{}{}{}{}
- - - - - - - - - """ + title = "

{}

".format(self.template["whitelist_title"]) + table = "
ProtocolDomainDst IP AddressDst port
" + table += " " + table += " " + table += " ".format(self.template["protocol"]) + table += " ".format(self.template["domain"]) + table += " ".format(self.template["dst_ip"]) + table += " ".format(self.template["dst_port"]) + table += " " + table += " " + table += "" for rec in sorted(self.whitelist, key=lambda k: k['resolution']): table += "" @@ -179,17 +204,18 @@ class Report(object): """ header = "
" header += "
" - header += "


Device name: {}
".format( - self.device["name"]) - header += "Device MAC address: {}
".format( - self.device["mac_address"]) - header += "Report generated on {}
".format( - datetime.now().strftime("%d/%m/%Y at %H:%M:%S")) - header += "Capture duration: {}s
".format( - self.capinfos["Capture duration"]) - header += "Number of packets: {}
".format( - self.capinfos["Number of packets"]) - header += "Capture SHA1: {}
".format(self.capture_sha1) + header += "


{}: {}
".format(self.template["device_name"], + self.device["name"]) + header += "{}: {}
".format(self.template["device_mac"], + self.device["mac_address"]) + header += "{} {}
".format(self.template["report_generated_on"], + datetime.now().strftime("%d/%m/%Y - %H:%M:%S")) + header += "{}: {}s
".format(self.template["capture_duration"], + self.capinfos["Capture duration"]) + header += "{}: {}
".format(self.template["packets_number"], + self.capinfos["Number of packets"]) + header += "{}: {}
".format( + self.template["capture_sha1"], self.capture_sha1) header += "

" header += "
" return header @@ -420,16 +446,16 @@ class Report(object): } @page { @top-center { - content: "REPORT_HEADER - Page " counter(page) " of " counter(pages) "."; + content: "REPORT_HEADER - Page " counter(page) " / " counter(pages) "."; font-size:12px; color:#CCC; } @bottom-center { - content: "This report has been autogenerated by a Tinycheck device. For any question, bug report or feedback, please contact tinycheck@kaspersky.com."; + content: REPORT_FOOTER; font-size:12px; color:#CCC; } } - """.replace("REPORT_HEADER", "Report for the capture {}".format(self.capture_sha1)) + """.replace("REPORT_HEADER", "{} {}".format(self.template["report_for_the_capture"], self.capture_sha1)).replace("REPORT_FOOTER", self.template["report_footer"]) diff --git a/analysis/classes/suricataengine.py b/analysis/classes/suricataengine.py index 98db0a2..6e7835c 100644 --- a/analysis/classes/suricataengine.py +++ b/analysis/classes/suricataengine.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from utils import get_iocs, get_apname, get_device +from utils import get_iocs, get_apname, get_device, get_config import time import os import subprocess as sp @@ -20,6 +20,14 @@ class SuricataEngine(): self.rules = [r[0] for r in get_iocs( "snort")] + self.generate_contextual_alerts() + self.userlang = get_config(("frontend", "user_lang")) + + # Load template language + if not re.match("^[a-z]{2,3}$", self.userlang): + self.userlang = "en" + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "lang/{}.json".format(self.userlang))) as f: + self.template = json.load(f)["alerts"] + def start_suricata(self): """ Launch suricata against the capture.pcap file. @@ -37,9 +45,8 @@ class SuricataEngine(): s = line.split("[**]")[1].strip() m = re.search( r"\[\d+\:(?P\d+)\:(?P\d+)\] (?P[ -~]+)", s) - self.alerts.append({"title": "Suricata rule tiggered: {}".format(m.group('title')), - "description": """A network detection rule has been tiggered. It's likely that your device has been compromised - or contains a malicious application.""", + self.alerts.append({"title": self.template["SNORT-01"]["title"].format(m.group('title')), + "description": self.template["SNORT-01"]["description"], "level": "High", "id": "SNORT-01"}) # Remove fast.log @@ -48,7 +55,7 @@ class SuricataEngine(): def generate_rule_file(self): """ Generate the rules file passed to suricata. - :return: bool if operation succeed. + :return: bool if operation succeed. """ try: with open(self.rules_file, "w+") as f: diff --git a/analysis/classes/zeekengine.py b/analysis/classes/zeekengine.py index eac38ce..367c803 100644 --- a/analysis/classes/zeekengine.py +++ b/analysis/classes/zeekengine.py @@ -10,6 +10,8 @@ import subprocess as sp import json import pydig import os +import re +import sys class ZeekEngine(object): @@ -24,10 +26,17 @@ class ZeekEngine(object): self.files = [] self.whitelist = [] - # Get analysis configuration + # Get analysis and userlang configuration self.heuristics_analysis = get_config(("analysis", "heuristics")) self.iocs_analysis = get_config(("analysis", "iocs")) self.whitelist_analysis = get_config(("analysis", "whitelist")) + self.userlang = get_config(("frontend", "user_lang")) + + # Load template language + if not re.match("^[a-z]{2,3}$", self.userlang): + self.userlang = "en" + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "lang/{}.json".format(self.userlang))) as f: + self.template = json.load(f)["alerts"] def fill_dns(self, dir): """ @@ -99,47 +108,41 @@ class ZeekEngine(object): # Check for UDP / ICMP (strange from a smartphone.) if c["proto"] in ["UDP", "ICMP"]: c["alert_tiggered"] = True - self.alerts.append({"title": "{} communication going outside the local network to {}.".format(c["proto"].upper(), c["resolution"]), - "description": "The {} protocol is commonly used in internal networks. Please, verify if the host {} leveraged other alerts which may ".format(c["proto"].upper(), c["resolution"]) - + "indicates a possible malicious behavior.", - "host": c["resolution"], + self.alerts.append({"title": self.template["PROTO-01"]["title"].format(c["proto"].upper(), c["resolution"]), + "description": self.template["PROTO-01"]["description"].format(c["proto"].upper(), c["resolution"]), + "host": c["resolution"], "level": "Moderate", "id": "PROTO-01"}) # Check for use of ports over 1024. if c["port_dst"] >= max_ports: c["alert_tiggered"] = True - self.alerts.append({"title": "{} connection to {} to a port over or equal to {}.".format(c["proto"].upper(), c["resolution"], max_ports), - "description": "{} connections have been seen to {} by using the port {}. The use of non-standard port can be sometimes associated to malicious activities. ".format(c["proto"].upper(), c["resolution"], c["port_dst"]) - + "We recommend to check if this host has a good reputation by looking on other alerts and search it on the internet.", - "host": c["resolution"], + self.alerts.append({"title": self.template["PROTO-02"]["title"].format(c["proto"].upper(), c["resolution"], max_ports), + "description": self.template["PROTO-02"]["description"].format(c["proto"].upper(), c["resolution"], c["port_dst"]), + "host": c["resolution"], "level": "Low", "id": "PROTO-02"}) # Check for use of HTTP. if c["service"] == "http" and c["port_dst"] == http_default_port: c["alert_tiggered"] = True - self.alerts.append({"title": "HTTP communications have been done to the host {}".format(c["resolution"]), - "description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol. ".format(c["resolution"]) - + "Even if this behavior is not malicious by itself, it is unusual to see HTTP communications issued from smartphone applications " - + "running in the background. Please check the host reputation by searching it on the internet.", - "host": c["resolution"], + self.alerts.append({"title": self.template["PROTO-03"]["title"].format(c["resolution"]), + "description": self.template["PROTO-03"]["description"].format(c["resolution"]), + "host": c["resolution"], "level": "Low", "id": "PROTO-03"}) # Check for use of HTTP on a non standard port. if c["service"] == "http" and c["port_dst"] != http_default_port: c["alert_tiggered"] = True - self.alerts.append({"title": "HTTP communications have been seen to the host {} on a non standard port ({}).".format(c["resolution"], c["port_dst"]), - "description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol on the port {}. ".format(c["resolution"], c["port_dst"]) - + "This behavior is quite unusual. Please check the host reputation by searching it on the internet.", - "host": c["resolution"], + self.alerts.append({"title": self.template["PROTO-04"]["title"].format(c["resolution"], c["port_dst"]), + "description": self.template["PROTO-04"]["description"].format(c["resolution"], c["port_dst"]), + "host": c["resolution"], "level": "Moderate", "id": "PROTO-04"}) # Check for non-resolved IP address. if c["ip_dst"] == c["resolution"]: c["alert_tiggered"] = True - self.alerts.append({"title": "The server {} hasn't been resolved by any DNS query during the session".format(c["ip_dst"]), - "description": "It means that the server {} is likely not resolved by any domain name or the resolution has already been cached by ".format(c["ip_dst"]) - + "the device. If the host appears in other alerts, please check it.", + self.alerts.append({"title": self.template["PROTO-05"]["title"].format(c["ip_dst"]), + "description": self.template["PROTO-05"]["description"].format(c["ip_dst"]), "host": c["ip_dst"], "level": "Low", "id": "PROTO-05"}) @@ -159,10 +162,8 @@ class ZeekEngine(object): for host in bl_hosts: if c["ip_dst"] == host[0]: c["alert_tiggered"] = True - self.alerts.append({"title": "A connection has been made to {} ({}) which is tagged as {}.".format(c["resolution"], c["ip_dst"], host[1].upper()), - "description": "The host {} has been explicitly blacklisted for malicious activities. Your device is likely compromised ".format(c["ip_dst"]) - + "and needs to be investigated more deeply by IT security professionals.", - + self.alerts.append({"title": self.template["IOC-01"]["title"].format(c["resolution"], c["ip_dst"], host[1].upper()), + "description": self.template["IOC-01"]["description"].format(c["ip_dst"]), "host": c["resolution"], "level": "High", "id": "IOC-01"}) @@ -171,10 +172,8 @@ class ZeekEngine(object): for cidr in bl_cidrs: if IPAddress(c["ip_dst"]) in cidr[0]: c["alert_tiggered"] = True - self.alerts.append({"title": "Communication to {} under the CIDR {} which is tagged as {}.".format(c["resolution"], cidr[0], cidr[1].upper()), - "description": "The server {} is hosted under a network which is known to host malicious activities. Even if this behavior is not malicious by itself, ".format(c["resolution"]) - + "you need to check if other alerts are also mentioning this host. If you have some doubts, please " - + "search this host on the internet to see if its legit or not.", + self.alerts.append({"title": self.template["IOC-02"]["title"].format(c["resolution"], cidr[0], cidr[1].upper()), + "description": self.template["IOC-02"]["description"].format(c["resolution"]), "host": c["resolution"], "level": "Moderate", "id": "IOC-02"}) @@ -183,42 +182,37 @@ class ZeekEngine(object): if c["resolution"].endswith(domain[0]): if domain[1] != "tracker": c["alert_tiggered"] = True - self.alerts.append({"title": "A DNS request have been done to {} which is tagged as {}.".format(c["resolution"], domain[1].upper()), - "description": "The domain name {} seen in the capture has been explicitly tagged as malicious. This indicates that ".format(c["resolution"]) - + "your device is likely compromised and needs to be investigated deeply.", + self.alerts.append({"title": self.template["IOC-03"]["title"].format(c["resolution"], domain[1].upper()), + "description": self.template["IOC-03"]["description"].format(c["resolution"]), "host": c["resolution"], "level": "High", "id": "IOC-03"}) else: c["alert_tiggered"] = True - self.alerts.append({"title": "A DNS request have been done to {} which is tagged as {}.".format(c["resolution"], domain[1].upper()), - "description": "The domain name {} seen in the capture has been explicitly tagged as a Tracker. This ".format(c["resolution"]) - + "indicates that one of the active apps is geo-tracking your moves.", + self.alerts.append({"title": self.template["IOC-04"]["title"].format(c["resolution"], domain[1].upper()), + "description": self.template["IOC-04"]["description"].format(c["resolution"]), "host": c["resolution"], "level": "Moderate", - "id": "IOC-03"}) + "id": "IOC-04"}) # Check for blacklisted FreeDNS. for domain in bl_freedns: if c["resolution"].endswith("." + domain[0]): c["alert_tiggered"] = True - self.alerts.append({"title": "A DNS request have been done to the domain {} which is a Free DNS.".format(c["resolution"]), - "description": "The domain name {} is using a Free DNS service. This kind of service is commonly used by cybercriminals ".format(c["resolution"]) - + "or state-sponsored threat actors during their operations. It is very suspicious that an application running in background use this kind of service, please investigate.", + self.alerts.append({"title": self.template["IOC-05"]["title"].format(c["resolution"]), + "description": self.template["IOC-05"]["description"].format(c["resolution"]), "host": c["resolution"], "level": "Moderate", - "id": "IOC-04"}) + "id": "IOC-05"}) # Check for suspect tlds. for tld in bl_tlds: if c["resolution"].endswith(tld[0]): c["alert_tiggered"] = True - self.alerts.append({"title": "A DNS request have been done to the domain {} which contains a suspect TLD.".format(c["resolution"]), - "description": "The domain name {} is using a suspect Top Level Domain ({}). Even not malicious, this non-generic TLD is used regularly by cybercrime ".format(c["resolution"], tld[0]) - + "or state-sponsored operations. Please check this domain by searching it on an internet search engine. If other alerts are related to this " - + "host, please consider it as very suspicious.", + self.alerts.append({"title": self.template["IOC-06"]["title"].format(c["resolution"]), + "description": self.template["IOC-06"]["description"].format(c["resolution"], tld[0]), "host": c["resolution"], "level": "Low", - "id": "IOC-05"}) + "id": "IOC-06"}) # Check for use of suspect nameservers. try: @@ -230,13 +224,11 @@ class ZeekEngine(object): for ns in bl_nameservers: if name_servers[0].endswith(".{}.".format(ns[0])): c["alert_tiggered"] = True - self.alerts.append({"title": "The domain {} is using a suspect nameserver ({}).".format(c["resolution"], name_servers[0]), - "description": "The domain name {} is using a nameserver that has been explicitly tagged to be associated to malicious activities. ".format(c["resolution"]) - + "Many cybercriminals and state-sponsored threat actors are using this kind of registrars because they allow cryptocurrencies and anonymous payments. It" - + " is adviced to investigate on this domain and the associated running application by doing a forensic analysis of the phone.", + self.alerts.append({"title": self.template["IOC-07"]["title"].format(c["resolution"], name_servers[0]), + "description": self.template["IOC-07"]["description"].format(c["resolution"]), "host": c["resolution"], "level": "Moderate", - "id": "IOC-06"}) + "id": "IOC-07"}) def files_check(self, dir): """ @@ -268,12 +260,11 @@ class ZeekEngine(object): if f["sha1"] == cert[0]: host = self.resolve(f["ip_dst"]) c["alert_tiggered"] = True - self.alerts.append({"title": "A certificate associated to {} activities have been found in the communication to {}.".format(cert[1].upper(), host), - "description": "The certificate ({}) associated to {} has been explicitly tagged as malicious. This indicates that ".format(f["sha1"], host) - + "your device is likely compromised and need a forensic analysis.", + self.alerts.append({"title": self.template["IOC-08"]["title"].format(cert[1].upper(), host), + "description": self.template["IOC-08"]["description"].format(f["sha1"], host), "host": host, "level": "High", - "id": "IOC-07"}) + "id": "IOC-08"}) def ssl_check(self, dir): """ @@ -306,30 +297,24 @@ class ZeekEngine(object): # Check for non generic SSL port. if cert["port"] not in ssl_default_ports: c["alert_tiggered"] = True - self.alerts.append({"title": "SSL connection done on a non standard port ({}) to {}".format(cert["port"], host), - "description": "It is not common to see SSL connections issued from smartphones using non-standard ports. Even this can be totally legit," - + " we recommend to check the reputation of {}, by looking at its WHOIS record, the associated autonomus system, its creation date, and ".format(host) - + " by searching it the internet.", + self.alerts.append({"title": self.template["SSL-01"]["title"].format(cert["port"], host), + "description": self.template["SSL-01"]["description"].format(host), "host": host, "level": "Moderate", "id": "SSL-01"}) # Check Free SSL certificates. if cert["issuer"] in free_issuers: c["alert_tiggered"] = True - self.alerts.append({"title": "An SSL connection to {} is using a free certificate.".format(host), - "description": "Free certificates — such as Let's Encrypt — are wildly used by command and control servers associated to " - + "malicious implants or phishing web pages. We recommend to check the host associated to this certificate, " - + "by looking at the domain name, its creation date, or by checking its reputation on the internet.", + self.alerts.append({"title": self.template["SSL-02"]["title"].format(host), + "description": self.template["SSL-02"]["description"], "host": host, "level": "Moderate", "id": "SSL-02"}) # Check for self-signed certificates. if cert["validation_status"] == "self signed certificate in certificate chain": c["alert_tiggered"] = True - self.alerts.append({"title": "The certificate associated to {} is self-signed.".format(host), - "description": "The use of self-signed certificates is a common thing for attacker infrastructure. We recommend to check the host {} ".format(host) - + "which is associated to this certificate, by looking at the domain name (if any), its WHOIS record, its creation date, and " - + " by checking its reputation on the internet.", + self.alerts.append({"title": self.template["SSL-03"]["title"].format(host), + "description": self.template["SSL-03"]["description"].format(host), "host": host, "level": "Moderate", "id": "SSL-03"}) @@ -349,8 +334,8 @@ class ZeekEngine(object): for host, nb in hosts.items(): if nb >= get_config(("analysis", "max_alerts")): - self.alerts.append({"title": "Check alerts for {}".format(host), - "description": "Please, check the reputation of the host {}, this one seems to be malicious as it leveraged {} alerts during the session.".format(host, nb), + self.alerts.append({"title": self.template["ADV-01"]["title"].format(host), + "description": self.template["ADV-01"]["description"].format(host, nb), "host": host, "level": "High", "id": "ADV-01"}) diff --git a/analysis/lang/en.json b/analysis/lang/en.json new file mode 100644 index 0000000..6d0e69f --- /dev/null +++ b/analysis/lang/en.json @@ -0,0 +1,108 @@ +{ + "alerts": { + "PROTO-01": { + "title": "{} communication going outside the local network to {}.", + "description": "The {} protocol is commonly used in internal networks. Please, verify if the host {} leveraged other alerts which may indicates a possible malicious behavior." + }, + "PROTO-02": { + "title": "{} connection to {} to a port over or equal to {}.", + "description": "{} connections have been seen to {} by using the port {}. The use of non-standard port can be sometimes associated to malicious activities. We recommend to check if this host has a good reputation by looking on other alerts and search it on the internet." + }, + "PROTO-03": { + "title": "HTTP communications have been done to the host {}", + "description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol. Even if this behavior is not malicious by itself, it is unusual to see HTTP communications issued from smartphone applications running in the background. Please check the host reputation by searching it on the internet." + }, + "PROTO-04": { + "title": "HTTP communications have been seen to the host {} on a non standard port ({}).", + "description": "Your device exchanged with the host {} by using HTTP, an unencrypted protocol on the port {}. This behavior is quite unusual. Please check the host reputation by searching it on the internet." + }, + "PROTO-05": { + "title": "The server {} hasn't been resolved by any DNS query during the session", + "description": "It means that the server {} is likely not resolved by any domain name or the resolution has already been cached by the device. If the host appears in other alerts, please check it." + }, + "IOC-01": { + "title": "A connection has been made to {} ({}) which is tagged as {}.", + "description": "The host {} has been explicitly blacklisted for malicious activities. Your device is likely compromised and needs to be investigated more deeply by IT security professionals." + }, + "IOC-02": { + "title": "Communication to {} under the CIDR {} which is tagged as {}.", + "description": "The server {} is hosted under a network which is known to host malicious activities. Even if this behavior is not malicious by itself, you need to check if other alerts are also mentioning this host. If you have some doubts, please search this host on the internet to see if its legit or not." + }, + "IOC-03": { + "title": "A DNS request have been done to {} which is tagged as {}.", + "description": "The domain name {} seen in the capture has been explicitly tagged as malicious. This indicates that your device is likely compromised and needs to be investigated deeply." + }, + "IOC-04": { + "title": "A DNS request have been done to {} which is tagged as {}.", + "description": "The domain name {} seen in the capture has been explicitly tagged as a Tracker. This indicates that one of the active apps is geo-tracking your moves." + }, + "IOC-05": { + "title": "A DNS request have been done to the domain {} which is a Free DNS.", + "description": "The domain name {} is using a Free DNS service. This kind of service is commonly used by cybercriminals or state-sponsored threat actors during their operations. It is very suspicious that an application running in background use this kind of service, please investigate.", + }, + "IOC-06": { + "title": "A DNS request have been done to the domain {} which contains a suspect TLD.", + "description": "The domain name {} is using a suspect Top Level Domain ({}). Even not malicious, this non-generic TLD is used regularly by cybercrime or state-sponsored operations. Please check this domain by searching it on an internet search engine. If other alerts are related to this host, please consider it as very suspicious." + }, + "IOC-07": { + "title": "The domain {} is using a suspect nameserver ({}).", + "description": "The domain name {} is using a nameserver that has been explicitly tagged to be associated to malicious activities. Many cybercriminals and state-sponsored threat actors are using this kind of registrars because they allow cryptocurrencies and anonymous payments. It is adviced to investigate on this domain and the associated running application by doing a forensic analysis of the phone." + }, + "IOC-08": { + "title": "A certificate associated to {} activities have been found in the communication to {}.", + "description": "The certificate ({}) associated to {} has been explicitly tagged as malicious. This indicates that your device is likely compromised and need a forensic analysis." + }, + "SSL-01": { + "title": "SSL connection done on a non standard port ({}) to {}", + "description": "It is not common to see SSL connections issued from smartphones using non-standard ports. Even this can be totally legit, we recommend to check the reputation of {}, by looking at its WHOIS record, the associated autonomus system, its creation date, and by searching it the internet.", + }, + "SSL-02": { + "title": "An SSL connection to {} is using a free certificate.", + "description": "Free certificates — such as Let's Encrypt — are wildly used by command and control servers associated to malicious implants or phishing web pages. We recommend to check the host associated to this certificate, by looking at the domain name, its creation date, or by checking its reputation on the internet." + }, + "SSL-03": { + "title": "The certificate associated to {} is self-signed.", + "description": "The use of self-signed certificates is a common thing for attacker infrastructure. We recommend to check the host {} which is associated to this certificate, by looking at the domain name (if any), its WHOIS record, its creation date, and by checking its reputation on the internet." + }, + "ADV-01": { + "title": "Check the alerts for {}", + "description": "Please, check the reputation of the host {}, this one seems to be malicious as it leveraged {} alerts during the session." + }, + "SNORT-01": { + "title": "Suricata rule tiggered: {}", + "description": "A network detection rule has been triggered. It's likely that your device has been compromised or have some suspect behaviour." + }, + }, + "report": { + "numbers": [ + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine" + ], + "suspect_title": "Suspect communications", + "uncat_title": "Uncategorized communications", + "whitelist_title": "Whitelisted communications", + "protocol": "Protocol", + "domain": "Domain", + "dst_ip": "Dst IP address", + "dst_port": "Dst port number", + "device_name": "Device name", + "device_mac": "Device MAC address", + "report_generated_on": "Report generated on", + "capture_duration": "Capture duration", + "packets_number": "Number of packets", + "capture_sha1": "Capture SHA1", + "report_for_the_capture": "Report for the capture", + "report_footer": "This report has been autogenerated by a Tinycheck device. For any question, bug report or feedback, please contact tinycheck@kaspersky.com.", + "high_msg": "Your device seems to be compromised as you have {} high alert(s).", + "moderate_msg": "You have {} moderate alert(s), your device might be compromised. Please look at them carefully.", + "low_msg": "You have only {} low alert(s), don't hesitate to check them.", + "none_msg": "Everything looks fine, zero alerts. Don't hesitate to check the uncategorized communications, if any." + } +} diff --git a/analysis/lang/fr.json b/analysis/lang/fr.json new file mode 100644 index 0000000..5dca018 --- /dev/null +++ b/analysis/lang/fr.json @@ -0,0 +1,108 @@ +{ + "alerts": { + "PROTO-01": { + "title": "Une communication {} va à l'extérieur du réseau jusqu'à {}.", + "description": "Le protocole {} est généralement utilisé dans des réseaux internes. Vérifiez si le serveur {} a soulevé d'autres alertes. Ceci pourrait indiquer la présence d'une possible application malveillante." + }, + "PROTO-02": { + "title": "Connexion {} vers {} sur un port au dessus ou égal à {}.", + "description": "Des connexions {} vers {} ont été vues utilisant le port {}. L'utilisation d'un numéro de port non standard peut être associé à des activités malveillantes. Nous recommandons de vérifier si ce serveur possède une bonne réputation en regardant les autres alertes et en le cherchant sur Internet." + }, + "PROTO-03": { + "title": "Des communications HTTP on été réalisées vers {}", + "description": "Votre appareil a communiqué avec le serveur {} en utilisant HTTP, un protocole non chiffré. Même si ce n'est pas malveillant en tant que tel, il est rare de voir des communications HTTP issues d'applications installées sur des smartphones exécutées en arrière plan. Il est conseillé de vérifier la réputation du serveur en le recherchant sur Internet." + }, + "PROTO-04": { + "title": "Des communications HTTP ont été réalisées vers {} sur un port non standard ({}).", + "description": "Votre appareil a communiqué vers le serveur {} en utilisant HTTP, un protocole non chiffré sur le port {}. Ce type de communication peut être signe d'une activité malveillante sur votre appareil car il est très rare qu'HTTP utilise ce port. Il est conseillé de vérifier la réputation du serveur en le recherchant sur Internet." + }, + "PROTO-05": { + "title": "Le serveur {} n'a pas été résolu par le protocole DNS durant la session", + "description": "Cela signifie que le serveur {} ne possède pas de nom de domaine associé ou que sa résolution a déjà été mise en cache par votre appareil. Si le serveur apparait dans d'autres alertes, merci de vérifier sa réputation." + }, + "IOC-01": { + "title": "Connexion vers {} ({}) qui est tagué en tant que {}.", + "description": "Le serveur {} est connu pour être associé à des activités malveillantes. Votre appareil est surement compromis et doit être investigué plus en détails par une équipe professionnelle." + }, + "IOC-02": { + "title": "Connexion vers {} appartenant au bloc réseau {} qui est tagué en tant que {}.", + "description": "Le serveur {} est hébergé dans un réseau qui est connu pour abriter des activités malveillantes. Même si ce n'est pas malveillant en tant que tel, vous devez regarder si d'autres alertes mentionnent ce serveur. Si vous avez certains doutes, recherchez sur Internet ce serveur pour savoir s'il semble être légitime ou non." + }, + "IOC-03": { + "title": "Requête DNS vers le domaine {} qui est tagué en tant que {}.", + "description": "Le serveur {} vers lequel communique votre appareil a été explicitement catégorisé en tant que malveillant. Votre appareil est sûrement compromis et doit être investigué plus en détails par une équipe professionnelle." + }, + "IOC-04": { + "title": "Requête DNS vers le domaine {} qui est tagué en tant que {}.", + "description": "Le nom de domaine {} vers lequel communique votre appareil a été explicitement catégorisé comme un tracker. Ceci indique d'une application active sur votre appareil est entrain de vous géo-localiser." + }, + "IOC-05": { + "title": "Requête DNS vers le domaine {} qui est un domaine gratuit.", + "description": "Le nom de domaine {} utilise un service de noms de domaine gratuits. Ce type de service est couramment utilisé par les cybercriminels ou des acteurs associés à des États au cours de leurs opérations d'espionnage. Il est très suspect qu'une application exécutée en arrière-plan utilise ce type de service, veuillez enquêter." + }, + "IOC-06": { + "title": "Requête DNS vers le domaine {} contenant une extension suspecte.", + "description": "Le nom de domaine {} utilise une extension suspecte ({}). Même si cela n'est pas malveillant en-soi, l'utilisation d'une extension non générique est l'apanage d'acteurs cybercriminels et étatiques durant leurs opérations. Veuillez vérifier la pertinance de ce domaine en le recherchant sur un moteur de recherche Internet. Si d'autres alertes sont liées à ce dernier, veuillez le considérer comme très suspect." + }, + "IOC-07": { + "title": "Le domaine {} utilise un serveur de noms suspect ({}).", + "description": "Le nom de domaine {} utilise un server de nom qui a été explicitement catégorisé comme associé à des activités malveillantes. Plusieurs cybercriminels et acteurs étatiques utilisent ce type de serveurs de noms car ils autorisent les paiements anonymes grâce aux cryptomonnaies. Il est conseillé d'investiguer sur ce domaine et l'application s'y connectant en réalisant une analyse post-mortem de l'appareil analysé." + }, + "IOC-08": { + "title": "Un certificat associé à des activités de {} a été vu lors de communications vers {}.", + "description": "Le certificat ({}) associé au serveur {} a été explicitement catégorisé comme malveillant. Votre appareil est sûrement compromis et doit être investigué plus en détails par une équipe professionnelle." + }, + "SSL-01": { + "title": "Connexion SSL utilisant un port non standard ({}) vers {}", + "description": "Il n'est pas commun de voir des connexions SSL issues de smartphones utiliser des ports non standards. Même si cela peut être totalement légitime, il est recommandé d'évaluer la réputation du serveur {}, en regardant son enregistrement WHOIS, son système autonome, sa date de création et en le recherchant sur Internet." + }, + "SSL-02": { + "title": "Une connexion SSL vers {} utilise un certificat gratuit.", + "description": "Les certificats gratuits — tels que Let's Encrypt - sont largement utilisés par des serveurs de commande et de contrôle associés à des implants malveillants ou à des pages Web de phishing. Nous vous recommandons de vérifier le serveur associé à ce certificat, en regardant le nom de domaine, sa date de création, ou en vérifiant sa réputation sur Internet." + }, + "SSL-03": { + "title": "Le certificat associé à {} est auto-signé.", + "description": "L'utilisation de certificats auto-signés est une chose courante pour des infrastructures d'attaque associées à des activités malveillantes. Nous vous recommandons de vérifier le serveur {} qui est associé à ce certificat, en regardant le nom de domaine (le cas échéant), son enregistrement WHOIS, sa date de création, et en vérifiant sa réputation sur Internet." + }, + "ADV-01": { + "title": "Vérifiez les alertes liées au serveur {}", + "description": "Merci de vérifier la réputation et les alertes liées au serveur {}, ce dernier semble malveillant, ayant engendré {} alertes durant la session de capture." + }, + "SNORT-01": { + "title": "Règle suricata déclanchée : {}", + "description": "Une règle de détection de réseau a été déclenchée. Il est probable que votre appareil ait été compromis ou présente un comportement suspect." + } + }, + "report": { + "numbers": [ + "un", + "deux", + "trois", + "quatre", + "cinq", + "six", + "sept", + "huit", + "neuf" + ], + "suspect_title": "Communications suspectes", + "uncat_title": "Communications non catégorisées", + "whitelist_title": "Communications légitimes", + "protocol": "Protocole", + "domain": "Domaine", + "dst_ip": "Adresse IP", + "dst_port": "Port", + "device_name": "Nom de l'appareil", + "device_mac": "Adresse MAC de l'appareil", + "report_generated_on": "Rapport généré le ", + "capture_duration": "Durée de la capture", + "packets_number": "Nombre de paquets", + "capture_sha1": "SHA1 de la capture", + "report_for_the_capture": "Rapport pour la capture", + "report_footer": "Ce rapport a été automatiquement généré par une instance de TinyCheck. Pour toute question et retours, n'hésitez pas à contacter tinycheck@kaspersky.com.", + "high_msg": "Votre appareil semble être compromis car vous avez {} alerte(s) élevée(s).", + "moderate_msg": "Vous avez {} alerte(s) modérée(s), votre appareil peut être compromis. Regardez ces alertes en détail.", + "low_msg": "Vous avez uniquement {} alerte(s) faibles, n'hésitez pas à les regarder, on ne sait jamais...", + "none_msg": "Toute semble normal, vous avez aucune alerte. Cependant, n'hésitez pas à regarder les communications non catégorisées." + } +} From b8d165178c896f15c3cc7ece3c8c48c2886fc052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Thu, 4 Feb 2021 15:22:54 +0100 Subject: [PATCH 03/17] Correcting typo --- server/frontend/app/blueprints/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/frontend/app/blueprints/misc.py b/server/frontend/app/blueprints/misc.py index aa76d4a..a2c7a6b 100644 --- a/server/frontend/app/blueprints/misc.py +++ b/server/frontend/app/blueprints/misc.py @@ -62,7 +62,7 @@ def get_config(): }) -@app.route("/get-lang", methods=["GET"]) +@misc_bp.route("/get-lang", methods=["GET"]) def get_lang(): """ Get the user lang defined in the config.yaml From dfbd920c6c766b191888e1431e95718968e3c6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Thu, 4 Feb 2021 15:47:03 +0100 Subject: [PATCH 04/17] Several corrections --- analysis/classes/report.py | 2 +- server/frontend/app/assets/lang/fr.json | 6 +++--- server/frontend/app/blueprints/misc.py | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/analysis/classes/report.py b/analysis/classes/report.py index 20a6b67..f823708 100644 --- a/analysis/classes/report.py +++ b/analysis/classes/report.py @@ -31,7 +31,7 @@ class Report(object): except: self.capture_sha1 = "N/A" - self.userlang = get_config(("frontend", "userlang")) + self.userlang = get_config(("frontend", "user_lang")) # Load template language if not re.match("^[a-z]{2}$", self.userlang): diff --git a/server/frontend/app/assets/lang/fr.json b/server/frontend/app/assets/lang/fr.json index 375bcd8..2312396 100644 --- a/server/frontend/app/assets/lang/fr.json +++ b/server/frontend/app/assets/lang/fr.json @@ -6,7 +6,7 @@ }, "analysis": { "question": "Voulez-vous analyser les communications capturées ?", - "no_btn": "Non, enregistrez-les directement.", + "no_btn": "Non, enregistrez-les.", "yes_btn": "Oui, allons-y !", "please_wait_msg": "Merci d'attendre pendant l'analyse...", "some_time_msg": "Oui, cela peut prendre du temps..." @@ -42,11 +42,11 @@ "dix", "onze" ], - "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, merci de regarder le rapport.", + "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, affichez le rapport.", "location_msg": "Une application dévoile votre<br /> géo-localisation actelle.", "fine_msg": "Tout semble OK, vous avez aucue alerte.", "high_msg": "Vous avez {nb} alerte(s) élevée(s),<br />votre appareil semble être compromis.", - "moderate_msg": "Vous avez {nb} alerte(s) moyenne(s),<br />votre appareil peut être compromis.", + "moderate_msg": "Vous avez {nb} alerte(s) moyenne(s),<br />votre appareil est peut-être compromis.", "low_msg": "Vous avez uniquement {nb} alerte(s) basse(s)<br /> n'hésitez pas à les lire.", "save_report": "Sauvegarder le rapport", "report_of": "Rapport de", diff --git a/server/frontend/app/blueprints/misc.py b/server/frontend/app/blueprints/misc.py index a2c7a6b..95c5c18 100644 --- a/server/frontend/app/blueprints/misc.py +++ b/server/frontend/app/blueprints/misc.py @@ -5,6 +5,8 @@ import subprocess as sp from flask import Blueprint, jsonify from app.utils import read_config import re +import sys +import os misc_bp = Blueprint("misc", __name__) @@ -70,8 +72,8 @@ def get_lang(): """ lang = read_config(("frontend", "user_lang")) if re.match("^[a-z]{2,3}$", lang): - with open("app/assets/lang/{}.json".format(lang), "r") as f: + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "app/assets/lang/{}.json".format(lang)), "r") as f: return(f.read()) else: - with open("app/assets/lang/en.json", "r") as f: + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "app/assets/lang/en.json"), "r") as f: return(f.read()) From fb9c28d609170aa9870d69f05b4d5a148bb4f960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Thu, 4 Feb 2021 16:04:59 +0100 Subject: [PATCH 05/17] Several corrections --- analysis/classes/suricataengine.py | 1 + server/frontend/app/assets/lang/fr.json | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/analysis/classes/suricataengine.py b/analysis/classes/suricataengine.py index 6e7835c..268f6b0 100644 --- a/analysis/classes/suricataengine.py +++ b/analysis/classes/suricataengine.py @@ -7,6 +7,7 @@ import os import subprocess as sp import re import json +import sys class SuricataEngine(): diff --git a/server/frontend/app/assets/lang/fr.json b/server/frontend/app/assets/lang/fr.json index 2312396..ca9fc55 100644 --- a/server/frontend/app/assets/lang/fr.json +++ b/server/frontend/app/assets/lang/fr.json @@ -26,8 +26,8 @@ }, "report": { "show_full_report": "Afficher le rapport complet", - "start_new_capture": "Lancer une nouvelle capture", - "save_capture": "Sauvegarder la capture", + "start_new_capture": "Nouvelle capture", + "save_capture": "Sauvegarder", "numbers": [ "zéro", "une", @@ -42,7 +42,7 @@ "dix", "onze" ], - "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, affichez le rapport.", + "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, lisez le rapport.", "location_msg": "Une application dévoile votre<br /> géo-localisation actelle.", "fine_msg": "Tout semble OK, vous avez aucue alerte.", "high_msg": "Vous avez {nb} alerte(s) élevée(s),<br />votre appareil semble être compromis.", @@ -50,8 +50,8 @@ "low_msg": "Vous avez uniquement {nb} alerte(s) basse(s)<br /> n'hésitez pas à les lire.", "save_report": "Sauvegarder le rapport", "report_of": "Rapport de", - "ip_address": "Adresse IP ", - "mac_address": "Adresse MAC", + "ip_address": "Adresse IP :", + "mac_address": "Adresse MAC :", "high": "elevee", "moderate": "moyenne", "low": "base" From b759054908a2e756d03e2e8991d9c2ea72357b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Thu, 4 Feb 2021 16:13:05 +0100 Subject: [PATCH 06/17] Several corrections --- analysis/lang/fr.json | 4 ++-- server/frontend/app/assets/lang/fr.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/analysis/lang/fr.json b/analysis/lang/fr.json index 5dca018..47eed56 100644 --- a/analysis/lang/fr.json +++ b/analysis/lang/fr.json @@ -9,7 +9,7 @@ "description": "Des connexions {} vers {} ont été vues utilisant le port {}. L'utilisation d'un numéro de port non standard peut être associé à des activités malveillantes. Nous recommandons de vérifier si ce serveur possède une bonne réputation en regardant les autres alertes et en le cherchant sur Internet." }, "PROTO-03": { - "title": "Des communications HTTP on été réalisées vers {}", + "title": "Des communications HTTP ont été réalisées vers {}", "description": "Votre appareil a communiqué avec le serveur {} en utilisant HTTP, un protocole non chiffré. Même si ce n'est pas malveillant en tant que tel, il est rare de voir des communications HTTP issues d'applications installées sur des smartphones exécutées en arrière plan. Il est conseillé de vérifier la réputation du serveur en le recherchant sur Internet." }, "PROTO-04": { @@ -105,4 +105,4 @@ "low_msg": "Vous avez uniquement {} alerte(s) faibles, n'hésitez pas à les regarder, on ne sait jamais...", "none_msg": "Toute semble normal, vous avez aucune alerte. Cependant, n'hésitez pas à regarder les communications non catégorisées." } -} +} \ No newline at end of file diff --git a/server/frontend/app/assets/lang/fr.json b/server/frontend/app/assets/lang/fr.json index ca9fc55..cb03a14 100644 --- a/server/frontend/app/assets/lang/fr.json +++ b/server/frontend/app/assets/lang/fr.json @@ -19,13 +19,13 @@ "network_name": "Nom du réseau", "network_password": "Mot de passe du réseau", "tap_msg": "Appuyez sur le cadre blanc pour générer un nouveau réseau.", - "generate_ap_msg": "Nous générons pour vous un réseau Wi-Fi éphémère.", + "generate_ap_msg": "Nous générons un réseau Wi-Fi éphémère.", "error_msg1": "Malheureusement, nous rencontrons des problèmes <br />lors de la création du point d'accès.", "error_msg2": "Veuillez vérifier que vous disposez de <br /> deux interfaces WiFi sur votre appareil.", "restart_btn": "Restart the device" }, "report": { - "show_full_report": "Afficher le rapport complet", + "show_full_report": "Lire le rapport complet", "start_new_capture": "Nouvelle capture", "save_capture": "Sauvegarder", "numbers": [ From 8b3cc56b08f26652253be7d53fbfbbe22b59145e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 16:34:32 +0100 Subject: [PATCH 07/17] Adding translation by using i18n --- app/frontend/package-lock.json | 210 ++++++++++++++--------- app/frontend/package.json | 5 +- app/frontend/src/App.vue | 27 ++- app/frontend/src/locales/en.json | 81 +++++++++ app/frontend/src/locales/fr.json | 81 +++++++++ app/frontend/src/main.js | 3 + app/frontend/src/plugins/i18n.js | 13 ++ app/frontend/src/views/analysis.vue | 25 ++- app/frontend/src/views/capture.vue | 30 ++-- app/frontend/src/views/generate-ap.vue | 34 ++-- app/frontend/src/views/home.vue | 15 +- app/frontend/src/views/report.vue | 65 +++---- app/frontend/src/views/save-capture.vue | 38 ++-- app/frontend/src/views/splash-screen.vue | 44 ++--- app/frontend/src/views/wifi-select.vue | 56 +++--- server/frontend/app/assets/lang/en.json | 81 --------- server/frontend/app/assets/lang/fr.json | 81 --------- server/frontend/app/blueprints/misc.py | 34 ++-- 18 files changed, 456 insertions(+), 467 deletions(-) create mode 100644 app/frontend/src/locales/en.json create mode 100644 app/frontend/src/locales/fr.json create mode 100644 app/frontend/src/plugins/i18n.js delete mode 100644 server/frontend/app/assets/lang/en.json delete mode 100644 server/frontend/app/assets/lang/fr.json diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json index 96fc6af..1a12fee 100644 --- a/app/frontend/package-lock.json +++ b/app/frontend/package-lock.json @@ -1806,6 +1806,16 @@ "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "cacache": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", @@ -1832,6 +1842,34 @@ "unique-filename": "^1.1.1" } }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -1853,6 +1891,25 @@ "path-exists": "^4.0.0" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1917,6 +1974,16 @@ "minipass": "^3.1.1" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "terser-webpack-plugin": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", @@ -1933,6 +2000,18 @@ "terser": "^4.6.12", "webpack-sources": "^1.4.3" } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.1.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", + "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + } } } }, @@ -4559,6 +4638,24 @@ "domelementtype": "1" } }, + "dot-object": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz", + "integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==", + "dev": true, + "requires": { + "commander": "^4.0.0", + "glob": "^7.1.5" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + } + } + }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -6798,6 +6895,12 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -11236,6 +11339,32 @@ "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", "dev": true }, + "vue-i18n": { + "version": "8.22.4", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.22.4.tgz", + "integrity": "sha512-XLI5s0AdqMP2Lf4I4CmdmOq8kjb5DDFGR77wAuxCfpEuYSfhTRyyx6MetgZMiL6Lxa0DasjBOiOcciU3NkL3/Q==" + }, + "vue-i18n-extract": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-1.1.10.tgz", + "integrity": "sha512-DYqcjrAm4L95Ftz8AVIzUyrHCBQV326rbOru6B3W2vXHIzUUuJwwFgyVPE+d7YTeHD189eoOkRu0aL6EzeMWZQ==", + "dev": true, + "requires": { + "commander": "^6.1.0", + "dot-object": "^2.1.4", + "glob": "^7.1.6", + "is-valid-glob": "^1.0.0", + "js-yaml": "^3.14.0" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + } + } + }, "vue-loader": { "version": "15.9.3", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz", @@ -11257,87 +11386,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.1.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", - "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-router": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.3.tgz", diff --git a/app/frontend/package.json b/app/frontend/package.json index 073ee82..5655dd9 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -5,7 +5,8 @@ "scripts": { "serve": "vue-cli-service serve --copy --port=4202", "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "lint": "vue-cli-service lint", + "vue-i18n-extract": "vue-i18n-extract report -a -v './src/**/*.?(js|vue)' -l './src/locales/*.?(json|yaml|yml)'" }, "dependencies": { "@fnando/sparkline": "^0.3.10", @@ -15,6 +16,7 @@ "sass-loader": "^10.0.4", "simple-keyboard": "^2.30.25", "vue": "^2.6.12", + "vue-i18n": "^8.22.4", "vue-router": "^3.4.3" }, "devDependencies": { @@ -24,6 +26,7 @@ "babel-eslint": "^10.1.0", "eslint": "^7.9.0", "eslint-plugin-vue": "^6.2.2", + "vue-i18n-extract": "1.1.10", "vue-template-compiler": "^2.6.12" }, "eslintConfig": { diff --git a/app/frontend/src/App.vue b/app/frontend/src/App.vue index 32c8dea..cca5c1d 100644 --- a/app/frontend/src/App.vue +++ b/app/frontend/src/App.vue @@ -29,24 +29,37 @@ <script> import axios from 'axios' - document.title = 'TinyCheck Frontend' import Controls from "@/components/Controls.vue" + document.title = 'TinyCheck Frontend' + export default { name: 'app', components: { Controls }, methods: { - get_lang: function() { - axios.get(`/api/misc/get-lang`, { timeout: 60000 }) - .then(response => { window.translation = response.data; }) - .catch(error => { console.log(error) }); + set_lang: function() { + if (window.config.user_lang) { + var lang = window.config.user_lang + if (Object.keys(this.$i18n.messages).includes(lang)) { + this.$i18n.locale = lang + document.querySelector('html').setAttribute('lang', lang) + } + } + }, + get_config: function() { + axios.get('/api/misc/config', { timeout: 60000 }) + .then(response => { + this.set_lang(); + window.config = response.data + }) + .catch(error => { console.log(error) }); } }, created: function() { - window.translation = {} - this.get_lang() + window.config = {} + this.get_config(); } } </script> diff --git a/app/frontend/src/locales/en.json b/app/frontend/src/locales/en.json new file mode 100644 index 0000000..e7786bf --- /dev/null +++ b/app/frontend/src/locales/en.json @@ -0,0 +1,81 @@ +{ + "home": { + "welcome_msg": "Welcome to TinyCheck.", + "help_msg": "We are going to help you to check your device.", + "start_btn": "Let's start!" + }, + "analysis": { + "question": "Do you want to analyze the captured communications?", + "no_btn": "No, just save them", + "yes_btn": "Yes, let's do it", + "please_wait_msg": "Please wait during the analysis...", + "some_time_msg": "Yes, it can take some time..." + }, + "capture": { + "intercept_coms_msg": "Intercepting the communications of ", + "stop_btn": "Stop the capture" + }, + "generate-ap": { + "network_name": "Network name", + "network_password": "Network password", + "tap_msg": "Tap the white frame to generate a new network.", + "generate_ap_msg": "We generate an ephemeral network for you.", + "error_msg1": "Unfortunately, we got some issues <br />during the AP creation.", + "error_msg2": "Please verify that you've two WiFi interfaces on your device<br /> and try again by restarting it.", + "restart_btn": "Restart the device" + }, + "report": { + "show_full_report": "Show the full report", + "start_new_capture": "Start a new capture", + "save_capture": "Save the capture", + "numbers": [ + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "ten", + "eleven" + ], + "stalkerware_msg": "Your device is compromised by<br />a Stalkerware, please check the report.", + "location_msg": "An application is sharing your<br /> current geolocation with a third party.", + "fine_msg": "Everything looks fine, zero alerts.", + "high_msg": "You have {nb} high alert,<br />your device seems to be compromised.", + "moderate_msg": "You have {nb} moderate alerts,<br />your device might be compromised.", + "low_msg": "You have only {nb} low alerts,<br /> don't hesitate to check them.", + "save_report": "Save the report", + "report_of": "Report of", + "ip_address": "IP address", + "mac_address": "MAC address", + "high": "high", + "moderate": "moderate", + "low": "low" + }, + "wifi-select": { + "already_connected_question": "You seem to be already connected to a network.<br />Do you want to use the current connection?", + "no_btn": "No, use another", + "yes_btn": "Yes, use it.", + "wifi_name": "Wi-Fi name", + "refresh_btn": "Refresh networks list", + "not_connected": "You seem to not be connected to Internet.", + "please_config": "Please configure the Wi-Fi connection.", + "lets_do_btn": "Ok, let's do that.", + "wifi_connected": "Wi-Fi connected", + "wifi_not_connected": "Wi-Fi not connected. Please retry.", + "tap_keyboard": "Tap on the virtual keyboard to start", + "wifi_password": "Wi-Fi password", + "connect_to_it": "Connect to it" + }, + "save-capture": { + "please_connect": "Please connect a USB key to save your capture.", + "we_are_saving": "We are saving your capture.", + "tap_msg": "You can tap the USB key to start a new capture.", + "capture_download": "The capture download is going to start...", + "start_capture_btn": "Start another capture" + } +} \ No newline at end of file diff --git a/app/frontend/src/locales/fr.json b/app/frontend/src/locales/fr.json new file mode 100644 index 0000000..4447bd8 --- /dev/null +++ b/app/frontend/src/locales/fr.json @@ -0,0 +1,81 @@ +{ + "home": { + "welcome_msg": "Bienvenue sur TinyCheck.", + "help_msg": "Nous allons vous accompagner pour analyser votre appareil.", + "start_btn": "Allons-y !" + }, + "analysis": { + "question": "Voulez-vous analyser les communications capturées ?", + "no_btn": "Non, enregistrez-les.", + "yes_btn": "Oui, allons-y !", + "please_wait_msg": "Merci d'attendre pendant l'analyse...", + "some_time_msg": "Oui, cela peut prendre du temps..." + }, + "capture": { + "intercept_coms_msg": "Interception des communications de ", + "stop_btn": "Arrêter la capture" + }, + "generate-ap": { + "network_name": "Nom du réseau Wi-Fi", + "network_password": "Mot de passe", + "tap_msg": "Appuyez sur le cadre blanc pour générer un nouveau réseau.", + "generate_ap_msg": "Nous générons un réseau Wi-Fi éphémère.", + "error_msg1": "Malheureusement, nous rencontrons des problèmes <br />lors de la création du point d'accès.", + "error_msg2": "Veuillez vérifier que vous disposez de <br /> deux interfaces WiFi sur votre appareil.", + "restart_btn": "Restart the device" + }, + "report": { + "show_full_report": "Lire le rapport complet", + "start_new_capture": "Nouvelle capture", + "save_capture": "Sauvegarder", + "numbers": [ + "zéro", + "une", + "deux", + "trois", + "quatre", + "cinq", + "six", + "sept", + "huit", + "neuf", + "dix", + "onze" + ], + "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, lisez le rapport.", + "location_msg": "Une application dévoile votre<br /> géo-localisation actelle.", + "fine_msg": "Tout semble OK, vous avez aucue alerte.", + "high_msg": "Vous avez {nb} alerte.s élevée.s,<br />votre appareil semble être compromis.", + "moderate_msg": "Vous avez {nb} alerte.s moyenne.s,<br />votre appareil est peut-être compromis.", + "low_msg": "Vous avez uniquement {nb} alerte.s basse.s<br /> n'hésitez pas à les parcourir.", + "save_report": "Sauvegarder le rapport", + "report_of": "Rapport de", + "ip_address": "Adresse IP :", + "mac_address": "Adresse MAC :", + "high": "elevee", + "moderate": "moyenne", + "low": "base" + }, + "wifi-select": { + "already_connected_question": "Vous semblez être connecté à un réseau.<br />Voulez-vous utiliser cette connexion ?", + "no_btn": "Non, une autre", + "yes_btn": "Oui, utilisez celle-là", + "wifi_name": "Nom du réseau", + "refresh_btn": "Rafraichir la liste des réseaux", + "not_connected": "Vous semblez ne pas être connecté à Internet.", + "please_config": "Configurez la connexion Wi-Fi.", + "lets_do_btn": "Oui, allons-y.", + "wifi_connected": "Wi-Fi connecté", + "wifi_not_connected": "Wi-Fi non connecté. Ressayez.", + "tap_keyboard": "Appuez sur cet espace pour écrire", + "wifi_password": "Mot de passe", + "connect_to_it": "Se connecter" + }, + "save-capture": { + "please_connect": "Connectez une clé USB pour enregistrer votre capture.", + "we_are_saving": "Nous enregistrons votre capture.", + "tap_msg": "Vous pouvez appuyer sur l'animation pour lancer une nouvelle capture.", + "capture_download": "Le téléchargement de la capture va se lancer...", + "start_capture_btn": "Lancer une nouvelle capture" + } +} \ No newline at end of file diff --git a/app/frontend/src/main.js b/app/frontend/src/main.js index 3c99e4e..7b84c01 100644 --- a/app/frontend/src/main.js +++ b/app/frontend/src/main.js @@ -1,10 +1,13 @@ import Vue from 'vue' import App from './App.vue' import router from './router' +import { i18n } from '@/plugins/i18n' + Vue.config.productionTip = true Vue.config.devtools = true new Vue({ router, + i18n, render: h => h(App) }).$mount('#app') \ No newline at end of file diff --git a/app/frontend/src/plugins/i18n.js b/app/frontend/src/plugins/i18n.js new file mode 100644 index 0000000..d48a14b --- /dev/null +++ b/app/frontend/src/plugins/i18n.js @@ -0,0 +1,13 @@ +import Vue from 'vue' +import VueI18n from 'vue-i18n' + +Vue.use(VueI18n) + +export const i18n = new VueI18n({ + locale: 'en', + fallbackLocale: 'en', + messages: { + 'en': require('@/locales/en.json'), + 'fr': require('@/locales/fr.json') + } +}) \ No newline at end of file diff --git a/app/frontend/src/views/analysis.vue b/app/frontend/src/views/analysis.vue index 9ce84ac..c852ed7 100644 --- a/app/frontend/src/views/analysis.vue +++ b/app/frontend/src/views/analysis.vue @@ -1,15 +1,15 @@ <template> <div class="center"> <div v-if="question"> - <p>{{ translation.question }}</p> + <p>{{ $t("analysis.question") }}</p> <div class="empty-action"> - <button class="btn" v-on:click="save_capture()">{{ translation.no_btn }}</button> <button class="btn btn-primary" v-on:click="start_analysis()">{{ translation.yes_btn }}</button> + <button class="btn" v-on:click="save_capture()">{{ $t("analysis.no_btn") }}</button> <button class="btn btn-primary" v-on:click="start_analysis()">{{ $t("analysis.yes_btn") }}</button> </div> </div> <div v-else-if="running"> <img src="@/assets/loading.svg"/> - <p class="legend" v-if="!long_waiting">{{ translation.please_wait_msg }}</p> - <p class="legend fade-in" v-if="long_waiting">{{ translation.some_time_msg }}</p> + <p class="legend" v-if="!long_waiting">{{ $t("analysis.please_wait_msg") }}</p> + <p class="legend fade-in" v-if="long_waiting">{{ $t("analysis.some_time_msg") }}</p> </div> </div> </template> @@ -39,7 +39,7 @@ export default { setTimeout(function () { this.long_waiting = true }.bind(this), 15000); axios.get(`/api/analysis/start/${this.capture_token}`, { timeout: 60000 }) .then(response => { - if(response.data.message == "Analysis started") + if(response.data.message == 'Analysis started') this.check_alerts = setInterval(() => { this.get_alerts(); }, 500); }) .catch(error => { @@ -49,13 +49,14 @@ export default { get_alerts: function() { axios.get(`/api/analysis/report/${this.capture_token}`, { timeout: 60000 }) .then(response => { - if(response.data.message != "No report yet"){ + if(response.data.message != 'No report yet'){ clearInterval(this.check_alerts); this.long_waiting = false; this.running = false; - router.replace({ name: 'report', params: { alerts : response.data.alerts, - device : response.data.device, - capture_token : this.capture_token } }); + router.replace({ name: 'report', + params: { alerts : response.data.alerts, + device : response.data.device, + capture_token : this.capture_token } }); } }) .catch(error => { @@ -64,11 +65,9 @@ export default { }, save_capture: function() { var capture_token = this.capture_token - router.replace({ name: 'save-capture', params: { capture_token: capture_token } }); + router.replace({ name: 'save-capture', + params: { capture_token: capture_token } }); } - }, - created: function() { - this.translation = window.translation[this.$route.name] } } </script> diff --git a/app/frontend/src/views/capture.vue b/app/frontend/src/views/capture.vue index 9f01f68..4323d10 100644 --- a/app/frontend/src/views/capture.vue +++ b/app/frontend/src/views/capture.vue @@ -3,10 +3,10 @@ <svg id="sparkline" stroke-width="3" :width="sparkwidth" :height="sparkheight" v-if="sparklines"></svg> <div class="center"> <div class="footer"> - <h3 class="timer">{{ timer_hours }}:{{ timer_minutes }}:{{ timer_seconds }}</h3> - <p>{{ translation.intercept_coms_msg }} {{ device_name }}.</p> + <h3 class="timer">{{timer_hours}}:{{timer_minutes}}:{{timer_seconds}}</h3> + <p>{{$t("capture.intercept_coms_msg")}} {{device_name}}.</p> <div class="empty-action"> - <button class="btn" :class="[ loading ? 'loading' : 'btn-primary', ]" v-on:click="stop_capture()">{{ translation.stop_btn }}</button> + <button class="btn" :class="[ loading ? 'loading' : 'btn-primary', ]" v-on:click="stop_capture()">{{$t("capture.stop_btn")}}</button> </div> </div> </div> @@ -43,16 +43,16 @@ export default { }, stop_capture: function() { this.loading = true - axios.get(`/api/network/ap/stop`, { timeout: 30000 }) - axios.get(`/api/capture/stop`, { timeout: 30000 }) + axios.get('/api/network/ap/stop', { timeout: 30000 }) + axios.get('/api/capture/stop', { timeout: 30000 }) .then(response => (this.handle_finish(response.data))) }, get_stats: function() { - axios.get(`/api/capture/stats`, { timeout: 30000 }) + axios.get('/api/capture/stats', { timeout: 30000 }) .then(response => (this.handle_stats(response.data))) }, handle_stats: function(data) { - if (data.packets.length) sparkline(document.querySelector("#sparkline"), data.packets); + if (data.packets.length) sparkline(document.querySelector('#sparkline'), data.packets); }, handle_finish: function(data) { clearInterval(this.chrono_interval); @@ -66,32 +66,30 @@ export default { chrono: function() { var time = Date.now() - this.capture_start this.timer_hours = Math.floor(time / (60 * 60 * 1000)); - this.timer_hours = (this.timer_hours < 10) ? "0" + this.timer_hours : this.timer_hours + this.timer_hours = (this.timer_hours < 10) ? '0' + this.timer_hours : this.timer_hours time = time % (60 * 60 * 1000); this.timer_minutes = Math.floor(time / (60 * 1000)); - this.timer_minutes = (this.timer_minutes < 10) ? "0" + this.timer_minutes : this.timer_minutes + this.timer_minutes = (this.timer_minutes < 10) ? '0' + this.timer_minutes : this.timer_minutes time = time % (60 * 1000); this.timer_seconds = Math.floor(time / 1000); - this.timer_seconds = (this.timer_seconds < 10) ? "0" + this.timer_seconds : this.timer_seconds + this.timer_seconds = (this.timer_seconds < 10) ? '0' + this.timer_seconds : this.timer_seconds }, setup_sparklines: function() { - axios.get(`/api/misc/config`, { timeout: 60000 }) + axios.get('/api/misc/config', { timeout: 60000 }) .then(response => { if(response.data.sparklines){ this.sparklines = true - this.sparkwidth = window.screen.width + "px"; - this.sparkheight = Math.trunc(window.screen.height / 5) + "px"; + this.sparkwidth = window.screen.width + 'px'; + this.sparkheight = Math.trunc(window.screen.height / 5) + 'px'; this.stats_interval = setInterval(() => { this.get_stats(); }, 500); } }) .catch(error => { console.log(error) - }); + }); }, }, created: function() { - this.translation = window.translation[this.$route.name] - // Get the config for the sparklines. this.setup_sparklines() diff --git a/app/frontend/src/views/generate-ap.vue b/app/frontend/src/views/generate-ap.vue index 53f5920..663238c 100644 --- a/app/frontend/src/views/generate-ap.vue +++ b/app/frontend/src/views/generate-ap.vue @@ -9,29 +9,30 @@ </div> <div class="divider-vert white-bg" data-content="OR"></div> <div class="column col-5"><br /> - <span class="light-grey">{{ translation.network_name }}</span><br /> + <span class="light-grey">{{ $t("generate-ap.network_name") }} </span><br /> <h4>{{ ssid_name }}</h4> - <span class="light-grey">{{ translation.network_password }}</span><br /> + <span class="light-grey">{{ $t("generate-ap.network_password") }} </span><br /> <h4>{{ ssid_password }}</h4> </div> </div> </div> <br /><br /><br /><br /> <br /><br /><br /><br /><br /><br /> <!-- Requite a CSS MEME for that shit :) --> - <span class="legend">{{ translation.tap_msg }}</span> + <span class="legend">{{ $t("generate-ap.tap_msg") }}</span> </div> <div v-else> <img src="@/assets/loading.svg"/> - <p class="legend">{{ translation.generate_ap_msg }}</p> + <p class="legend">{{ $t("generate-ap.generate_ap_msg") }}</p> </div> </div> <div v-else> <p> - <strong v-html="translation.error_msg1"></strong> + <strong v-html="$t('generate-ap.error_msg1')"></strong> <br /><br /> - <span v-html="translation.error_msg2"></span><br /><br /> + <span v-html="$t('generate-ap.error_msg2')"></span> + <br /><br /> </p> - <button v-if="reboot_option" class="btn" v-on:click="reboot()">{{ translation.restart_btn }}</button> + <button v-if="window.config.reboot_option" class="btn" v-on:click="reboot()">{{ $t("generate-ap.restart_btn") }}</button> </div> </div> @@ -62,7 +63,7 @@ export default { generate_ap: function() { clearInterval(this.interval); this.ssid_name = false - axios.get(`/api/network/ap/start`, { timeout: 30000 }) + axios.get('/api/network/ap/start', { timeout: 30000 }) .then(response => (this.show_ap(response.data))) }, show_ap: function(data) { @@ -81,11 +82,11 @@ export default { } }, start_capture: function() { - axios.get(`/api/capture/start`, { timeout: 30000 }) + axios.get('/api/capture/start', { timeout: 30000 }) .then(response => (this.get_capture_token(response.data))) }, reboot: function() { - axios.get(`/api/misc/reboot`, { timeout: 30000 }) + axios.get('/api/misc/reboot', { timeout: 30000 }) .then(response => { console.log(response)}) }, get_capture_token: function(data) { @@ -116,20 +117,9 @@ export default { } }); } - }, - get_config: function() { - axios.get(`/api/misc/config`, { timeout: 60000 }) - .then(response => { - this.reboot_option = response.data.reboot_option - }) - .catch(error => { - console.log(error) - }); - }, + } }, created: function() { - this.translation = window.translation[this.$route.name] - this.get_config(); this.generate_ap(); } } diff --git a/app/frontend/src/views/home.vue b/app/frontend/src/views/home.vue index 2dddea2..6f7b52a 100644 --- a/app/frontend/src/views/home.vue +++ b/app/frontend/src/views/home.vue @@ -1,8 +1,8 @@ <template> <div class="center"> - <h3 class="lobster">{{ translation.welcome_msg }}</h3> - <p>{{ translation.help_msg }}</p> - <button class="btn btn-primary" v-on:click="next()">{{ translation.start_btn }}</button> + <h3 class="lobster">{{ $t("home.welcome_msg") }}</h3> + <p>{{ $t("home.help_msg") }}</p> + <button class="btn btn-primary" v-on:click="next()">{{ $t("home.start_btn") }}</button> </div> </template> @@ -11,7 +11,7 @@ import router from '../router' export default { name: 'home', - props: { saved_ssid: String, iface_out: String, list_ssids: Array, internet: Boolean }, + props: { saved_ssid: String, list_ssids: Array, internet: Boolean }, data() { return { translation: {}, @@ -22,18 +22,15 @@ export default { var saved_ssid = this.saved_ssid var list_ssids = this.list_ssids var internet = this.internet - if (this.iface_out.charAt(0) == "e"){ + if (window.config.iface_out.charAt(0) == "e"){ router.push({ name: 'generate-ap' }); } else { router.push({ name: 'wifi-select', params: { saved_ssid: saved_ssid, list_ssids: list_ssids, - internet:internet } }); + internet: internet } }); } } - }, - created: function() { - this.translation = window.translation[this.$route.name] } } </script> diff --git a/app/frontend/src/views/report.vue b/app/frontend/src/views/report.vue index 51a1ba5..131b2ae 100644 --- a/app/frontend/src/views/report.vue +++ b/app/frontend/src/views/report.vue @@ -3,69 +3,69 @@ <div v-if="results"> <div v-if="grep_keyword('STALKERWARE', 'high')" class="high-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="translation.stalkerware_msg"></h1> - <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ translation.start_new_capture }}</button> - <button class="btn btn-report-high" @click="show_report=true;results=false;">{{ translation.show_full_report }}</button> + <h1 class="warning-title" v-html="$t('report.stalkerware_msg')"></h1> + <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> + <button class="btn btn-report-high" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button> </div> </div> <div v-else-if="alerts.high.length >= 1" class="high-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="translation.high_msg"></h1> - <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ translation.start_new_capture }}</button> - <button class="btn btn-report-high" @click="show_report=true;results=false;">{{ translation.show_full_report }}</button> + <h1 class="warning-title" v-html="$t('report.high_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.high.length] })"></h1> + <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> + <button class="btn btn-report-high" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button> </div> </div> <div v-else-if="grep_keyword('TRACKER', 'moderate')" class="med-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="translation.location_msg"></h1> - <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ translation.start_new_capture }}</button> - <button class="btn btn-report-moderate" @click="show_report=true;results=false;">{{ translation.show_full_report }}</button> + <h1 class="warning-title" v-html="$t('report.location_msg')"></h1> + <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> + <button class="btn btn-report-moderate" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button> </div> </div> <div v-else-if="alerts.moderate.length >= 1" class="med-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="translation.moderate_msg"></h1> - <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ translation.start_new_capture }}</button> - <button class="btn btn-report-moderate" @click="show_report=true;results=false;">{{ translation.show_full_report }}</button> + <h1 class="warning-title" v-html="$t('report.moderate_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.moderate.length] })"></h1> + <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> + <button class="btn btn-report-moderate" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button> </div> </div> <div v-else-if="alerts.low.length >= 1" class="low-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="translation.low_msg"></h1> - <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ translation.start_new_capture }}</button> - <button class="btn btn-report-low" @click="show_report=true;results=false;">{{ translation.show_full_report }}</button> + <h1 class="warning-title" v-html="$t('report.low_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.moderate.low] })"></h1> + <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> + <button class="btn btn-report-low" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button> </div> </div> <div v-else class="none-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="translation.fine_msg"> - <button class="btn btn-report-low-light" v-on:click="save_capture()">{{ translation.save_capture }}</button> - <button class="btn btn-report-low" v-on:click="new_capture()">{{ translation.start_new_capture }}</button> + <h1 class="warning-title">{{ $t("report.fine_msg") }}</h1> + <button class="btn btn-report-low-light" v-on:click="save_capture()">{{ $t("report.save_capture") }}</button> + <button class="btn btn-report-low" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> </div> </div> </div> <div v-else-if="show_report" class="report-wrapper"> <div class="device-ctx"> - <h3 style="margin: 0;">{{ translation.report_of }} {{ device.name }}</h3> - {{ translation.ip_address }} {{device.ip_address}}<br />{{ translation.mac_address }} {{device.mac_address}} + <h3 style="margin: 0;">{{ $t("report.report_of") }} {{device.name}}</h3> + {{ $t("report.ip_address") }} {{device.ip_address}}<br />{{ $t("report.mac_address") }} {{device.mac_address}} </div> <ul class="alerts"> <li class="alert" v-for="alert in alerts.high" :key="alert.message"> - <span class="high-label">{{ translation.high }}</span><span class="alert-id">{{ alert.id }}</span> + <span class="high-label">{{ $t("report.high") }}</span><span class="alert-id">{{ alert.id }}</span> <div class="alert-body"> <span class="title">{{ alert.title }}</span> <p class="description">{{ alert.description }}</p> </div> </li> <li class="alert" v-for="alert in alerts.moderate" :key="alert.message"> - <span class="moderate-label">{{ translation.moderate }}</span><span class="alert-id">{{ alert.id }}</span> + <span class="moderate-label">{{ $t("report.moderate") }}</span><span class="alert-id">{{ alert.id }}</span> <div class="alert-body"> <span class="title">{{ alert.title }}</span> <p class="description">{{ alert.description }}</p> </div> </li> <li class="alert" v-for="alert in alerts.low" :key="alert.message"> - <span class="low-label">{{ translation.low }}</span><span class="alert-id">{{ alert.id }}</span> + <span class="low-label">{{ $t("report.low") }}</span><span class="alert-id">{{ alert.id }}</span> <div class="alert-body"> <span class="title">{{ alert.title }}</span> <p class="description">{{ alert.description }}</p> @@ -74,11 +74,11 @@ </ul> <div class="columns" id="controls-analysis"> <div class="column col-5"> - <button class="btn width-100" @click="$router.push('generate-ap')">{{ translation.start_new_capture }}</button> + <button class="btn width-100" @click="$router.push('generate-ap')">{{ $t("report.start_new_capture") }}</button> </div> <div class="divider-vert column col-2" data-content="OR"></div> <div class="column col-5"> - <button class="btn btn btn-primary width-100" v-on:click="save_capture()">{{ translation.save_report }}</button> + <button class="btn btn btn-primary width-100" v-on:click="save_capture()">{{ $t("report.save_report") }}</button> </div> </div> </div> @@ -116,15 +116,6 @@ export default { new_capture: function() { router.push({ name: 'generate-ap' }) }, - nb_translate: function(x) { - var nbs = this.translation["numbers"] - try { - return nbs[x]; - } catch (error) - { - return x; - } - }, grep_keyword: function(kw, level){ try { if(this.alerts[level].length){ @@ -143,12 +134,6 @@ export default { return false; } } - }, - created: function() { - this.translation = window.translation[this.$route.name] - this.translation["high_msg"] = this.translation["high_msg"].replace("{nb}", this.nb_translate(this.alerts.high.length)) - this.translation["moderate_msg"] = this.translation["moderate_msg"].replace("{nb}", this.nb_translate(this.alerts.moderate.length)) - this.translation["low_msg"] = this.translation["low_msg"].replace("{nb}", this.nb_translate(this.alerts.low.length)) } } </script> diff --git a/app/frontend/src/views/save-capture.vue b/app/frontend/src/views/save-capture.vue index 26cb79a..d94013d 100644 --- a/app/frontend/src/views/save-capture.vue +++ b/app/frontend/src/views/save-capture.vue @@ -6,14 +6,14 @@ <div class="icon-usb"></div> <div class="icon-usb-plug"></div> </div> - <p class="legend" v-if="!saved && !usb"><br />{{ translation.please_connect }}</p> - <p class="legend" v-if="!saved && usb"><br />{{ translation.we_are_saving }}</p> - <p class="legend" v-if="saved"><br />{{ translation.tap_msg }}</p> + <p class="legend" v-if="!saved && !usb"><br />{{ $t("save-capture.please_connect") }}</p> + <p class="legend" v-if="!saved && usb"><br />{{ $t("save-capture.we_are_saving") }}</p> + <p class="legend" v-if="saved"><br />{{ $t("save-capture.tap_msg") }}</p> </div> <div class="center" v-else-if="!save_usb && init"> <div> - <p class="legend">{{ translation.catpure_download }}<br /><br /><br /></p> - <button class="btn btn-primary" v-on:click="new_capture()">{{ translation.start_capture_btn }}</button> + <p class="legend">{{ $t("save-capture.capture_download") }}<br /><br /><br /></p> + <button class="btn btn-primary" v-on:click="new_capture()">{{ $t("save-capture.start_capture_btn") }}</button> <iframe :src="download_url" class="frame-download"></iframe> </div> </div> @@ -175,28 +175,18 @@ export default { new_capture: function() { clearTimeout(this.timeout); router.push({ name: 'generate-ap' }) - }, - load_config: function() { - axios.get(`/api/misc/config`, { timeout: 60000 }) - .then(response => { - if(response.data.download_links){ - this.init = true - this.save_usb = false - this.download_url = `/api/save/save-capture/${this.capture_token}/url` - } else { - this.init = true - this.save_usb = true - this.interval = setInterval(() => { this.check_usb() }, 500); - } - }) - .catch(error => { - console.log(error) - }); } }, created: function() { - this.translation = window.translation[this.$route.name] - this.load_config() + if(window.config.download_links){ + this.init = true + this.save_usb = false + this.download_url = `/api/save/save-capture/${this.capture_token}/url` + } else { + this.init = true + this.save_usb = true + this.interval = setInterval(() => { this.check_usb() }, 500); + } } } </script> \ No newline at end of file diff --git a/app/frontend/src/views/splash-screen.vue b/app/frontend/src/views/splash-screen.vue index a915982..bc76224 100644 --- a/app/frontend/src/views/splash-screen.vue +++ b/app/frontend/src/views/splash-screen.vue @@ -15,58 +15,34 @@ data() { return { list_ssids: [], - internet: false, - iface_out:"" + internet: false } }, methods: { - // Check if the device is connected to internet. internet_check: function() { - axios.get(`/api/network/status`, { timeout: 10000 }) + axios.get('/api/network/status', { timeout: 10000 }) .then(response => { - if (response.data.internet){ - this.internet = true - } - this.load_config() + if (response.data.internet) this.internet = true + if (window.config.iface_out.charAt(0) == 'e') { + setTimeout(function () { this.goto_home(); }.bind(this), 1000); + } else { + this.get_wifi_networks(); + } }) .catch(err => (console.log(err))) }, - // Get the WiFi networks around the box. get_wifi_networks: function() { - axios.get(`/api/network/wifi/list`, { timeout: 10000 }) + axios.get('/api/network/wifi/list', { timeout: 10000 }) .then(response => { this.list_ssids = response.data.networks this.goto_home(); }) .catch(err => (console.log(err))) }, - // Forward the view to home, with some props - // such as (SSIDs, internet & interface) goto_home: function() { var list_ssids = this.list_ssids var internet = this.internet - var iface_out = this.iface_out - router.replace({ name: 'home', params: { list_ssids: list_ssids, internet: internet, iface_out : iface_out } }); - }, - // Get the network_out from the config - // to determine the next steps. - load_config: function() { - axios.get(`/api/misc/config`, { timeout: 60000 }) - .then(response => { - if(response.data.iface_out){ - this.iface_out = response.data.iface_out - // If ethernet, just goto the homepage. - // Else, get wifi networks and then go to home. - if(this.iface_out.charAt(0) == "e"){ - setTimeout(function () { this.goto_home(); }.bind(this), 1000); - } else { - this.get_wifi_networks(); - } - } - }) - .catch(error => { - console.log(error) - }); + router.replace({ name: 'home', params: { list_ssids: list_ssids, internet: internet } }); } }, created: function() { diff --git a/app/frontend/src/views/wifi-select.vue b/app/frontend/src/views/wifi-select.vue index cc16eee..ce3fc1b 100644 --- a/app/frontend/src/views/wifi-select.vue +++ b/app/frontend/src/views/wifi-select.vue @@ -2,41 +2,42 @@ <div :class="[ keyboard == false ? 'center' : '' ]"> <div v-if="keyboard == false"> <div v-if="have_internet"> - <p v-html="translation.already_connected_question"></p> + <p v-html="$t('wifi-select.already_connected_question')"></p> <div class="empty-action"> - <button class="btn" @click="have_internet = false">{{ translation.no_btn }}</button> <button class="btn" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" @click="$router.push({ name: 'generate-ap' })">{{ translation.yes_btn }}</button> + <button class="btn" @click="have_internet = false">{{ $t("wifi-select.no_btn") }}</button>   + <button class="btn" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" @click="$router.push({ name: 'generate-ap' })">{{ $t("wifi-select.yes_btn") }}</button> </div> </div> <div v-else> <div v-if="enter_creds" class="wifi-login"> <div class="form-group"> <select class="form-select" id="ssid-select" v-model="ssid"> - <option value="" selected>{{ translation.wifi_name }}</option> + <option value="" selected>{{ $t("wifi-select.wifi_name") }}</option> <option v-for="ssid in ssids" v-bind:key="ssid.ssid"> {{ ssid.ssid }} </option> </select> </div> <div class="form-group"> - <input class="form-input" type="password" id="password" v-model="password" :placeholder="translation.wifi_password" v-on:click="keyboard = (virtual_keyboard)? true : false"> + <input class="form-input" type="password" id="password" v-model="password" :placeholder="$t('wifi-select.wifi_password')" v-on:click="keyboard = (window.config.virtual_keyboard)? true : false"> </div> <div class="form-group"> <button class="btn width-100" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" v-on:click="wifi_setup()">{{ btnval }}</button> </div> <div class="form-group"> - <button class="btn width-100" :class="[ refreshing ? 'loading' : '' ]" v-on:click="refresh_wifi_list()">{{ translation.refresh_btn }}</button> + <button class="btn width-100" :class="[ refreshing ? 'loading' : '' ]" v-on:click="refresh_wifi_list()">{{ $t("wifi-select.refresh_btn") }}</button> </div> </div> <div v-else> - <p><strong>{{ translation.not_connected }}</strong><br />{{ translation.please_config }}</p> + <p><strong>{{ $t("wifi-select.not_connected") }}</strong><br />{{ $t("wifi-select.please_config") }}</p> <div class="empty-action"> - <button class="btn btn-primary" @click="enter_creds = true">{{ translation.lets_do_btn }}</button> + <button class="btn btn-primary" @click="enter_creds = true">{{ $t("wifi-select.lets_do_btn") }}</button> </div> </div> </div> </div> <div v-else> - <input :value="input" class="keyboardinput" @input="onInputChange" :placeholder="translation.tap_keyboard"> + <input :value="input" class="keyboardinput" @input="onInputChange" :placeholder="$t('wifi-select.tap_keyboard')"> <SimpleKeyboard @onChange="onChange" @onKeyPress="onKeyPress" :input="input" /> </div> </div> @@ -63,7 +64,7 @@ export default { connecting: false, error: false, success: false, - btnval: "", + btnval: this.$t("wifi-select.connect_to_it"), ssid: "", selected_ssid: false, password: "", @@ -72,7 +73,6 @@ export default { ssids: [], have_internet: false, enter_creds: false, - virtual_keyboard: false, refreshing: false, translation: {} } @@ -84,15 +84,15 @@ export default { }, methods: { wifi_connect: function() { - axios.get(`/api/network/wifi/connect`, { timeout: 60000 }) + axios.get('/api/network/wifi/connect', { timeout: 60000 }) .then(response => { if (response.data.status) { this.success = true this.connecting = false - this.btnval = this.translation.wifi_connected + this.btnval = this.$t('wifi-select.wifi_connected') setTimeout(() => router.push('generate-ap'), 1000); } else { - this.btnval = this.translation["wifi_not_connected"] + this.btnval = this.$t('wifi-select.wifi_not_connected') this.connecting = false } }) @@ -102,7 +102,7 @@ export default { }, wifi_setup: function() { if (this.ssid.length && this.password.length >= 8 ){ - axios.post(`/api/network/wifi/setup`, { ssid: this.ssid, password: this.password }, { timeout: 60000 }) + axios.post('/api/network/wifi/setup', { ssid: this.ssid, password: this.password }, { timeout: 60000 }) .then(response => { if(response.data.status) { this.connecting = true @@ -116,15 +116,6 @@ export default { }); } }, - get_config: function() { - axios.get(`/api/misc/config`, { timeout: 60000 }) - .then(response => { - this.virtual_keyboard = response.data.virtual_keyboard - }) - .catch(error => { - console.log(error) - }); - }, onChange(input) { this.input = input this.password = this.input; @@ -141,25 +132,22 @@ export default { }, refresh_wifi_list: function(){ this.refreshing = true - axios.get(`/api/network/wifi/list`, { timeout: 10000 }) - .then(response => { - this.refreshing = false - this.append_ssids(response.data.networks) - }).catch(error => { - this.refreshing = false - console.log(error) + axios.get('/api/network/wifi/list', { timeout: 10000 }) + .then(response => { + this.refreshing = false + this.append_ssids(response.data.networks) + }).catch(error => { + this.refreshing = false + console.log(error) }); } }, created: function() { - this.translation = window.translation[this.$route.name] - this.btnval = this.translation.connect_to_it - this.get_config() this.have_internet = (this.internet) ? true : false this.keyboard = false - if (typeof this.list_ssids == "object" && this.list_ssids.length != 0){ + if (typeof this.list_ssids == 'object' && this.list_ssids.length != 0){ this.ssids = this.list_ssids } else { this.refresh_wifi_list() diff --git a/server/frontend/app/assets/lang/en.json b/server/frontend/app/assets/lang/en.json deleted file mode 100644 index 239008d..0000000 --- a/server/frontend/app/assets/lang/en.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "home": { - "welcome_msg": "Welcome to TinyCheck.", - "help_msg": "We are going to help you to check your device.", - "start_btn": "Let's start!" - }, - "analysis": { - "question": "Do you want to analyze the captured communications?", - "no_btn": "No, just save them", - "yes_btn": "Yes, let's do it", - "please_wait_msg": "Please wait during the analysis...", - "some_time_msg": "Yes, it can take some time..." - }, - "capture": { - "intercept_coms_msg": "Intercepting the communications of ", - "stop_btn": "Stop the capture" - }, - "generate-ap": { - "network_name": "Network name", - "network_password": "Network password", - "tap_msg": "Tap the white frame to generate a new network.", - "generate_ap_msg": "We generate an ephemeral network for you.", - "error_msg1": "Unfortunately, we got some issues <br />during the AP creation.", - "error_msg2": "Please verify that you've two WiFi interfaces on your device<br /> and try again by restarting it.", - "restart_btn": "Restart the device" - }, - "report": { - "show_full_report": "Show the full report", - "start_new_capture": "Start a new capture", - "save_capture": "Save the capture", - "numbers": [ - "zero", - "one", - "two", - "three", - "four", - "five", - "six", - "seven", - "eight", - "nine", - "ten", - "eleven" - ], - "stalkerware_msg": "Your device is compromised by<br />a Stalkerware, please check the report.", - "location_msg": "An application is sharing your<br /> current geolocation with a third party.", - "fine_msg": "Everything looks fine, zero alerts.", - "high_msg": "You have {nb} high alert,<br />your device seems to be compromised.", - "moderate_msg": "You have {nb} moderate alerts,<br />your device might be compromised.", - "low_msg": "You have only {nb} low alerts,<br /> don't hesitate to check them.", - "save_report": "Save the report", - "report_of": "Report of", - "ip_address": "IP address", - "mac_address": "MAC address", - "high": "high", - "moderate": "moderate", - "low": "low" - }, - "wifi-select": { - "already_connected_question": "You seem to be already connected to a network.<br />Do you want to use the current connection?", - "no_btn": "No, use another", - "yes_btn": "Yes, use it.", - "wifi_name": "Wi-Fi name", - "refresh_btn": "Refresh networks list", - "not_connected": "You seem to not be connected to Internet.", - "please_config": "Please configure the Wi-Fi connection.", - "lets_do_btn": "Ok, let's do that.", - "wifi_connected": "Wi-Fi connected", - "wifi_not_connected": "Wi-Fi not connected. Please retry.", - "tap_keyboard": "Tap on the virtual keyboard to start", - "wifi_password": "Wi-Fi password", - "connect_to_it": "Connect to it" - }, - "save-capture": { - "please_connect": "Please connect a USB key to save your capture.", - "we_are_saving": "We are saving your capture.", - "tap_msg": "You can tap the USB key to start a new capture.", - "capture_download": "The capture download is going to start...", - "start_capture_btn": "Start another capture" - } -} \ No newline at end of file diff --git a/server/frontend/app/assets/lang/fr.json b/server/frontend/app/assets/lang/fr.json deleted file mode 100644 index cb03a14..0000000 --- a/server/frontend/app/assets/lang/fr.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "home": { - "welcome_msg": "Bienvenue sur TinyCheck.", - "help_msg": "Nous allons vous accompagner pour analyser votre appareil.", - "start_btn": "Allons-y !" - }, - "analysis": { - "question": "Voulez-vous analyser les communications capturées ?", - "no_btn": "Non, enregistrez-les.", - "yes_btn": "Oui, allons-y !", - "please_wait_msg": "Merci d'attendre pendant l'analyse...", - "some_time_msg": "Oui, cela peut prendre du temps..." - }, - "capture": { - "intercept_coms_msg": "Interception des communications de ", - "stop_btn": "Arrêter la capture" - }, - "generate-ap": { - "network_name": "Nom du réseau", - "network_password": "Mot de passe du réseau", - "tap_msg": "Appuyez sur le cadre blanc pour générer un nouveau réseau.", - "generate_ap_msg": "Nous générons un réseau Wi-Fi éphémère.", - "error_msg1": "Malheureusement, nous rencontrons des problèmes <br />lors de la création du point d'accès.", - "error_msg2": "Veuillez vérifier que vous disposez de <br /> deux interfaces WiFi sur votre appareil.", - "restart_btn": "Restart the device" - }, - "report": { - "show_full_report": "Lire le rapport complet", - "start_new_capture": "Nouvelle capture", - "save_capture": "Sauvegarder", - "numbers": [ - "zéro", - "une", - "deux", - "trois", - "quatre", - "cinq", - "six", - "sept", - "huit", - "neuf", - "dix", - "onze" - ], - "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, lisez le rapport.", - "location_msg": "Une application dévoile votre<br /> géo-localisation actelle.", - "fine_msg": "Tout semble OK, vous avez aucue alerte.", - "high_msg": "Vous avez {nb} alerte(s) élevée(s),<br />votre appareil semble être compromis.", - "moderate_msg": "Vous avez {nb} alerte(s) moyenne(s),<br />votre appareil est peut-être compromis.", - "low_msg": "Vous avez uniquement {nb} alerte(s) basse(s)<br /> n'hésitez pas à les lire.", - "save_report": "Sauvegarder le rapport", - "report_of": "Rapport de", - "ip_address": "Adresse IP :", - "mac_address": "Adresse MAC :", - "high": "elevee", - "moderate": "moyenne", - "low": "base" - }, - "wifi-select": { - "already_connected_question": "Vous semblez être connecté à un réseau.<br />Voulez-vous utiliser cette connexion ?", - "no_btn": "Non, une autre", - "yes_btn": "Oui, utilisez celle-là", - "wifi_name": "Nom du réseau", - "refresh_btn": "Rafraichir la liste des réseaux", - "not_connected": "Vous semblez ne pas être connecté à Internet.", - "please_config": "Configurez la connexion Wi-Fi.", - "lets_do_btn": "Oui, allons-y.", - "wifi_connected": "Wi-Fi connecté", - "wifi_not_connected": "Wi-Fi non connecté. Ressayez.", - "tap_keyboard": "Appuez sur cet espace pour écrire", - "wifi_password": "Mot de passe", - "connect_to_it": "Se connecter" - }, - "save-capture": { - "please_connect": "Connectez une clé USB pour enregistrer votre capture.", - "we_are_saving": "Nous enregistrons votre capture.", - "tap_msg": "Vous pouvez appuyer sur l'animation pour lancer une nouvelle capture.", - "capture_download": "Le téléchargement de la capture va se lancer...", - "start_capture_btn": "Lancer une nouvelle capture" - } -} \ No newline at end of file diff --git a/server/frontend/app/blueprints/misc.py b/server/frontend/app/blueprints/misc.py index 95c5c18..c9bbeed 100644 --- a/server/frontend/app/blueprints/misc.py +++ b/server/frontend/app/blueprints/misc.py @@ -13,8 +13,8 @@ misc_bp = Blueprint("misc", __name__) @misc_bp.route("/reboot", methods=["GET"]) def api_reboot(): - """ - Reboot the device + """ + Reboot the device """ if read_config(("frontend", "reboot_option")): sp.Popen("shutdown -r now", shell=True) @@ -25,8 +25,8 @@ def api_reboot(): @misc_bp.route("/quit", methods=["GET"]) def api_quit(): - """ - Quit the interface (Chromium browser) + """ + Quit the interface (Chromium browser) """ if read_config(("frontend", "quit_option")): sp.Popen('pkill -INT -f "chromium-browser"', shell=True) @@ -37,8 +37,8 @@ def api_quit(): @misc_bp.route("/shutdown", methods=["GET"]) def api_shutdown(): - """ - Reboot the device + """ + Reboot the device """ if read_config(("frontend", "shutdown_option")): sp.Popen("shutdown -h now", shell=True) @@ -49,8 +49,8 @@ def api_shutdown(): @misc_bp.route("/config", methods=["GET"]) def get_config(): - """ - Get configuration keys relative to the GUI + """ + Get configuration keys relative to the GUI """ return jsonify({ "virtual_keyboard": read_config(("frontend", "virtual_keyboard")), @@ -60,20 +60,6 @@ def get_config(): "quit_option": read_config(("frontend", "quit_option")), "shutdown_option": read_config(("frontend", "shutdown_option")), "reboot_option": read_config(("frontend", "reboot_option")), - "iface_out": read_config(("network", "out")) + "iface_out": read_config(("network", "out")), + "user_lang": read_config(("frontend", "user_lang")) }) - - -@misc_bp.route("/get-lang", methods=["GET"]) -def get_lang(): - """ - Get the user lang defined in the config.yaml - and retrieve the interface translation. - """ - lang = read_config(("frontend", "user_lang")) - if re.match("^[a-z]{2,3}$", lang): - with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "app/assets/lang/{}.json".format(lang)), "r") as f: - return(f.read()) - else: - with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "app/assets/lang/en.json"), "r") as f: - return(f.read()) From 059bada28103890ceb8128ff897b594a515b830d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 17:07:33 +0100 Subject: [PATCH 08/17] Minor changes related to the translation --- app/frontend/src/App.vue | 2 +- app/frontend/src/locales/fr.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/frontend/src/App.vue b/app/frontend/src/App.vue index cca5c1d..b8668ba 100644 --- a/app/frontend/src/App.vue +++ b/app/frontend/src/App.vue @@ -51,8 +51,8 @@ get_config: function() { axios.get('/api/misc/config', { timeout: 60000 }) .then(response => { - this.set_lang(); window.config = response.data + this.set_lang(); }) .catch(error => { console.log(error) }); } diff --git a/app/frontend/src/locales/fr.json b/app/frontend/src/locales/fr.json index 4447bd8..05c9f1e 100644 --- a/app/frontend/src/locales/fr.json +++ b/app/frontend/src/locales/fr.json @@ -47,8 +47,8 @@ "fine_msg": "Tout semble OK, vous avez aucue alerte.", "high_msg": "Vous avez {nb} alerte.s élevée.s,<br />votre appareil semble être compromis.", "moderate_msg": "Vous avez {nb} alerte.s moyenne.s,<br />votre appareil est peut-être compromis.", - "low_msg": "Vous avez uniquement {nb} alerte.s basse.s<br /> n'hésitez pas à les parcourir.", - "save_report": "Sauvegarder le rapport", + "low_msg": "Vous avez uniquement {nb} alerte.s basse.s<br /> n'hésitez pas à les consulter.", + "save_report": "Archiver le rapport", "report_of": "Rapport de", "ip_address": "Adresse IP :", "mac_address": "Adresse MAC :", From 1cd025b2eaa2cfa3f90d0bedccbcab7caa23cbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 17:22:44 +0100 Subject: [PATCH 09/17] Small changes regarding Analysis translation --- analysis/classes/report.py | 2 +- analysis/classes/suricataengine.py | 2 +- analysis/classes/zeekengine.py | 2 +- analysis/{lang => locales}/en.json | 0 analysis/{lang => locales}/fr.json | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) rename analysis/{lang => locales}/en.json (100%) rename analysis/{lang => locales}/fr.json (99%) diff --git a/analysis/classes/report.py b/analysis/classes/report.py index f823708..d38d26e 100644 --- a/analysis/classes/report.py +++ b/analysis/classes/report.py @@ -451,7 +451,7 @@ class Report(object): color:#CCC; } @bottom-center { - content: REPORT_FOOTER; + content: "REPORT_FOOTER"; font-size:12px; color:#CCC; } diff --git a/analysis/classes/suricataengine.py b/analysis/classes/suricataengine.py index 268f6b0..fb7edd9 100644 --- a/analysis/classes/suricataengine.py +++ b/analysis/classes/suricataengine.py @@ -26,7 +26,7 @@ class SuricataEngine(): # Load template language if not re.match("^[a-z]{2,3}$", self.userlang): self.userlang = "en" - with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "lang/{}.json".format(self.userlang))) as f: + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f: self.template = json.load(f)["alerts"] def start_suricata(self): diff --git a/analysis/classes/zeekengine.py b/analysis/classes/zeekengine.py index 367c803..4ce60a5 100644 --- a/analysis/classes/zeekengine.py +++ b/analysis/classes/zeekengine.py @@ -35,7 +35,7 @@ class ZeekEngine(object): # Load template language if not re.match("^[a-z]{2,3}$", self.userlang): self.userlang = "en" - with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "lang/{}.json".format(self.userlang))) as f: + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f: self.template = json.load(f)["alerts"] def fill_dns(self, dir): diff --git a/analysis/lang/en.json b/analysis/locales/en.json similarity index 100% rename from analysis/lang/en.json rename to analysis/locales/en.json diff --git a/analysis/lang/fr.json b/analysis/locales/fr.json similarity index 99% rename from analysis/lang/fr.json rename to analysis/locales/fr.json index 47eed56..15be281 100644 --- a/analysis/lang/fr.json +++ b/analysis/locales/fr.json @@ -75,7 +75,7 @@ }, "report": { "numbers": [ - "un", + "une", "deux", "trois", "quatre", @@ -102,7 +102,7 @@ "report_footer": "Ce rapport a été automatiquement généré par une instance de TinyCheck. Pour toute question et retours, n'hésitez pas à contacter tinycheck@kaspersky.com.", "high_msg": "Votre appareil semble être compromis car vous avez {} alerte(s) élevée(s).", "moderate_msg": "Vous avez {} alerte(s) modérée(s), votre appareil peut être compromis. Regardez ces alertes en détail.", - "low_msg": "Vous avez uniquement {} alerte(s) faibles, n'hésitez pas à les regarder, on ne sait jamais...", + "low_msg": "Vous avez uniquement {} alerte(s) faibles, n'hésitez pas à les consulter.", "none_msg": "Toute semble normal, vous avez aucune alerte. Cependant, n'hésitez pas à regarder les communications non catégorisées." } } \ No newline at end of file From 7e6922e89d3dd1fc6ec2fd8b43491539866018a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 17:40:30 +0100 Subject: [PATCH 10/17] Update install script with locales --- install.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/install.sh b/install.sh index 474fdb0..8b32880 100644 --- a/install.sh +++ b/install.sh @@ -6,6 +6,7 @@ HOST="$( hostname )" IFACES="$( ifconfig -a | grep -Eo '[a-z0-9]{4,14}\: ' | grep -oE [a-z0-9]+ )" IFACE_OUT="" IFACE_IN="" +LOCALES=(en fr) welcome_screen() { cat << "EOF" @@ -40,6 +41,22 @@ check_operating_system() { fi } +set_userlang() { + # Set the user language. + echo -e "\e[39m[+] Setting the user language...\e[39m" + printf -v joined '%s/' "${LOCALES[@]}" + echo -n " Please choose a language for the reports and the interface (${joined%/}): " + read lang + + if [[ " ${LOCALES[@]} " =~ " ${lang} " ]]; then + sed -i "s/userlang/${$lang}/g" /usr/share/tinycheck/config.yaml + echo -e "\e[92m [✔] User language setted!\e[39m" + else + echo -e "\e[91m [✘] You must choose between the languages proposed, let's retry.\e[39m" + set_userlang + fi +} + set_credentials() { # Set the credentials to access to the backend. echo -e "\e[39m[+] Setting the backend credentials...\e[39m" @@ -410,6 +427,7 @@ else check_operating_system check_interfaces create_directory + set_userlang set_credentials check_dependencies configure_dnsmask From 34f672a6014392c62e1b060e47a940977a02ce71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 18:02:25 +0100 Subject: [PATCH 11/17] Minor changes --- app/frontend/src/locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/frontend/src/locales/fr.json b/app/frontend/src/locales/fr.json index 05c9f1e..2b86ca8 100644 --- a/app/frontend/src/locales/fr.json +++ b/app/frontend/src/locales/fr.json @@ -27,7 +27,7 @@ "report": { "show_full_report": "Lire le rapport complet", "start_new_capture": "Nouvelle capture", - "save_capture": "Sauvegarder", + "save_capture": "Sauvegarder la capture", "numbers": [ "zéro", "une", @@ -44,7 +44,7 @@ ], "stalkerware_msg": "Votre smartphone est compromis<br /> par un Stalkerware, lisez le rapport.", "location_msg": "Une application dévoile votre<br /> géo-localisation actelle.", - "fine_msg": "Tout semble OK, vous avez aucue alerte.", + "fine_msg": "Aucun flux suspect ou malveillant détecté,<br />vous avez aucune alerte.", "high_msg": "Vous avez {nb} alerte.s élevée.s,<br />votre appareil semble être compromis.", "moderate_msg": "Vous avez {nb} alerte.s moyenne.s,<br />votre appareil est peut-être compromis.", "low_msg": "Vous avez uniquement {nb} alerte.s basse.s<br /> n'hésitez pas à les consulter.", From 60480922aeaf5c1f682cfa383bd6fc3b6cc520fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 18:03:06 +0100 Subject: [PATCH 12/17] Minor changes --- app/frontend/src/locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/locales/fr.json b/app/frontend/src/locales/fr.json index 2b86ca8..ef7b373 100644 --- a/app/frontend/src/locales/fr.json +++ b/app/frontend/src/locales/fr.json @@ -64,7 +64,7 @@ "refresh_btn": "Rafraichir la liste des réseaux", "not_connected": "Vous semblez ne pas être connecté à Internet.", "please_config": "Configurez la connexion Wi-Fi.", - "lets_do_btn": "Oui, allons-y.", + "lets_do_btn": "Oui, allons-y", "wifi_connected": "Wi-Fi connecté", "wifi_not_connected": "Wi-Fi non connecté. Ressayez.", "tap_keyboard": "Appuez sur cet espace pour écrire", From b0a45f6ef39b461f0a6531836eff146dcb012ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 18:11:34 +0100 Subject: [PATCH 13/17] Minor changes --- config.yaml | 2 +- install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index a2032ee..633a577 100644 --- a/config.yaml +++ b/config.yaml @@ -48,7 +48,7 @@ frontend: remote_access: true sparklines: true virtual_keyboard: true - user_lang: en + user_lang: userlang # NETWORK - # Some elements related to the network configuration, such as diff --git a/install.sh b/install.sh index 8b32880..eec8071 100644 --- a/install.sh +++ b/install.sh @@ -49,7 +49,7 @@ set_userlang() { read lang if [[ " ${LOCALES[@]} " =~ " ${lang} " ]]; then - sed -i "s/userlang/${$lang}/g" /usr/share/tinycheck/config.yaml + sed -i "s/userlang/${lang}/g" /usr/share/tinycheck/config.yaml echo -e "\e[92m [✔] User language setted!\e[39m" else echo -e "\e[91m [✘] You must choose between the languages proposed, let's retry.\e[39m" From 50a9eb3a89f02ada275c02ef7cd768fae9ff7a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 18:35:26 +0100 Subject: [PATCH 14/17] Correcting a bug in wifi-select --- app/frontend/src/views/wifi-select.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/frontend/src/views/wifi-select.vue b/app/frontend/src/views/wifi-select.vue index ce3fc1b..f132f27 100644 --- a/app/frontend/src/views/wifi-select.vue +++ b/app/frontend/src/views/wifi-select.vue @@ -19,7 +19,7 @@ </select> </div> <div class="form-group"> - <input class="form-input" type="password" id="password" v-model="password" :placeholder="$t('wifi-select.wifi_password')" v-on:click="keyboard = (window.config.virtual_keyboard)? true : false"> + <input class="form-input" type="password" id="password" v-model="password" :placeholder="$t('wifi-select.wifi_password')" v-on:click="keyboard = (virtual_keyboard)? true : false"> </div> <div class="form-group"> <button class="btn width-100" :class="[ connecting ? 'loading' : '', success ? 'btn-success' : 'btn-primary', ]" v-on:click="wifi_setup()">{{ btnval }}</button> @@ -71,6 +71,7 @@ export default { keyboard: false, input: "", ssids: [], + virtual_keyboard: window.config.virtual_keyboard, have_internet: false, enter_creds: false, refreshing: false, From 0058817b761185bc2d03d148e16996257cb18030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 18:59:54 +0100 Subject: [PATCH 15/17] Debug report.py lang to locales folder --- analysis/classes/report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysis/classes/report.py b/analysis/classes/report.py index d38d26e..9146bb2 100644 --- a/analysis/classes/report.py +++ b/analysis/classes/report.py @@ -34,9 +34,9 @@ class Report(object): self.userlang = get_config(("frontend", "user_lang")) # Load template language - if not re.match("^[a-z]{2}$", self.userlang): + if not re.match("^[a-z]{2,3}$", self.userlang): self.userlang = "en" - with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "lang/{}.json".format(self.userlang)))as f: + with open(os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), "locales/{}.json".format(self.userlang))) as f: self.template = json.load(f)["report"] def read_json(self, json_path): From 9988f0d5cf9286634ace066a21019209dda5461b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 19:03:19 +0100 Subject: [PATCH 16/17] Debug report.vue file --- app/frontend/src/views/report.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/views/report.vue b/app/frontend/src/views/report.vue index 131b2ae..5bfe8ff 100644 --- a/app/frontend/src/views/report.vue +++ b/app/frontend/src/views/report.vue @@ -31,7 +31,7 @@ </div> <div v-else-if="alerts.low.length >= 1" class="low-wrapper"> <div class="center"> - <h1 class="warning-title" v-html="$t('report.low_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.moderate.low] })"></h1> + <h1 class="warning-title" v-html="$t('report.low_msg', { nb: $i18n.messages[$i18n.locale].report.numbers[alerts.low.length] })"></h1> <button class="btn btn-report-low-light" v-on:click="new_capture()">{{ $t("report.start_new_capture") }}</button> <button class="btn btn-report-low" @click="show_report=true;results=false;">{{ $t("report.show_full_report") }}</button> </div> From efe1e3f864a878a184b21c23d665c3b517246a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Aime?= <felix.aime@kaspersky.com> Date: Mon, 8 Feb 2021 19:23:29 +0100 Subject: [PATCH 17/17] Update 'cause of JSON bug --- analysis/locales/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/analysis/locales/en.json b/analysis/locales/en.json index 6d0e69f..94a2987 100644 --- a/analysis/locales/en.json +++ b/analysis/locales/en.json @@ -38,7 +38,7 @@ }, "IOC-05": { "title": "A DNS request have been done to the domain {} which is a Free DNS.", - "description": "The domain name {} is using a Free DNS service. This kind of service is commonly used by cybercriminals or state-sponsored threat actors during their operations. It is very suspicious that an application running in background use this kind of service, please investigate.", + "description": "The domain name {} is using a Free DNS service. This kind of service is commonly used by cybercriminals or state-sponsored threat actors during their operations. It is very suspicious that an application running in background use this kind of service, please investigate." }, "IOC-06": { "title": "A DNS request have been done to the domain {} which contains a suspect TLD.", @@ -54,7 +54,7 @@ }, "SSL-01": { "title": "SSL connection done on a non standard port ({}) to {}", - "description": "It is not common to see SSL connections issued from smartphones using non-standard ports. Even this can be totally legit, we recommend to check the reputation of {}, by looking at its WHOIS record, the associated autonomus system, its creation date, and by searching it the internet.", + "description": "It is not common to see SSL connections issued from smartphones using non-standard ports. Even this can be totally legit, we recommend to check the reputation of {}, by looking at its WHOIS record, the associated autonomus system, its creation date, and by searching it the internet." }, "SSL-02": { "title": "An SSL connection to {} is using a free certificate.", @@ -71,7 +71,7 @@ "SNORT-01": { "title": "Suricata rule tiggered: {}", "description": "A network detection rule has been triggered. It's likely that your device has been compromised or have some suspect behaviour." - }, + } }, "report": { "numbers": [ @@ -105,4 +105,4 @@ "low_msg": "You have only {} low alert(s), don't hesitate to check them.", "none_msg": "Everything looks fine, zero alerts. Don't hesitate to check the uncategorized communications, if any." } -} +} \ No newline at end of file
{}{}{}{}