Merge pull request 'Problem: nieznany cel. Dodanie narzędzi diagnostycznych' (#99) from unknown-purpose into develop
Reviewed-on: #99
This commit is contained in:
commit
1106e86b41
@ -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,
|
||||
|
@ -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({
|
||||
</p>
|
||||
</section>
|
||||
) : null}
|
||||
<div className="diag-toolbox">
|
||||
<a
|
||||
href="#"
|
||||
onClick={() =>
|
||||
downloadText(
|
||||
'diag.json',
|
||||
JSON.stringify({
|
||||
answers,
|
||||
fake_clusters_data: getFakeClusterData(clusters),
|
||||
visited_url,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Pobierz plik diagnostyczny
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
56
components/report-window/fake-clusters.ts
Normal file
56
components/report-window/fake-clusters.ts
Normal file
@ -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<string, RequestCluster>
|
||||
): Record<string, FakeRequestClusterData> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(clusters).map(([key, cluster]) => [key, cluster.makeDataForFake()])
|
||||
);
|
||||
}
|
||||
|
||||
export function makeFakeClusters(
|
||||
fake_clusters_data: Record<string, FakeRequestClusterData>
|
||||
): Record<string, FakeCluster> {
|
||||
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;
|
||||
}
|
||||
}
|
109
components/report-window/problems/unknown-purpose.tsx
Normal file
109
components/report-window/problems/unknown-purpose.tsx
Normal file
@ -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 (
|
||||
<>
|
||||
<h2>Cele przetwarzania danych</h2>
|
||||
<p>
|
||||
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:
|
||||
</p>
|
||||
<ul>
|
||||
{affected_clusters.map((cluster, index) => {
|
||||
const locations = cluster.exposesOriginWhere();
|
||||
return (
|
||||
<li>
|
||||
<strong>{cluster.id}</strong>:{' '}
|
||||
{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 ? '.' : ';'}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{mode === 'email' ? (
|
||||
tone === 'official' ? (
|
||||
<p>
|
||||
Proszę o wskazanie, jakie są cele takiego przetwarzania danych, które
|
||||
mnie dotyczą.
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
)
|
||||
) : (
|
||||
<p>
|
||||
<strong>Zalecenie</strong>: warto dodać informacje o tym, jakie są cele
|
||||
ujawniania wyżej opisanych danych wyżej opisanym podmiotom trzecim.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ function Report() {
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
<section>{result}</section>
|
||||
<section id="main-section">{result}</section>
|
||||
</Fragment>
|
||||
);
|
||||
} catch (e) {
|
||||
|
@ -104,13 +104,11 @@ export default function StolenDataCluster({
|
||||
shorthost,
|
||||
minValueLength,
|
||||
cookiesOnly,
|
||||
refreshToken,
|
||||
cookiesOrOriginOnly,
|
||||
detailsVisibility,
|
||||
}: {
|
||||
origin: string;
|
||||
shorthost: string;
|
||||
refreshToken: number;
|
||||
minValueLength: number;
|
||||
cookiesOnly: boolean;
|
||||
cookiesOrOriginOnly: boolean;
|
||||
@ -130,6 +128,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);
|
||||
}}
|
||||
|
13
diag.html
Normal file
13
diag.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RENTGEN DIAG</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
<script src="/node_modules/react/umd/react.production.min.js"></script>
|
||||
<script src="/node_modules/react-dom/umd/react-dom.production.min.js"></script>
|
||||
<script src="/node_modules/survey-react/survey.react.min.js"></script>
|
||||
<script src="./lib/diag.js"></script>
|
||||
</html>
|
65
diag.tsx
Normal file
65
diag.tsx
Normal file
@ -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<any, { hasError: boolean; error: any }> {
|
||||
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 <h1>Something went wrong.</h1>;
|
||||
}
|
||||
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 (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '50% 50%', minHeight: '100vh' }}>
|
||||
<div>
|
||||
<textarea
|
||||
style={{ width: 'calc(100% - 50px)', height: '100%' }}
|
||||
value={json}
|
||||
onChange={(e) => {
|
||||
setjson(e.target.value);
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<EmailContent
|
||||
{...{
|
||||
answers,
|
||||
visited_url,
|
||||
clusters: fake_clusters,
|
||||
scrRequestPath: '/screenshots',
|
||||
downloadFiles: () => {
|
||||
alert('download!');
|
||||
},
|
||||
user_role: 'user',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<Diag />
|
||||
</ErrorBoundary>,
|
||||
document.getElementById('app')
|
||||
);
|
@ -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',
|
||||
],
|
||||
|
@ -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[] {
|
||||
|
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
34
util.ts
34
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> = T extends Promise<infer R> ? R : T;
|
||||
export type Unarray<T> = T extends Array<infer R> ? 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<Sources, string> = {
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user