From ea59a506f800611b45d695e50f2780b75b53809d Mon Sep 17 00:00:00 2001
From: Kuba Orlik
Date: Sat, 13 Aug 2022 22:42:50 +0200
Subject: [PATCH] Problem: nieznany cel. Fixes #68 #97
---
components/report-window/deduce-problems.tsx | 2 +
components/report-window/email-content.tsx | 19 +++
components/report-window/fake-clusters.ts | 56 +++++++++
.../problems/unknown-purpose.tsx | 109 ++++++++++++++++++
components/report-window/report-window.scss | 17 +++
components/report-window/report-window.tsx | 2 +-
components/sidebar/stolen-data-cluster.tsx | 6 +-
diag.html | 13 +++
diag.tsx | 65 +++++++++++
esbuild.config.js | 1 +
extended-request.ts | 14 ++-
request-cluster.ts | 22 +++-
stolen-data-entry.ts | 14 +++
util.ts | 34 +++++-
14 files changed, 362 insertions(+), 12 deletions(-)
create mode 100644 components/report-window/fake-clusters.ts
create mode 100644 components/report-window/problems/unknown-purpose.tsx
create mode 100644 diag.html
create mode 100644 diag.tsx
diff --git a/components/report-window/deduce-problems.tsx b/components/report-window/deduce-problems.tsx
index 5512aa3..7a26e95 100644
--- a/components/report-window/deduce-problems.tsx
+++ b/components/report-window/deduce-problems.tsx
@@ -5,6 +5,7 @@ import { Problem } from './problems/problem';
import { TransferOutsideEU } from './problems/transfer-outside-eu';
import { UnknownIdentity } from './problems/unknown-identity';
import { UnknownLegalBasis } from './problems/unknown-legal-basis';
+import { UnknownPurposes } from './problems/unknown-purpose';
import { UnlawfulCookieAccess } from './problems/unlawful-cookies';
export default function deduceProblems(
@@ -13,6 +14,7 @@ export default function deduceProblems(
): Problem[] {
return [
NoInformationAtAllProblem,
+ UnknownPurposes,
UnlawfulCookieAccess,
UnknownLegalBasis,
UnknownIdentity,
diff --git a/components/report-window/email-content.tsx b/components/report-window/email-content.tsx
index 5115c32..c0f0109 100644
--- a/components/report-window/email-content.tsx
+++ b/components/report-window/email-content.tsx
@@ -7,6 +7,8 @@ import './email-content.scss';
import { Fragment, useState } from 'react';
import emailIntro from './email-intro';
import { reportIntro } from './report-intro';
+import { downloadText } from '../../util';
+import { getFakeClusterData } from './fake-clusters';
const SS_URL = 'http://65.108.60.135:3000';
@@ -136,6 +138,23 @@ export default function EmailContent({
) : null}
+
);
diff --git a/components/report-window/fake-clusters.ts b/components/report-window/fake-clusters.ts
new file mode 100644
index 0000000..14da1e7
--- /dev/null
+++ b/components/report-window/fake-clusters.ts
@@ -0,0 +1,56 @@
+// good for diagnostic purposes
+
+import { RequestCluster } from '../../request-cluster';
+import { DataLocation } from '../../stolen-data-entry';
+
+export type FakeRequestClusterData = {
+ id: string;
+ hasCookies: boolean;
+ hasMarkedCookies: boolean;
+ hasMarks: boolean;
+ exposesOriginWhere: DataLocation[];
+ exposesOrigin: boolean;
+};
+
+export function getFakeClusterData(
+ clusters: Record
+): Record {
+ return Object.fromEntries(
+ Object.entries(clusters).map(([key, cluster]) => [key, cluster.makeDataForFake()])
+ );
+}
+
+export function makeFakeClusters(
+ fake_clusters_data: Record
+): Record {
+ return Object.fromEntries(
+ Object.entries(fake_clusters_data).map(([key, cluster_data]) => [
+ key,
+ new FakeCluster(cluster_data),
+ ])
+ );
+}
+
+export class FakeCluster extends RequestCluster {
+ constructor(public data: FakeRequestClusterData) {
+ super(data.id);
+
+ for (const key of [
+ 'hasCookies',
+ 'hasMarkedCookies',
+ 'hasMarks',
+ 'exposesOriginWhere',
+ 'exposesOrigin',
+ ]) {
+ //@ts-ignore
+ this[key] = () => {
+ //@ts-ignore
+ return this.data[key];
+ };
+ }
+ }
+
+ hasCookies() {
+ return this.data.hasCookies;
+ }
+}
diff --git a/components/report-window/problems/unknown-purpose.tsx b/components/report-window/problems/unknown-purpose.tsx
new file mode 100644
index 0000000..31afade
--- /dev/null
+++ b/components/report-window/problems/unknown-purpose.tsx
@@ -0,0 +1,109 @@
+import { RequestCluster } from '../../../request-cluster';
+import { dataLocationToText, wordlist } from '../../../util';
+import { ExplainerKey } from '../explainers';
+import { v } from '../verbs';
+import { Problem } from './problem';
+
+export class UnknownPurposes extends Problem {
+ getNecessaryExplainers(): ExplainerKey[] {
+ return [];
+ }
+
+ isHostAffected(host: string) {
+ const answers = this.answers.hosts[host];
+ if (!answers) {
+ return false;
+ }
+ return (
+ ['not_mentioned', 'not_before_making_a_choice'].includes(answers.present) &&
+ ['no', 'not_sure'].includes(answers.was_processing_necessary) &&
+ (this.clusters[host].hasCookies() || this.clusters[host].exposesOrigin())
+ );
+ }
+
+ qualifies(): boolean {
+ return Object.keys(this.answers.hosts).some((host) => this.isHostAffected(host));
+ }
+
+ getAffectedClusters(): RequestCluster[] {
+ return Object.keys(this.answers.hosts)
+ .filter((host) => this.isHostAffected(host))
+ .map((host) => this.clusters[host]);
+ }
+
+ getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
+ const _ = (key: string) => v(key, this.answers.zaimek);
+ const affected_clusters = this.getAffectedClusters();
+ const has_history = affected_clusters.some((cluster) => cluster.exposesOrigin());
+ const has_cookies = affected_clusters.some((cluster) => cluster.hasCookies());
+
+ return (
+ <>
+ Cele przetwarzania danych
+
+ Państwa strona{' '}
+ {mode == 'email'
+ ? `ujawniła dane ${_('mnie')} dotyczące`
+ : 'ujawnia dane dotyczące użytkowników'}{' '}
+ w zakresie{' '}
+ {wordlist([
+ ...(has_cookies ? ['treści plików cookies'] : []),
+ ...(has_history
+ ? [
+ mode === 'email'
+ ? `części ${_('mojej')} historii przeglądania`
+ : `części historii przeglądania`,
+ ]
+ : []),
+ ])}{' '}
+ podmiotom, które są właścicielami nastepujących domen:
+
+
+ {affected_clusters.map((cluster, index) => {
+ const locations = cluster.exposesOriginWhere();
+ return (
+ -
+ {cluster.id}:{' '}
+ {wordlist([
+ ...(cluster.hasCookies() ? ['treść plików cookies'] : []),
+ ...(cluster.exposesOrigin()
+ ? [
+ (mode === 'email'
+ ? `część ${_('mojej')} historii przeglądania`
+ : `część historii przeglądania użytkownika`) +
+ ' (' +
+ wordlist(
+ locations.map((l) => dataLocationToText(l))
+ ) +
+ ')',
+ ]
+ : []),
+ ])}
+ {index === affected_clusters.length - 1 ? '.' : ';'}
+
+ );
+ })}
+
+ {mode === 'email' ? (
+ tone === 'official' ? (
+
+ Proszę o wskazanie, jakie są cele takiego przetwarzania danych, które
+ mnie dotyczą.
+
+ ) : (
+
+ Apeluję o umieszczenie informacji na temat na Państwa stronie, aby jej
+ użytkownicy mogli podejmować w pełni świadome wybory dotyczące
+ przetwarzania danych ich dotyczących.
+
+ )
+ ) : (
+
+ Zalecenie: warto dodać informacje o tym, jakie są cele
+ ujawniania wyżej opisanych danych wyżej opisanym podmiotom trzecim.
+
+ )}
+ >
+ );
+ }
+}
diff --git a/components/report-window/report-window.scss b/components/report-window/report-window.scss
index f612dbc..1724d29 100644
--- a/components/report-window/report-window.scss
+++ b/components/report-window/report-window.scss
@@ -5,6 +5,17 @@
font-family: 'OpenSans' !important;
}
+#app {
+ min-height: 100vh;
+ display: flex;
+ flex-flow: column;
+}
+
+#main-section {
+ flex-grow: 1;
+ margin-bottom: 20px; // to contain diag section
+}
+
html {
font-size: 1rem;
}
@@ -289,3 +300,9 @@ h1 {
color: $ultra-black-color;
}
}
+
+.diag-toolbox {
+ position: fixed;
+ bottom: 10px;
+ left: 10px;
+}
diff --git a/components/report-window/report-window.tsx b/components/report-window/report-window.tsx
index c51a7ff..5dc58e8 100644
--- a/components/report-window/report-window.tsx
+++ b/components/report-window/report-window.tsx
@@ -110,7 +110,7 @@ function Report() {
)}
-
+
);
} catch (e) {
diff --git a/components/sidebar/stolen-data-cluster.tsx b/components/sidebar/stolen-data-cluster.tsx
index 65d0514..5f41074 100644
--- a/components/sidebar/stolen-data-cluster.tsx
+++ b/components/sidebar/stolen-data-cluster.tsx
@@ -103,13 +103,11 @@ export default function StolenDataCluster({
shorthost,
minValueLength,
cookiesOnly,
- refreshToken,
cookiesOrOriginOnly,
detailsVisibility,
}: {
origin: string;
shorthost: string;
- refreshToken: number;
minValueLength: number;
cookiesOnly: boolean;
cookiesOrOriginOnly: boolean;
@@ -129,6 +127,10 @@ export default function StolenDataCluster({
data-version={version}
checked={cluster.hasMarks()}
onChange={() => {
+ console.log('Clicked checkbox!', {
+ cluster_id: cluster.id,
+ has_marks: cluster.hasMarks(),
+ });
cluster.hasMarks() ? cluster.undoMark() : cluster.autoMark();
getMemory().emit('change', cluster.id);
}}
diff --git a/diag.html b/diag.html
new file mode 100644
index 0000000..7d3794d
--- /dev/null
+++ b/diag.html
@@ -0,0 +1,13 @@
+
+
+
+ RENTGEN DIAG
+
+
+
+
+
+
+
+
+
diff --git a/diag.tsx b/diag.tsx
new file mode 100644
index 0000000..5239130
--- /dev/null
+++ b/diag.tsx
@@ -0,0 +1,65 @@
+import React, { Fragment } from 'react';
+import ReactDOM from 'react-dom';
+import EmailContent from './components/report-window/email-content';
+
+import { makeFakeClusters } from './components/report-window/fake-clusters';
+
+class ErrorBoundary extends React.Component {
+ constructor(props: any) {
+ super(props);
+ this.state = { hasError: false, error: null };
+ }
+
+ static getDerivedStateFromError(error: any) {
+ return { hasError: true, error };
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return Something went wrong.
;
+ }
+ return this.props.children;
+ }
+}
+
+function Diag() {
+ const [json, setjson] = React.useState(
+ JSON.stringify({ answers: { hosts: {} }, visited_url: '', fake_clusters_data: {} })
+ );
+ const { answers, visited_url, fake_clusters_data } = JSON.parse(json);
+ const fake_clusters = makeFakeClusters(fake_clusters_data);
+ return (
+
+
+
+
+
+ {
+ alert('download!');
+ },
+ user_role: 'user',
+ }}
+ />
+
+
+ );
+}
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('app')
+);
diff --git a/esbuild.config.js b/esbuild.config.js
index f747889..873010c 100644
--- a/esbuild.config.js
+++ b/esbuild.config.js
@@ -48,6 +48,7 @@ esbuild
'components/sidebar/sidebar.tsx',
'components/report-window/report-window.tsx',
'background.ts',
+ 'diag.tsx',
'styles/global.scss',
'styles/fonts.scss',
],
diff --git a/extended-request.ts b/extended-request.ts
index 4c82f8c..462e990 100644
--- a/extended-request.ts
+++ b/extended-request.ts
@@ -1,5 +1,5 @@
'use strict';
-import { StolenDataEntry } from './stolen-data-entry';
+import { DataLocation, StolenDataEntry } from './stolen-data-entry';
import {
flattenObjectEntries,
getshorthost,
@@ -164,12 +164,12 @@ export default class ExtendedRequest {
);
}
- exposesOrigin() {
+ exposesOriginWhere(): null | DataLocation {
const host = this.originalHost;
const path = this.originalPathname || '/';
const shorthost = getshorthost(host);
if (this.getReferer().includes(shorthost)) {
- return true;
+ return { path: this.url, source: 'header', key: 'Referer' };
}
for (const entry of this.stolenData) {
if (
@@ -177,10 +177,14 @@ export default class ExtendedRequest {
entry.value.includes(path) ||
entry.value.includes(shorthost)
) {
- return true;
+ return entry.toDataLocation();
}
}
- return false;
+ return null;
+ }
+
+ exposesOrigin() {
+ return this.exposesOriginWhere() !== null;
}
private getAllStolenData(): StolenDataEntry[] {
diff --git a/request-cluster.ts b/request-cluster.ts
index 0e4b9a8..70c6288 100644
--- a/request-cluster.ts
+++ b/request-cluster.ts
@@ -1,6 +1,7 @@
+import { FakeRequestClusterData } from './components/report-window/fake-clusters';
import ExtendedRequest from './extended-request';
import { SaferEmitter } from './safer-emitter';
-import { Sources, StolenDataEntry } from './stolen-data-entry';
+import { DataLocation, Sources, StolenDataEntry } from './stolen-data-entry';
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
@@ -171,7 +172,13 @@ export class RequestCluster extends SaferEmitter {
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
}
- exposesOrigin() {
+ exposesOriginWhere(): DataLocation[] {
+ return this.requests
+ .map((request) => request.exposesOriginWhere())
+ .filter((l) => l !== null) as DataLocation[];
+ }
+
+ exposesOrigin(): boolean {
return this.requests.some((request) => request.exposesOrigin());
}
@@ -203,4 +210,15 @@ export class RequestCluster extends SaferEmitter {
}
return types_of_data.join(', ');
}
+
+ makeDataForFake(): FakeRequestClusterData {
+ return {
+ id: this.id,
+ hasCookies: this.hasCookies(),
+ hasMarkedCookies: this.hasMarkedCookies(),
+ hasMarks: this.hasMarks(),
+ exposesOriginWhere: this.exposesOriginWhere(),
+ exposesOrigin: this.exposesOrigin(),
+ };
+ }
}
diff --git a/stolen-data-entry.ts b/stolen-data-entry.ts
index 6c110a2..9be09f2 100644
--- a/stolen-data-entry.ts
+++ b/stolen-data-entry.ts
@@ -33,6 +33,12 @@ const id = (function* id() {
export type DecodingSchema = 'base64' | 'raw';
+export type DataLocation = {
+ path: string;
+ source: Sources;
+ key: string;
+};
+
export class StolenDataEntry extends SaferEmitter {
public isIAB = false;
public id: number;
@@ -253,4 +259,12 @@ export class StolenDataEntry extends SaferEmitter {
haystack.includes(getshorthost(this.request.origin))
);
}
+
+ toDataLocation(): DataLocation {
+ return {
+ path: this.request.url,
+ source: this.source,
+ key: this.name,
+ };
+ }
}
diff --git a/util.ts b/util.ts
index 516c11d..13bdcc4 100644
--- a/util.ts
+++ b/util.ts
@@ -1,5 +1,6 @@
import { EventEmitter } from 'events';
import React from 'react';
+import { DataLocation, Sources } from './stolen-data-entry';
export type Unpromisify = T extends Promise ? R : T;
export type Unarray = T extends Array ? R : T;
@@ -283,8 +284,37 @@ export function normalizeForClassname(string: string) {
}
export function wordlist(words: string[]) {
- return words.reduce(
- (acc, word, i) => `${acc}${i > 0 ? (i < words.length - 1 ? ',' : ' i') : ''} ${word}`,
+ return Array.from(new Set(words)).reduce(
+ (acc, word, i) =>
+ `${acc}${
+ i > 0 ? (i < words.length - 1 ? ', ' : Math.random() > 0.5 ? ' i ' : ' oraz ') : ''
+ }${word}`,
''
);
}
+
+const source_to_word: Record = {
+ cookie: 'plik cookie o nazwie',
+ pathname: 'fragment ścieżki w URL',
+ queryparams: 'query params w URL o nazwie',
+ header: 'nagłówek HTTP',
+ request_body: 'body zapytania HTTP, pod kluczem',
+};
+
+export function dataLocationToText(l: DataLocation) {
+ return `${source_to_word[l.source]} ${l.key}`;
+}
+
+export function downloadText(filename: string, text: string) {
+ // https://stackoverflow.com/questions/45831191/generate-and-download-file-from-js
+ var element = document.createElement('a');
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
+ element.setAttribute('download', filename);
+
+ element.style.display = 'none';
+ document.body.appendChild(element);
+
+ element.click();
+
+ document.body.removeChild(element);
+}