]> git.immae.eu Git - perso/Immae/Config/Nix.git/commitdiff
Add anonymize for dmarc_reports
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Sun, 26 Apr 2020 13:22:33 +0000 (15:22 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Sun, 26 Apr 2020 13:36:39 +0000 (15:36 +0200)
modules/private/environment.nix
modules/private/websites/tools/tools/dmarc_reports.nix
modules/private/websites/tools/tools/dmarc_reports/api.php
modules/private/websites/tools/tools/dmarc_reports/app.js
modules/private/websites/tools/tools/dmarc_reports/default.css
modules/private/websites/tools/tools/dmarc_reports/index.html

index 5fbd023f6338028fa071d82bf73faec5c5b9c5df..00026227dba94172a7b14b484e5654f8472d72a9 100644 (file)
@@ -825,6 +825,7 @@ in
             type = submodule {
               options = {
                 mysql = mkMysqlOptions "DMARC" {};
+                anonymous_key = mkOption { type = str; description = "Anonymous hashing key"; };
               };
             };
           };
index 2e445264e39366183a185cbdaaf51ea1f137e3fc..e264e80f421ed1d4ccbd15a23b004642c1739d51 100644 (file)
@@ -12,6 +12,7 @@ rec {
       $dbuser = "${env.mysql.user}";
       $dbpass = "${env.mysql.password}";
       $dbport = "${env.mysql.port}";
+      $anonymous_key = "${env.anonymous_key}";
       ?>
     '';
   }];
@@ -32,7 +33,11 @@ rec {
 
         AllowOverride None
         Options +FollowSymlinks
+
+        SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+        Use LDAPConnect
         Require all granted
+        Require ldap-attribute uid=immae
       </Directory>
       '';
   };
index 9b7f0c02f65063c009cf8f7096ead4896668efbd..5d4657edf17cdbaa4e5a7b2cc447b7f3ea92c6d2 100644 (file)
@@ -18,6 +18,28 @@ function error_die($text, $number) {
   die(json_encode($message));
 }
 
+$anonymous = isset($_GET['anonymous']) && $_GET['anonymous'];
+function maybe_anonymize($string, $long = false) {
+  global $anonymous_key;
+  global $anonymous;
+  if ($anonymous) {
+    if ($long) {
+      return md5($anonymous_key . ":" . $string);
+    } else {
+      return substr(md5($anonymous_key . ":" . $string), 0, 6);
+    }
+  } else {
+    return $string;
+  }
+}
+
+if (!$anonymous && (!isset($_SERVER['HTTP_AUTHORIZATION']) || $_SERVER['HTTP_AUTHORIZATION'] === "")) {
+  header('WWW-Authenticate: Basic realm="Immae"');
+  header('HTTP/1.0 401 Unauthorized');
+  echo "You need to be authenticated to access private information";
+  exit;
+}
+
 if ($mysqli->connect_errno) {
   error_die($mysqli->connect_error, $mysqli->connect_errno);
 }
@@ -27,14 +49,14 @@ if (!isset($_GET['serial'])) {
   $query = $mysqli->query("SELECT DISTINCT domain FROM `report` ORDER BY domain");
   if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); }
   while($row = $query->fetch_assoc()) {
-    $response["domains"][] = $row['domain'];
+    $response["domains"][] = maybe_anonymize($row['domain']);
   }
 
   $response["orgs"] = array();
   $query = $mysqli->query("SELECT DISTINCT org FROM `report` ORDER BY org");
   if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); }
   while($row = $query->fetch_assoc()) {
-    $response["orgs"][] = $row['org'];
+    $response["orgs"][] = maybe_anonymize($row['org']);
   }
 
   $response["dates"] = array();
@@ -55,7 +77,13 @@ if (!isset($_GET['serial'])) {
   $query = $mysqli->query($sql);
   if ($mysqli->error) { error_die($mysqli->error, $mysqli->errno); }
   while($row = $query->fetch_assoc()) {
-    unset($row["raw_xml"]);
+    $wanted_keys = array(
+      'domain', 'org', 'reportid', 'mindate', 'maxdate', 'rcount', 'serial', 'policy_adkim', 'policy_aspf', 'policy_none', 'policy_sp', 'policy_pct', 'spfresult', 'dkimresult'
+    );
+    $row = array_intersect_key($row, array_fill_keys($wanted_keys, '1'));
+    $row["domain"] = maybe_anonymize($row["domain"]);
+    $row["org"] = maybe_anonymize($row["org"]);
+    $row["reportid"] = maybe_anonymize($row["reportid"], true);
     $response["summaries"][] = $row;
   }
 } else {
@@ -76,9 +104,14 @@ if (!isset($_GET['serial'])) {
       $ip = "-";
       $host = "-";
     }
-    $row['ip'] = $ip;
-    $row['host'] = $host;
-    unset($row['ip6']);
+    $wanted_keys = array(
+      'ip', 'host', 'rcount', 'disposition', 'reason', 'dkimdomain', 'dkimresult', 'spfdomain', 'spfresult'
+    );
+    $row = array_intersect_key($row, array_fill_keys($wanted_keys, '1'));
+    $row['ip'] = maybe_anonymize($ip);
+    $row['host'] = maybe_anonymize($host);
+    $row['dkimdomain'] = maybe_anonymize($row['dkimdomain']);
+    $row['spfdomain'] = maybe_anonymize($row['spfdomain']);
     $response["rptrecord"][] = $row;
   }
 }
index 7fe67d01d1345dee47792dadd2c0ab20bcf0a16f..8e8a6c47a57105c39cd007facab3b7f624bab4f2 100644 (file)
@@ -2,7 +2,7 @@ const app = new Vue({
   el: '#app',
   data: {
     info: null,
-    summaries: [],
+    summaries: null,
     selectedSummary: null,
     filterGreen: true,
     filterDomain: null,
@@ -10,16 +10,31 @@ const app = new Vue({
     //filterDate: (new Date()).toISOString().substring(0, 7),
     filterDate: null,
     reverse: true,
+    anonymous: true,
   },
   created: async function () {
     let that = this;
 
-    try {
-      this.info = await this.getInfo();
-      this.summaries = this.info.summaries;
-    } catch (error) {}
+    if ('anonymous' in localStorage) {
+      this.anonymous = JSON.parse(localStorage.anonymous);
+    }
+    this.fetchAll();
   },
   methods: {
+    fetchAll: async function() {
+      try {
+        this.info = await this.getInfo();
+        this.summaries = this.info.summaries;
+      } catch (error) {
+        this.info = null;
+        this.summaries = null;
+      }
+    },
+    toggleAnonymous: function() {
+      this.anonymous = !this.anonymous;
+      localStorage.anonymous = this.anonymous;
+      this.fetchAll();
+    },
     filtered: function () {
       let that = this;
       let filtered = this.summaries.filter(function (summary) {
@@ -53,7 +68,7 @@ const app = new Vue({
       return mindate === this.filterDate || maxdate === this.filterDate;
     },
     printDate: function (date) {
-      return (new Date(date)).toISOString();
+      return (new Date(date)).toISOString().replace("T", " ").replace(/\..*Z$/, " UTC");
     },
     getColor: function (element) {
       if (element.dkimresult === "fail" && element.spfresult === "fail") {
@@ -67,7 +82,8 @@ const app = new Vue({
       }
     },
     getInfo: function (event) {
-      return fetch('api.php').then(function (response) {
+      let anonymous = this.anonymous ? "anonymous=1" : "";
+      return fetch(`api.php?${anonymous}`).then(function (response) {
         if (response.status != 200) { return; }
         return response.text().then(function (body) {
           return JSON.parse(body);
@@ -75,7 +91,8 @@ const app = new Vue({
       });
     },
     getDetails: function (serial) {
-      return fetch(`api.php?serial=${serial}`).then(function (response) {
+      let anonymous = this.anonymous ? "&anonymous=1" : "";
+      return fetch(`api.php?serial=${serial}${anonymous}`).then(function (response) {
         if (response.status != 200) { return; }
         return response.text().then(function (body) {
           return JSON.parse(body);
index 11608cc225f0db3f9bb948f16bb3d7ffa16c8383..9e0c63f153ce21c13ea996954c0f5884be20dc95 100644 (file)
-h1 {\r
-  text-align: center;\r
-}\r
-\r
-table.reportlist {\r
-  margin: 2em auto 2em auto;\r
-  border-collapse: collapse;\r
-  clear: both;\r
-}\r
-\r
-table.reportlist td, table.reportlist th {\r
-  padding:3px;\r
-}\r
-\r
-table.reportlist thead {\r
-  border-top: 1px solid grey;\r
-  border-bottom: 1px solid grey;\r
-\r
-}\r
-table.reportlist tbody tr:first-child td {\r
-  padding-top: 10px;\r
-}\r
-table.reportlist tr.sum {\r
-  border-top: 1px solid grey;\r
-}\r
-table.reportlist tr.selected {\r
-  background-color: lightgrey;\r
-}\r
-.reportdesc {\r
-  font-weight: bold;\r
-  width: 90%;\r
-  margin-left: auto;\r
-  margin-right: auto;\r
-}\r
-\r
-tr.summaryrow {\r
-  cursor: pointer;\r
-}\r
-\r
-tr.summaryrow:hover, tr.summaryrow.selected {\r
-  background-color: lightgray;\r
-  border-left: 1px solid lightgray;\r
-}\r
-\r
-td.reportcell {\r
-  border-bottom: 1px solid lightgray;\r
-  border-left: 1px solid lightgray;\r
-  border-right: 1px solid lightgray;\r
-}\r
-\r
-table.reportdata {\r
-  margin: 0px auto 0px auto;\r
-  border-collapse: separate;\r
-  border-spacing: 2px;\r
-}\r
-\r
-table.reportdata tr th, table.reportdata tr td {\r
-  text-align: center;\r
-  padding: 3px;\r
-}\r
-\r
-table.reportdata tr.red {\r
-  background-color: #FF0000;\r
-}\r
-\r
-table.reportdata tr.orange {\r
-  background-color: #FFA500;\r
-}\r
-\r
-table.reportdata tr.lime {\r
-  background-color: #00FF00;\r
-}\r
-\r
-table.reportdata tr.yellow {\r
-  background-color: #FFFF00;\r
-}\r
-\r
-.optionblock {\r
-  background: lightgrey;\r
-  padding: 0.4em;\r
-  float: right;\r
-  margin: auto 2em 1em auto;\r
-  white-space: nowrap;\r
-}\r
-\r
-.optionlabel {\r
-  font-weight: bold;\r
-  float: left; clear: left; \r
-  margin-right: 1em;\r
-}\r
-\r
-.options {\r
-  font-size: 70%;\r
-  text-align: right;\r
-  border: none;\r
-  width: 97%;\r
-  padding: 0.4em;\r
-}\r
-\r
-.center {\r
-  text-align:center;\r
-}\r
-\r
-.circle_lime:before {\r
-  content: ' \25CF';\r
-  font-size: 25px;\r
-  color: #00FF00;\r
-}\r
-\r
-.circle_red:before {\r
-  content: ' \25CF';\r
-  font-size: 25px;\r
-  color: #FF0000;\r
-}\r
-\r
-.circle_yellow:before {\r
-  content: ' \25CF';\r
-  font-size: 25px;\r
-  color: #FFFF00;\r
-}\r
-\r
-.circle_orange:before {\r
-  content: ' \25CF';\r
-  font-size: 25px;\r
-  color: #FFA500;\r
-}\r
+h1 {
+  text-align: center;
+}
+
+p.warninginfo {
+  text-align: center;
+}
+
+table.reportlist {
+  margin: 2em auto 2em auto;
+  border-collapse: collapse;
+  clear: both;
+}
+
+table.reportlist td, table.reportlist th {
+  padding:3px;
+}
+
+table.reportlist thead {
+  border-top: 1px solid grey;
+  border-bottom: 1px solid grey;
+
+}
+table.reportlist tbody tr:first-child td {
+  padding-top: 10px;
+}
+table.reportlist tr.sum {
+  border-top: 1px solid grey;
+}
+table.reportlist tr.selected {
+  background-color: lightgrey;
+}
+.reportdesc {
+  font-weight: bold;
+  width: 90%;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+tr.summaryrow {
+  cursor: pointer;
+}
+
+tr.summaryrow:hover, tr.summaryrow.selected {
+  background-color: lightgray;
+  border-left: 1px solid lightgray;
+}
+
+td.reportcell {
+  border-bottom: 1px solid lightgray;
+  border-left: 1px solid lightgray;
+  border-right: 1px solid lightgray;
+}
+
+table.reportdata {
+  margin: 0px auto 0px auto;
+  border-collapse: separate;
+  border-spacing: 2px;
+}
+
+table.reportdata tr th, table.reportdata tr td {
+  text-align: center;
+  padding: 3px;
+}
+
+table.reportdata tr.red {
+  background-color: #FF0000;
+}
+
+table.reportdata tr.orange {
+  background-color: #FFA500;
+}
+
+table.reportdata tr.lime {
+  background-color: #00FF00;
+}
+
+table.reportdata tr.yellow {
+  background-color: #FFFF00;
+}
+
+.optionblock {
+  background: lightgrey;
+  padding: 0.4em;
+  float: right;
+  margin: auto 2em 1em auto;
+  white-space: nowrap;
+}
+
+.optionlabel {
+  font-weight: bold;
+  float: left; clear: left; 
+  margin-right: 1em;
+}
+
+.options {
+  font-size: 70%;
+  text-align: right;
+  border: none;
+  width: 97%;
+  padding: 0.4em;
+}
+
+.center {
+  text-align:center;
+}
+
+.circle_lime:before {
+  content: ' \25CF';
+  font-size: 25px;
+  color: #00FF00;
+}
+
+.circle_red:before {
+  content: ' \25CF';
+  font-size: 25px;
+  color: #FF0000;
+}
+
+.circle_yellow:before {
+  content: ' \25CF';
+  font-size: 25px;
+  color: #FFFF00;
+}
+
+.circle_orange:before {
+  content: ' \25CF';
+  font-size: 25px;
+  color: #FFA500;
+}
index ded05e15dee6bf484e06712dbf09878ac5bd745f..af29cdf6c61861dd55b85361cc8ea521034a6733 100644 (file)
 
 <body>
   <div id="app" style="width: 100%">
-    <div class="optionblock" v-if="info">
+    <div class="optionblock">
+      <div class='options'>
+        <span class='optionlabel'>Anonymize</span>
+        <label><input type="radio" :value="false" v-model="anonymous" v-on:click="toggleAnonymous()"> no</label>
+        <label><input type="radio" :value="true" v-model="anonymous" v-on:click="toggleAnonymous()"> yes</label>
+      </div>
+      <template v-if="info">
       <div class='options'>
         <span class='optionlabel'>Hide all-green lines:</span>
         <label><input type="radio" :value="false" v-model="filterGreen"> no</label>
           <option v-for="date in info.dates" :value="date">{{ date }}</option>
         </select>
       </div>
+      </template>
     </div>
 
     <h1 class='main'>DMARC Reports</h1>
+    <p v-if="!info" class="warninginfo">
+    No information could be fetched. If in non-anonymous mode you need to be logged-in
+    </p>
     <table class='reportlist' v-if="summaries">
       <thead>
         <tr>