Compare commits
No commits in common. "develop" and "toolbar-design" have entirely different histories.
develop
...
toolbar-de
9
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"ECLI",
|
||||||
|
"EROD",
|
||||||
|
"targetowania",
|
||||||
|
"targetowaniem",
|
||||||
|
"TSUE"
|
||||||
|
]
|
||||||
|
}
|
117
README.md
|
@ -1,114 +1,17 @@
|
||||||
<h1 style="display: flex; align-items: center;"><img src="./assets/icon-addon-2048.png" alt="Rentgen logo" style="margin-right: 1rem;" width="48"/> Rentgen</h1>
|
<h1 style="display: flex; align-items: center;"><img src="./assets/icon-addon-2048.png" alt="Rentgen logo" style="margin-right: 1rem;" width="48"/> Rentgen</h1>
|
||||||
|
|
||||||
<strong>Rentgen</strong> is an add-on prepared for Firefox-based browsers. This extension will automatically visualize all the data that a given website ~~steals~~ sends to third parties.
|
## Description
|
||||||
|
|
||||||
|
<strong>Rentgen</strong> to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony.
|
||||||
|
|
||||||
|
<strong>Rentgen</strong> is an add-on prepared for Firefox based browsers. This extension will automatically visualize all the data that a given website sends to third parties.
|
||||||
|
|
||||||
Note: At the moment, we support Polish language because this extension generates mail content that is dedicated to Polish website owners. In further versions of this add-on, we will add other languages as well.
|
Note: At the moment, we support Polish language because this extension generates mail content that is dedicated to Polish website owners. In further versions of this add-on, we will add other languages as well.
|
||||||
|
|
||||||
**Features:**
|
## How build and run add-on
|
||||||
|
|
||||||
- analysis of web traffic generated by the visited website;
|
1. Use latest node 16.x and npm 8.x
|
||||||
- visualization of data transmitted to third parties by the visited site (user's browsing history and cookies);
|
2. `npm install`
|
||||||
- preparation of screenshots of development tools as evidence of data transmitted to third parties;
|
3. `npm run build`
|
||||||
- assisting in the evaluation of potential work areas for compliance with GDPR;
|
4. The build code is located in the `lib/` directory
|
||||||
- generating a report or email content that can be sent to an administrator and Personal Data Protection Office in Poland.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Firefox: https://addons.mozilla.org/en-US/firefox/addon/rentgen/
|
|
||||||
|
|
||||||
## How to build and run Rentgen on your own
|
|
||||||
|
|
||||||
### Pre-requirements
|
|
||||||
|
|
||||||
- OS: Linux x86_64
|
|
||||||
- Node.js: 16.x version
|
|
||||||
- npm: 7.x version or higher
|
|
||||||
|
|
||||||
### Build steps
|
|
||||||
|
|
||||||
1. Pull repository or download a zip package
|
|
||||||
2. Go to the root directory of the pulled repository
|
|
||||||
3. Run command: `npm install`
|
|
||||||
4. Run command: `npm run build`
|
|
||||||
5. Run command: `npm run create-package`
|
|
||||||
6. Go to the `web-ext-artifacts` directory
|
|
||||||
7. You will find a zip archive: `rentgen-x-x-x.zip` (`x-x-x` means add-on version)
|
|
||||||
|
|
||||||
### Run steps
|
|
||||||
|
|
||||||
1. Run Firefox and go to `about:debugging`
|
|
||||||
2. Click _This Firefox_ tab
|
|
||||||
3. Click _Load Temporary Add-on..._ button
|
|
||||||
4. Pick the zip archive from last step of build process.
|
|
||||||
|
|
||||||
## Issue tracker
|
|
||||||
|
|
||||||
If you find a problem, please send us an email: kontakt@internet-czas-dzialac.pl
|
|
||||||
|
|
||||||
We don't receive issues on Microsoft Github.
|
|
||||||
|
|
||||||
Each issue will be reviewed and moved to an internal issues list of our Gitea instance: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. We use Gitea and most likely in the future with the federalization of Gitea, we will be able to let users in to report issues directly from the Gitea site.
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
<img src="./assets/screenshots/image-14.png" />
|
|
||||||
<img src="./assets/screenshots/image-15.png" />
|
|
||||||
<img src="./assets/screenshots/3a.png" />
|
|
||||||
<img src="./assets/screenshots/3b.png" />
|
|
||||||
<img src="./assets/screenshots/4a.png" />
|
|
||||||
<img src="./assets/screenshots/4b.png" />
|
|
||||||
<img src="./assets/screenshots/5a.png" />
|
|
||||||
<img src="./assets/screenshots/5b.png" />
|
|
||||||
<img src="./assets/screenshots/2022-07-14_21-04.png" />
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<strong>Rentgen</strong> to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony. Wtyczka obrazuje ilość skryptów śledzących na stronie internetowej i pomaga w sformułowaniu maila do administratora strony, który może być podstawą do skargi RODO w Urzędzie Ochrony Danych Osobowych.
|
|
||||||
|
|
||||||
**Funkcje Rentgena:**
|
|
||||||
|
|
||||||
- analiza ruchu sieciowego generowanego przez stronę internetową;
|
|
||||||
- wizualizacja danych przekazanych do podmiotów trzecich przez odwiedzaną stronę (historia przeglądania użytkownika oraz jego ciasteczka);
|
|
||||||
- przygotowywanie zrzutów ekranów narzędzi deweloperskich będących dowodem przekazanych danych do podmiotów trzecich;
|
|
||||||
- pomoc w oszacowaniu potencjalnych obszarów roboczych względem zgodności z RODO;
|
|
||||||
- generowanie raportu lub treści maila, którą można wysłać do administratora oraz Urzędu Ochrony Danych Osobowych.
|
|
||||||
|
|
||||||
## Instalacja
|
|
||||||
|
|
||||||
Firefox: https://addons.mozilla.org/pl/firefox/addon/rentgen/
|
|
||||||
|
|
||||||
## Jak zbudować i uruchomić Rentgena ze źródeł
|
|
||||||
|
|
||||||
### Wymagania wstępne
|
|
||||||
|
|
||||||
- System operacyjny: Linux x86_64
|
|
||||||
- Node.js: 16.x
|
|
||||||
- npm: 7.x lub wyższy
|
|
||||||
|
|
||||||
### Proces budowy
|
|
||||||
|
|
||||||
1. Pobierz repozytorium przez `git pull https://git.internet-czas-dzialac.pl/icd/rentgen.git` lub pobierz archwium zip
|
|
||||||
2. Przejdź do głównego katalogu pobranego repozytorium
|
|
||||||
3. Uruchom komendę: `npm install`
|
|
||||||
4. Uruchom komendę: `npm run build`
|
|
||||||
5. Uruchom komendę: `npm run create-package`
|
|
||||||
6. Przejdź do katalogu `web-ext-artifacts`
|
|
||||||
7. Znajdziesz tam archiwum zip: `rentgen-x-x-x.zip` (`x-x-x` oznaczają wersję wtyczki)
|
|
||||||
|
|
||||||
### Kroki do uruchomienia
|
|
||||||
|
|
||||||
1. Uruchom Firefoxa i przejdź do strony `about:debugging`
|
|
||||||
2. Kliknij zakładkę _This Firefox_
|
|
||||||
3. Kliknij przycisk _Load Temporary Add-on..._
|
|
||||||
4. Wybierz archiwum, które zbudowałeś w ostatnim kroku procesu budowy
|
|
||||||
|
|
||||||
## Zgłaszanie błędów
|
|
||||||
|
|
||||||
Jeżeli znajdziesz jakieś problem, napisz do nas maila: kontakt@internet-czas-dzialac.pl
|
|
||||||
|
|
||||||
Nie przyjmujemy zgłoszeń na platformie Microsoft Github.
|
|
||||||
|
|
||||||
Każdy problem zostanie sprawdzony i przeniesiony na wewnętrzną listę problemów na naszej instancji Gitea: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. Korzystamy z Gitea i najprawdopodobniej w przyszłości dzięki federalizacji Gitea będziemy w stanie wpuścić użytkowników do zgłaszania błędów bezpośrednio ze strony Gitea.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 460 KiB |
Before Width: | Height: | Size: 480 KiB |
Before Width: | Height: | Size: 526 KiB |
Before Width: | Height: | Size: 414 KiB |
Before Width: | Height: | Size: 275 KiB |
Before Width: | Height: | Size: 316 KiB |
Before Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 215 KiB |
Before Width: | Height: | Size: 362 KiB |
Before Width: | Height: | Size: 468 KiB |
Before Width: | Height: | Size: 752 KiB |
Before Width: | Height: | Size: 346 KiB |
|
@ -2,24 +2,18 @@ import { RequestCluster } from '../../request-cluster';
|
||||||
import { ParsedAnswers } from './parse-answers';
|
import { ParsedAnswers } from './parse-answers';
|
||||||
import NoInformationAtAllProblem from './problems/no-information-at-all';
|
import NoInformationAtAllProblem from './problems/no-information-at-all';
|
||||||
import { Problem } from './problems/problem';
|
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';
|
import { UnlawfulCookieAccess } from './problems/unlawful-cookies';
|
||||||
|
|
||||||
export default function deduceProblems(
|
export default function deduceProblems(
|
||||||
answers: ParsedAnswers,
|
answers: ParsedAnswers,
|
||||||
clusters: Record<string, RequestCluster>
|
clusters: Record<string, RequestCluster>
|
||||||
): Problem[] {
|
): Problem[] {
|
||||||
return [
|
const problems = [];
|
||||||
NoInformationAtAllProblem,
|
if (answers.popup_type === 'none') {
|
||||||
UnknownPurposes,
|
problems.push(new NoInformationAtAllProblem(answers, clusters));
|
||||||
UnlawfulCookieAccess,
|
}
|
||||||
UnknownLegalBasis,
|
if (UnlawfulCookieAccess.qualifies(answers, Object.values(clusters))) {
|
||||||
UnknownIdentity,
|
problems.push(new UnlawfulCookieAccess(answers, clusters));
|
||||||
TransferOutsideEU,
|
}
|
||||||
]
|
return problems;
|
||||||
.map((c) => new c(answers, clusters))
|
|
||||||
.filter((p) => p.qualifies());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,12 @@ h1 {
|
||||||
margin-bottom: calc(24 / 16 * 1rem);
|
margin-bottom: calc(24 / 16 * 1rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.generator-container {
|
||||||
|
max-width: 100ex;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: calc(14 / 16 * 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
.mail-container {
|
.mail-container {
|
||||||
box-shadow: rgba(12, 12, 13, 0.1) 0px 1px 4px 0px;
|
box-shadow: rgba(12, 12, 13, 0.1) 0px 1px 4px 0px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
@ -47,28 +53,3 @@ h1 {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons-email-container {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 1rem;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
margin: 2rem 0;
|
|
||||||
padding: 1em 0;
|
|
||||||
|
|
||||||
&--single {
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sv_prev_btn,
|
|
||||||
.sv_next_btn {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sv_prev_btn {
|
|
||||||
grid-column: 2/3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sv_next_btn--single {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
import { RequestCluster } from '../../request-cluster';
|
import { RequestCluster } from '../../request-cluster';
|
||||||
|
import { getDate } from '../../util';
|
||||||
import deduceProblems from './deduce-problems';
|
import deduceProblems from './deduce-problems';
|
||||||
import { Explainers } from './explainers';
|
import { Explainers } from './explainers';
|
||||||
import { ParsedAnswers } from './parse-answers';
|
import { ParsedAnswers } from './parse-answers';
|
||||||
import { v } from './verbs';
|
import { v } from './verbs';
|
||||||
import './email-content.scss';
|
import './email-content.scss';
|
||||||
import { Fragment, useState } from 'react';
|
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';
|
declare var PLUGIN_NAME: string;
|
||||||
|
declare var PLUGIN_URL: string;
|
||||||
|
|
||||||
export default function EmailContent({
|
export default function EmailContent({
|
||||||
answers,
|
answers,
|
||||||
visited_url,
|
visited_url,
|
||||||
clusters,
|
clusters,
|
||||||
scrRequestPath,
|
|
||||||
downloadFiles,
|
|
||||||
user_role,
|
|
||||||
}: {
|
}: {
|
||||||
answers: ParsedAnswers;
|
answers: ParsedAnswers;
|
||||||
visited_url: string;
|
visited_url: string;
|
||||||
clusters: Record<string, RequestCluster>;
|
clusters: Record<string, RequestCluster>;
|
||||||
scrRequestPath: string;
|
|
||||||
downloadFiles: Function;
|
|
||||||
user_role: string;
|
|
||||||
}) {
|
}) {
|
||||||
const _ = (key: string) => v(key, answers.zaimek);
|
const _ = (key: string) => v(key, answers.zaimek);
|
||||||
const problems = deduceProblems(answers, clusters);
|
const problems = deduceProblems(answers, clusters);
|
||||||
|
@ -41,92 +33,56 @@ export default function EmailContent({
|
||||||
function copyTextToClipboard() {
|
function copyTextToClipboard() {
|
||||||
// Should be changed in the future to Clipboard API (https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write#browser_compatibility)
|
// Should be changed in the future to Clipboard API (https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write#browser_compatibility)
|
||||||
let r = document.createRange();
|
let r = document.createRange();
|
||||||
const container = document.querySelector('.mail-container__content');
|
r.selectNode(document.querySelector('.mail-container__content'));
|
||||||
if (!container) return;
|
window.getSelection().addRange(r);
|
||||||
r.selectNode(container);
|
|
||||||
window.getSelection()?.addRange(r);
|
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
window.getSelection()?.removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
setCopy(true);
|
setCopy(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mode = answers.user_role === 'user' ? 'email' : 'report';
|
|
||||||
const email_tone = answers.email_type === 'polite_information' ? 'polite' : 'official';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="generator-container">
|
<div className="generator-container">
|
||||||
<h1>Treść {mode === 'email' ? 'maila' : 'raportu'}</h1>
|
<h1>Treść maila</h1>
|
||||||
<div className="mail-container">
|
<div className="mail-container">
|
||||||
<div className="mail-container__header">
|
<div className="mail-container__header">
|
||||||
<div className="mail-container__header--control"></div>
|
<div className="mail-container__header--control"></div>
|
||||||
</div>
|
</div>
|
||||||
<article className="mail-container__content">
|
<article className="mail-container__content">
|
||||||
{mode === 'email'
|
<p>Dzień dobry,</p>
|
||||||
? emailIntro(email_tone, _, visited_url)
|
<p>
|
||||||
: reportIntro(visited_url)}
|
w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po
|
||||||
{problems.map((problem, index) => {
|
podejrzeniu ruchu sieciowego generowanego przez tę stronę za pomocą
|
||||||
const Component = problem.getEmailContent.bind(problem);
|
wtyczki <a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w przeglądarce Firefox{' '}
|
||||||
return <Component mode={mode} tone={email_tone} key={index} />;
|
{_('mam')} pytania dotyczące przetwarzania {_('moich')} danych
|
||||||
})}
|
osobowych, na które nie {_('znalazłem')} odpowiedzi nigdzie na Państwa
|
||||||
|
stronie.
|
||||||
|
</p>
|
||||||
|
{problems.map((problem) => problem.getEmailContent())}
|
||||||
{explainers.map((explainer) => explainer(answers.zaimek))}
|
{explainers.map((explainer) => explainer(answers.zaimek))}
|
||||||
<h2>Państwa rola jako współadministratora danych osobowych</h2>
|
<h2>Państwa rola jako współadministratora danych osobowych</h2>
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>
|
<p>
|
||||||
{_('Zwracam')} Państwa uwagę na fakt, że w myśl{' '}
|
{_('Zwracam')} Państwa uwagę na fakt, że w myśl{' '}
|
||||||
<a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
|
<a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
|
||||||
treści wyroku TSUE w sprawie C-40/17
|
treści wyroku TSUE w sprawie C-40/17
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
poprzez wysyłanie moich danych w wyżej opisanym zakresie stają się
|
poprzez wysyłanie moich danych w wyżej opisanym zakresie stają się
|
||||||
Państwo współadministratorem {_('moich')} danych osobowych, nawet
|
Państwo współadministratorem moich danych osobowych, dlatego ciąży na
|
||||||
jeżeli nie są Państwo bezpośrednimi autorami osadzonych na Państwa
|
Państwu obowiązek odpowiedzi na moje pytania na mocy Art. 12 i 13
|
||||||
stronie skryptów czy innych zasobów ujawniających dane użytkowników
|
Rozporządzenia 2016/679 Parlamentu Europejskiego i Rady (UE) z dnia 27
|
||||||
Państwa strony podmiotom trzecim. Dlatego ciąży na Państwu obowiązek
|
kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z
|
||||||
odpowiedzi na {_('moje')} pytania na mocy Art. 12 i 13
|
przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich
|
||||||
Rozporządzenia 2016/679 Parlamentu Europejskiego i Rady (UE) z dnia
|
danych oraz uchylenia dyrektywy 95/46/WE (ogólne rozporządzenie o
|
||||||
27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z
|
ochronie danych, dalej: „RODO”).
|
||||||
przetwarzaniem danych osobowych i w sprawie swobodnego przepływu
|
|
||||||
takich danych oraz uchylenia dyrektywy 95/46/WE (ogólne
|
|
||||||
rozporządzenie o ochronie danych) – RODO
|
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
W myśl{' '}
|
|
||||||
<a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
|
|
||||||
treści wyroku TSUE w sprawie C-40/17
|
|
||||||
</a>
|
|
||||||
, ponoszą Państwo współodpowiedzialność za skrypty i inne zasoby
|
|
||||||
ujawniajace dane osobowe na Państwa stronie, nawet jeżeli nie są
|
|
||||||
Państwo ich bezpośrednimi autorami.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="buttons-container">
|
||||||
className={
|
<button className="sv_next_btn" onClick={() => copyTextToClipboard()}>
|
||||||
scrRequestPath
|
{copied ? 'Skopiowano!' : 'Kopiuj treść wiadomości'}
|
||||||
? 'buttons-email-container'
|
|
||||||
: 'buttons-email-container buttons-email-container--single'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{scrRequestPath ? (
|
|
||||||
<button
|
|
||||||
className="sv_prev_btn"
|
|
||||||
onClick={() => downloadFiles(`${SS_URL}${scrRequestPath}`)}
|
|
||||||
>
|
|
||||||
Pobierz zrzuty ekranów
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
scrRequestPath ? 'sv_next_btn' : 'sv_next_btn sv_next_btn--single'
|
|
||||||
}
|
|
||||||
onClick={() => copyTextToClipboard()}
|
|
||||||
>
|
|
||||||
{copied ? 'Skopiowano!' : 'Kopiuj treść'}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{copied && user_role === 'user' ? (
|
{copied ? (
|
||||||
<section className="greeting-text">
|
<section className="greeting-text">
|
||||||
<strong>Przed Tobą ostatni krok! 😊</strong>
|
<strong>Przed Tobą ostatni krok! 😊</strong>
|
||||||
<p>
|
<p>
|
||||||
|
@ -138,23 +94,6 @@ export default function EmailContent({
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : 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>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { getDate } from '../../util';
|
|
||||||
|
|
||||||
declare var PLUGIN_NAME: string;
|
|
||||||
declare var PLUGIN_URL: string;
|
|
||||||
|
|
||||||
export default function emailIntro(
|
|
||||||
tone: 'polite' | 'official',
|
|
||||||
_: (verb: string) => string,
|
|
||||||
visited_url: string
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p>{tone == 'polite' ? 'Szanowni Państwo' : 'Dzień dobry'},</p>
|
|
||||||
<p>
|
|
||||||
w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po podejrzeniu ruchu
|
|
||||||
sieciowego generowanego przez tę stronę za pomocą wtyczki{' '}
|
|
||||||
<a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w przeglądarce Firefox{' '}
|
|
||||||
{tone == 'polite' ? (
|
|
||||||
<>
|
|
||||||
{_('chciałbym')} zwrócić Państwa uwagę na kilka potencjalnych problemów ze
|
|
||||||
zgodnością RODO na Państwa stronie.
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{_('mam')} pytania dotyczące przetwarzania {_('moich')} danych osobowych, na
|
|
||||||
które nie {_('znalazłem')} odpowiedzi nigdzie na Państwa stronie.
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
// various explainers that could be related to multiple problems. They are gathered here and added at the end of the email to avoid pasting them multiple times
|
export type ExplainerKey = 'cookies_are_pii';
|
||||||
|
|
||||||
export type ExplainerKey = 'cookies_are_pii' | 'responsibility_for_third_parties';
|
|
||||||
|
|
||||||
export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => JSX.Element> = {
|
export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => JSX.Element> = {
|
||||||
cookies_are_pii: () => (
|
cookies_are_pii: () => (
|
||||||
|
@ -9,63 +7,7 @@ export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => J
|
||||||
<p>
|
<p>
|
||||||
Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
|
Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
|
||||||
osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając „identyfikator
|
osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając „identyfikator
|
||||||
internetowy” i „numer identyfikacyjny” jako przykłady danych osobowych. Losowe
|
internetowy” i „numer identyfikacyjny” jako przykłady danych osobowych.
|
||||||
przypisane identyfikatory mogą nie zawierać imienia i nazwiska osoby, której
|
|
||||||
dotyczą, ani nie prowadzić wprost do ich ustalenia, ale pozwalają odróżnić jedną,
|
|
||||||
daną konkretną osobę, od innych.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Por. komentarz z D. Lubasz [w:] Ochrona Danych Osobowych [red.] D. Lubasz, Warszawa
|
|
||||||
2020 r., str. 81:
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<em>
|
|
||||||
Zidentyfikowaną osobą fizyczną jest osoba, której tożsamość jest ustalona -
|
|
||||||
bezpośrednio i natychmiast, czyli taka, którą bezpośrednio można wskazać,
|
|
||||||
wyodrębnić lub wyróżnić z określonej zbiorowości.{' '}
|
|
||||||
<strong>Nie musi to natomiast polegać na podaniu jej imienia nazwiska</strong>.
|
|
||||||
Konstatacja ta jest zwłaszcza istotna w środowisku cyfrowym, w którym
|
|
||||||
identyfikacja sprowadza się do oznaczenia danego użytkownika w celu wywierania
|
|
||||||
na niego określonego wpływu. (...) Możliwą do zidentyfikowania jest osoba,
|
|
||||||
której tożsamość dopiero administrator może ustalić -{' '}
|
|
||||||
<strong>niezależnie od tego, czy to zrobi, czy nie</strong>.
|
|
||||||
</em>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Podobnie za{' '}
|
|
||||||
<em>
|
|
||||||
P. Litwiński [w:] Rozporządzenie UE w sprawie ochrony osób fizycznych w związku
|
|
||||||
z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich
|
|
||||||
danych, Komentarz [red.] P. Litwiński, Warszawa 2018 r.
|
|
||||||
</em>
|
|
||||||
:
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<em>
|
|
||||||
Jak zwrócono uwagę w nauce prawa, identyfikacja osoby powinna być rozumiana jako
|
|
||||||
możliwość „fizycznego” wskazania tejże osoby, nie zaś jako ustalenie
|
|
||||||
podstawowych danych tej osoby (...). Analogicznie,{' '}
|
|
||||||
<em>identyfikacja osoby nie wymaga znajomości jej imienia lub nazwiska</em>,
|
|
||||||
wymaga natomiast znajomości pewnych unikalnych cech tej osoby, które odróżniają
|
|
||||||
ją od innych osób (...). W ten sam sposób należy więc rozumieć zwrot „można
|
|
||||||
zidentyfikować” - nie tylko jako możliwość odniesienia konkretnej informacji do
|
|
||||||
konkretnej osoby, lecz także jako możliwość wskazania tej osoby, rozumianego
|
|
||||||
jako faktyczne wyodrębnienie jej spośród innych osób.
|
|
||||||
</em>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
responsibility_for_third_parties: () => (
|
|
||||||
<>
|
|
||||||
<h2>Administrator strony ponosi odpowiedzialność za skrypty podmiotów trzecich</h2>
|
|
||||||
<p>
|
|
||||||
W wypadku, gdy ujawnienie czy dostęp do danych osobowych zostało dokonane przez
|
|
||||||
skrypty podmiotów trzecich (np. Google, Facebook, itp), których autorem nie jest
|
|
||||||
Administrator strony, Administrator wciąż jest odpowiedzialny za procesy
|
|
||||||
przetwarzania danych osobowych, jakie realizują te skrypty - w myśl treści{' '}
|
|
||||||
<a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
|
|
||||||
wyroku TSUE w sprawie C-40/17
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +1,26 @@
|
||||||
import { RequestCluster } from '../../request-cluster';
|
|
||||||
|
|
||||||
function generateHostPage(
|
function generateHostPage(
|
||||||
cluster: RequestCluster,
|
host: string,
|
||||||
index: number,
|
index: number,
|
||||||
all_clusters: RequestCluster[]
|
all_hosts: string[]
|
||||||
): { title: string; elements: any[]; visibleIf?: string } {
|
): { title: string; elements: any[]; visibleIf: string } {
|
||||||
function f(name: string, c = cluster) {
|
function f(name: string, h = host) {
|
||||||
return `${c.id.replace(/\./g, '_')}|${name}`;
|
return `${h.replace(/\./g, '_')}|${name}`;
|
||||||
}
|
}
|
||||||
const previous_cluster: RequestCluster | null = index > 0 ? all_clusters[index - 1] : null;
|
const previous_host: string | null = index > 0 ? all_hosts[index - 1] : null;
|
||||||
function defaultValue(name: string) {
|
function defaultValue(name: string) {
|
||||||
if (!previous_cluster) {
|
if (!previous_host) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return { defaultValueExpression: `{${f(name, previous_cluster)}}` };
|
return { defaultValueExpression: `{${f(name, previous_host)}}` };
|
||||||
}
|
}
|
||||||
const domain = cluster.id;
|
|
||||||
const danych = cluster.getDataTypeDescription();
|
|
||||||
return {
|
return {
|
||||||
title: cluster.id,
|
title: host,
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
type: 'radiogroup',
|
type: 'radiogroup',
|
||||||
name: f('present'),
|
name: f('present'),
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
title: `Strona udostępniła właścicielowi domeny ${domain} ${danych}. Cel takiego przetwarzania danych:`,
|
title: `Cel ujawnienia danych właścicielowi domeny ${host}`,
|
||||||
...defaultValue('present'),
|
...defaultValue('present'),
|
||||||
visibleIf: '{popup_type} != "none"',
|
visibleIf: '{popup_type} != "none"',
|
||||||
choices: [
|
choices: [
|
||||||
|
@ -60,10 +56,10 @@ function generateHostPage(
|
||||||
'present'
|
'present'
|
||||||
)}} != "not_mentioned" and {${f('present')}} != "not_before_making_a_choice"`,
|
)}} != "not_mentioned" and {${f('present')}} != "not_before_making_a_choice"`,
|
||||||
choices: [
|
choices: [
|
||||||
{ value: 'consent', text: 'to zgoda (art. 6 ust. 1 lit. a RODO).' },
|
{ value: 'consent', text: 'to zgoda.' },
|
||||||
{
|
{
|
||||||
value: 'legitimate_interest',
|
value: 'legitimate_interest',
|
||||||
text: 'to uzasadniony interes (art. 6 ust. 1 lit. f RODO).',
|
text: 'to uzasadniony interes.',
|
||||||
},
|
},
|
||||||
{ value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' },
|
{ value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' },
|
||||||
],
|
],
|
||||||
|
@ -80,7 +76,7 @@ function generateHostPage(
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
value: 'claims_consent_but_sends_before_consent',
|
value: 'claims_consent_but_sends_before_consent',
|
||||||
text: `Strona wysłała {moje} dane do ${domain} zanim {wyraziłem} na to zgodę`,
|
text: `Strona wysłała {moje} dane do ${host} zanim {wyraziłem} na to zgodę`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'claims_consent_but_there_was_no_easy_refuse',
|
value: 'claims_consent_but_there_was_no_easy_refuse',
|
||||||
|
@ -115,21 +111,18 @@ function generateHostPage(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${domain}?`,
|
title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${host}?`,
|
||||||
name: f('legitimate_interest_description'),
|
name: f('legitimate_interest_description'),
|
||||||
visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`,
|
visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`,
|
||||||
placeholder: 'marketing',
|
placeholder: 'marketing',
|
||||||
defaultValueExpression:
|
defaultValueExpression:
|
||||||
index == 0
|
index == 0
|
||||||
? 'marketing'
|
? 'marketing'
|
||||||
: `{${f(
|
: `{${f('legitimate_interest_description', previous_host)}}`,
|
||||||
'legitimate_interest_description',
|
|
||||||
previous_cluster || undefined
|
|
||||||
)}}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'radiogroup',
|
type: 'radiogroup',
|
||||||
title: `Czy domena ${domain} należy do podmiotu spoza Europy (np. Google, Facebook)?`,
|
title: `Czy domena ${host} należy do podmiotu spoza Europy (np. Google, Facebook)?`,
|
||||||
name: f('outside_eu'),
|
name: f('outside_eu'),
|
||||||
...defaultValue('outside_eu'),
|
...defaultValue('outside_eu'),
|
||||||
visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f(
|
visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f(
|
||||||
|
@ -144,15 +137,13 @@ function generateHostPage(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'radiogroup',
|
type: 'radiogroup',
|
||||||
title: `Czy w {Twojej} ocenie ujawnienie {Twoich} danych (${danych}) właścicielowi domeny ${domain} było konieczne do świadczenia zażądanej przez {Ciebie} usługi drogą elektroniczną?`,
|
title: `Czy w {Twojej} ocenie wysłanie {Twoich} danych do właściciela domeny ${host} było konieczne do świadczenia zażądanej przez {Ciebie} usługi drogą elektroniczną?`,
|
||||||
name: f('was_processing_necessary'),
|
name: f('was_processing_necessary'),
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
...defaultValue('was_processing_necessary'),
|
...defaultValue('was_processing_necessary'),
|
||||||
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f(
|
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f(
|
||||||
'present'
|
'present'
|
||||||
)}} = "not_mentioned" or {${f(
|
)}} = "not_mentioned" or {popup_type} = "none"`,
|
||||||
'present'
|
|
||||||
)}} = "not_before_making_a_choice" or {popup_type} = "none"`,
|
|
||||||
choices: [
|
choices: [
|
||||||
{ value: 'yes', text: 'Tak, było konieczne' },
|
{ value: 'yes', text: 'Tak, było konieczne' },
|
||||||
{ value: 'no', text: 'Nie, nie było konieczne' },
|
{ value: 'no', text: 'Nie, nie było konieczne' },
|
||||||
|
@ -163,7 +154,7 @@ function generateHostPage(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function generateSurveyQuestions(clusters: RequestCluster[]) {
|
export default function generateSurveyQuestions(hosts: string[]) {
|
||||||
return {
|
return {
|
||||||
showQuestionNumbers: 'off',
|
showQuestionNumbers: 'off',
|
||||||
showProgressBar: 'top',
|
showProgressBar: 'top',
|
||||||
|
@ -174,60 +165,28 @@ export default function generateSurveyQuestions(clusters: RequestCluster[]) {
|
||||||
clearInvisibleValues: 'onHidden',
|
clearInvisibleValues: 'onHidden',
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
title: 'Dodatkowe pytania',
|
title: 'Tytuł - co to za ankieta?',
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
type: 'html',
|
type: 'html',
|
||||||
name: 'intro',
|
name: 'intro',
|
||||||
html: /* HTML */ `<p>
|
html: '<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ab, odit dicta at aut esse culpa eveniet iure odio voluptates veniam sit. Libero explicabo, perspiciatis ad expedita officiis inventore impedit ducimus!</p>',
|
||||||
Analiza ruchu sieciowego generowanego przez stronę została zakończona.
|
|
||||||
Teraz, aby lepiej oszacować, gdzie są potencjalne obszary robocze pod
|
|
||||||
względem zgodności z RODO, możesz udzielić odpowiedzi na pytania
|
|
||||||
dotyczące funkcjonowania strony. Wtyczka wtedy wygeneruje raport lub
|
|
||||||
treść maila, którą możesz wysłać do administratora strony.
|
|
||||||
</p>`,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Kontekst analizy',
|
title: 'Zaimki',
|
||||||
elements: [
|
elements: [
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'user_role',
|
|
||||||
title: 'Jestem:',
|
|
||||||
isRequired: true,
|
|
||||||
choices: [
|
|
||||||
{ value: 'user', text: 'użytkownikiem strony' },
|
|
||||||
{ value: 'admin', text: 'administratorem strony' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'email_type',
|
|
||||||
title: 'Chcę:',
|
|
||||||
visibleIf: "{user_role} = 'user'",
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'polite_information',
|
|
||||||
text: 'uprzejmie poinformować administratora strony o potencjalnych problemach ze zgodnością z RODO na jego stronie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'official_request',
|
|
||||||
text: 'wysłać formalne zapytanie do administratora strony, na które ma obowiązek odpowiedzieć. Jeżeli administrator nie odpowie na takie zapytanie, może to być podstawą złożenia skargi do UODO',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'radiogroup',
|
type: 'radiogroup',
|
||||||
name: 'zaimek',
|
name: 'zaimek',
|
||||||
title: 'Forma czasownika, jaka będzie użyta w raporcie:',
|
title: 'Forma czasownika:',
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
choices: [
|
choices: [
|
||||||
{ value: 0, text: 'wysłałem' },
|
{ value: 0, text: 'Wysłałem' },
|
||||||
{ value: 1, text: 'wysłałam' },
|
{ value: 1, text: 'Wysłałam' },
|
||||||
{ value: 2, text: 'wysłałom' },
|
{ value: 2, text: 'Wysłałom' },
|
||||||
{ value: 3, text: 'wysłaliśmy' },
|
{ value: 3, text: 'Wysłaliśmy' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -343,7 +302,7 @@ export default function generateSurveyQuestions(clusters: RequestCluster[]) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'yes',
|
value: 'yes',
|
||||||
text: 'Nie. {Muszę} wykonać więcej czynności (np. kliknięć) aby odmówić wszystkich zgód, albo opcja niewyrażenia zgody jest mało widoczna.',
|
text: 'Nie. {Muszę} wykonać więcej czynności aby odmówić wszystkich zgód, albo opcja niewyrażenia zgody jest mało widoczna.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -434,7 +393,7 @@ export default function generateSurveyQuestions(clusters: RequestCluster[]) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...clusters.map(generateHostPage),
|
...hosts.map(generateHostPage),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,7 @@ function handleNewFile(
|
||||||
);
|
);
|
||||||
setFiltered(new Blob([JSON.stringify(content)], { type: 'application/json' }));
|
setFiltered(new Blob([JSON.stringify(content)], { type: 'application/json' }));
|
||||||
});
|
});
|
||||||
const file = element?.files?.[0];
|
reader.readAsText(element.files[0]);
|
||||||
if (!file) throw new Error('file empty?');
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateFakeHAR(entries: StolenDataEntry[]) {
|
function generateFakeHAR(entries: StolenDataEntry[]) {
|
||||||
|
@ -82,11 +80,8 @@ export default function HARConverter({ entries }: { entries: StolenDataEntry[] }
|
||||||
type="file"
|
type="file"
|
||||||
accept=".har"
|
accept=".har"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const file = e.target?.files?.[0];
|
setFilename(e.target.files[0].name);
|
||||||
if (file) {
|
|
||||||
setFilename(file.name);
|
|
||||||
handleNewFile(e.target, entries, setFiltered);
|
handleNewFile(e.target, entries, setFiltered);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{(filtered && (
|
{(filtered && (
|
||||||
|
@ -105,7 +100,7 @@ export default function HARConverter({ entries }: { entries: StolenDataEntry[] }
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
download={`${getshorthost(
|
download={`${getshorthost(
|
||||||
entries[0].request.origin
|
entries[0].request.originalURL
|
||||||
)}-${new Date().toJSON()}-trimmed.har`}
|
)}-${new Date().toJSON()}-trimmed.har`}
|
||||||
>
|
>
|
||||||
Pobierz "zredukowany" HAR
|
Pobierz "zredukowany" HAR
|
||||||
|
|
|
@ -42,8 +42,6 @@ function parseHostAnswers(
|
||||||
|
|
||||||
export function parseAnswers({
|
export function parseAnswers({
|
||||||
zaimek,
|
zaimek,
|
||||||
user_role,
|
|
||||||
email_type,
|
|
||||||
is_incognito_different,
|
is_incognito_different,
|
||||||
policy_readable,
|
policy_readable,
|
||||||
popup_type,
|
popup_type,
|
||||||
|
@ -52,13 +50,10 @@ export function parseAnswers({
|
||||||
mentions_passive_consent,
|
mentions_passive_consent,
|
||||||
rejection_is_hard,
|
rejection_is_hard,
|
||||||
administrator_identity_available_before_choice,
|
administrator_identity_available_before_choice,
|
||||||
popup_action,
|
|
||||||
...rest
|
...rest
|
||||||
}: RawAnswers): ParsedAnswers {
|
}: RawAnswers): ParsedAnswers {
|
||||||
return {
|
return {
|
||||||
zaimek,
|
zaimek,
|
||||||
user_role,
|
|
||||||
email_type,
|
|
||||||
is_incognito_different,
|
is_incognito_different,
|
||||||
policy_readable,
|
policy_readable,
|
||||||
popup_type,
|
popup_type,
|
||||||
|
@ -67,7 +62,6 @@ export function parseAnswers({
|
||||||
mentions_passive_consent,
|
mentions_passive_consent,
|
||||||
rejection_is_hard,
|
rejection_is_hard,
|
||||||
administrator_identity_available_before_choice,
|
administrator_identity_available_before_choice,
|
||||||
popup_action,
|
|
||||||
hosts: parseHostAnswers(rest),
|
hosts: parseHostAnswers(rest),
|
||||||
} as ParsedAnswers;
|
} as ParsedAnswers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,49 +3,27 @@ import { v } from '../verbs';
|
||||||
import { Problem } from './problem';
|
import { Problem } from './problem';
|
||||||
|
|
||||||
export default class NoInformationAtAllProblem extends Problem {
|
export default class NoInformationAtAllProblem extends Problem {
|
||||||
qualifies() {
|
getEmailContent() {
|
||||||
return this.answers.popup_type === 'none';
|
|
||||||
}
|
|
||||||
getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
|
|
||||||
const _ = (word: string) => v(word, this.answers.zaimek);
|
const _ = (word: string) => v(word, this.answers.zaimek);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>Brak informacji na temat przetwarzania danych osobowych</h2>
|
<h2>Brak informacji na temat przetwarzania danych osobowych</h2>
|
||||||
{mode == 'email' ? (
|
|
||||||
tone == 'official' ? (
|
|
||||||
<p>
|
<p>
|
||||||
{_('Moje')} dane osobowe zostały ujawnione podmiotom, które są
|
{_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami
|
||||||
właścicielami domen:
|
domen:
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Państwa strona ujawnia dane użytkowników podmiotom, które są
|
|
||||||
właścicielami następujących domen:
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Poprzez skrypty osadzone na stronie dane osobowe użytkownika końcowego są
|
|
||||||
przekazywane podmiotom, którzy są właścicielami następujacych domen:
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{this.getRangeDescription()}
|
{this.getRangeDescription()}
|
||||||
<p>
|
<p>
|
||||||
Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie są cele
|
Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie są cele
|
||||||
przetwarzania takich danych oraz jakie są podstawy prawne takiego przetwarzania.
|
przetwarzania takich danych oraz jakie są podstawy prawne takiego przetwarzania.
|
||||||
</p>
|
</p>
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>Zwracam się zatem do Państwa z następującymi pytaniami:</p>
|
<p>Zwracam się zatem do Państwa z następującymi pytaniami:</p>
|
||||||
) : (
|
|
||||||
<p>Na stronie należy zawrzeć odpowiedzi na następujące pytania:</p>
|
|
||||||
)}
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Jaka jest tożsamość właścicieli tych domen?</li>
|
<li>Jaka jest tożsamość właścicieli tych domen?</li>
|
||||||
<li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li>
|
<li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li>
|
||||||
<li>
|
<li>
|
||||||
Jaka jest podstawa prawna takiego przetwarzania{' '}
|
Jaka jest podstawa prawna takiego przetwarzania moich danych osobowych przez
|
||||||
{mode == 'email' ? _('moich') : ''} danych osobowych
|
Państwa stronę?
|
||||||
{mode == 'report' ? 'użytkowników końcowych' : ''} przez Państwa stronę?
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
|
@ -56,11 +34,11 @@ export default class NoInformationAtAllProblem extends Problem {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.getMarkedClusters().some((cluster) => {
|
this.getMarkedClusters().some((cluster) => {
|
||||||
|
console.log(cluster);
|
||||||
return cluster.hasMarkedCookies();
|
return cluster.hasMarkedCookies();
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
explainers.push('cookies_are_pii');
|
explainers.push('cookies_are_pii');
|
||||||
explainers.push('responsibility_for_third_parties');
|
|
||||||
}
|
}
|
||||||
return explainers;
|
return explainers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ParsedAnswers } from '../parse-answers';
|
||||||
|
|
||||||
function formatRange(cluster: RequestCluster) {
|
function formatRange(cluster: RequestCluster) {
|
||||||
const parts = [] as string[];
|
const parts = [] as string[];
|
||||||
|
console.log(cluster);
|
||||||
if (cluster.hasMarkedCookies()) {
|
if (cluster.hasMarkedCookies()) {
|
||||||
parts.push('mojego identyfikatora internetowego pozyskanego z Cookie');
|
parts.push('mojego identyfikatora internetowego pozyskanego z Cookie');
|
||||||
}
|
}
|
||||||
|
@ -16,12 +17,8 @@ function formatRange(cluster: RequestCluster) {
|
||||||
export abstract class Problem {
|
export abstract class Problem {
|
||||||
constructor(public answers: ParsedAnswers, public clusters: Record<string, RequestCluster>) {}
|
constructor(public answers: ParsedAnswers, public clusters: Record<string, RequestCluster>) {}
|
||||||
|
|
||||||
abstract getEmailContent(props: {
|
abstract getEmailContent(): JSX.Element;
|
||||||
mode: 'email' | 'report';
|
|
||||||
tone: 'polite' | 'official';
|
|
||||||
}): JSX.Element;
|
|
||||||
abstract getNecessaryExplainers(): ExplainerKey[];
|
abstract getNecessaryExplainers(): ExplainerKey[];
|
||||||
abstract qualifies(): boolean;
|
|
||||||
|
|
||||||
getMarkedClusters() {
|
getMarkedClusters() {
|
||||||
return Object.values(this.clusters).filter((c) => c.hasMarks());
|
return Object.values(this.clusters).filter((c) => c.hasMarks());
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
import { RequestCluster } from '../../../request-cluster';
|
|
||||||
import { ExplainerKey } from '../explainers';
|
|
||||||
import { v } from '../verbs';
|
|
||||||
import { Problem } from './problem';
|
|
||||||
|
|
||||||
export class TransferOutsideEU extends Problem {
|
|
||||||
getNecessaryExplainers(): ExplainerKey[] {
|
|
||||||
const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
|
|
||||||
return has_cookies ? ['cookies_are_pii'] : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
qualifies(): boolean {
|
|
||||||
return Object.values(this.answers.hosts).some(
|
|
||||||
(hostAnswers) => hostAnswers.outside_eu == 'yes'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRelatedClusters(): RequestCluster[] {
|
|
||||||
return Object.entries(this.answers.hosts)
|
|
||||||
.filter(([_, hostAnswers]) => hostAnswers.outside_eu == 'yes')
|
|
||||||
.map(([id]) => this.clusters[id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
getEmailContent({ mode }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
|
|
||||||
const clusters = this.getRelatedClusters();
|
|
||||||
const _ = (key: string) => v(key, this.answers.zaimek);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Transfer danych osobowych poza Europejski Obszar Gospodarczy</h2>
|
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>
|
|
||||||
Państwa strona przetworzyła {_('moje')} dane osobowe poprzez przesłanie
|
|
||||||
danych do:
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Strona przetwarza dane osobowe użytkowników końcowych poprzez przesłanie
|
|
||||||
przekazanie ich do:
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<ul>
|
|
||||||
{clusters.map((cluster) => (
|
|
||||||
<li key={cluster.id}>
|
|
||||||
właściciela domeny <strong>{cluster.id}</strong>: (w zakresie:{' '}
|
|
||||||
{cluster.getDataTypeDescription(mode == 'email' ? 'mojej' : '')});
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>
|
|
||||||
Według {_('mojej')} najlepszej wiedzy, każdy z tych podmiotów utrzymuje
|
|
||||||
swoje serwery poza Europejskim Obszarem Gospodarczym. Zatem Państwa strona
|
|
||||||
przesłała
|
|
||||||
{_('moje')} dane osobowe poza EOG. Jeżeli tak jest, to takie przetwarzanie
|
|
||||||
danych jest niezgodne z prawem, gdyż dane trafiają do krajów, które nie
|
|
||||||
gwarantują ochrony danych w stopniu, jakiego wymaga RODO, a tzw. „Tarcza
|
|
||||||
Prywatności” została unieważniona w 2020r. Zob.{' '}
|
|
||||||
<a href="https://panoptykon.org/noyb-skargi-schrems-ii">
|
|
||||||
artykuł Fundacji Panoptykon w tej sprawie
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Te podmioty utrzymują swoje centra danych poza Europejskim Obszarem
|
|
||||||
Gospodarczym. Jako, że tzw. „Tarcza Prywatności” zostałą unieważniona w
|
|
||||||
2020r., nie można przesyłać danych osobowych obywateli Unii Europejskiej do
|
|
||||||
krajów, które nie zapewniają ochrony danych o sile odpowiadającej RODO.
|
|
||||||
Przykłądem kraju, do którego nie można przekazywać danych osobowych
|
|
||||||
obywateli UE są Stany Zjednoczone.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>
|
|
||||||
{_('Zwracam')} się zatem do Państwa z pytaniem:{' '}
|
|
||||||
<strong>
|
|
||||||
czy wyżej wymienione podmioty, którym Państwa strona ujawniła moje dane
|
|
||||||
osobowe, przechowują moje dane poza EOG?
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Zaleca się rezygnację z korzystania z usług firm, które przetwarzają dane
|
|
||||||
osobowe użytkowników, a których centra danych znajdują się poza EOG.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { ExplainerKey } from '../explainers';
|
|
||||||
import { v } from '../verbs';
|
|
||||||
import { Problem } from './problem';
|
|
||||||
|
|
||||||
export class UnknownIdentity extends Problem {
|
|
||||||
getNecessaryExplainers(): ExplainerKey[] {
|
|
||||||
return ['responsibility_for_third_parties'];
|
|
||||||
}
|
|
||||||
|
|
||||||
qualifies(): boolean {
|
|
||||||
return this.answers.administrator_identity_available_before_choice == 'no';
|
|
||||||
}
|
|
||||||
|
|
||||||
getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
|
|
||||||
const _ = (key: string) => v(key, this.answers.zaimek);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Tożsamość administratora</h2>
|
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>
|
|
||||||
Na Państwa stronie nie {_('znalazłem')} sposobu na poznanie tożsamości
|
|
||||||
administratora strony <strong>przed</strong> podjęciem wyboru dotyczącego
|
|
||||||
przetwarzania danych mnie dotyczących.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>Na stronie brakuje sposobu na poznanie tożsamości administratora strony.</p>
|
|
||||||
)}
|
|
||||||
<p>
|
|
||||||
Zgodnie z treścią Art. 13 RODO, jeżeli dane osobowe osoby, której dane dotyczą,
|
|
||||||
zbierane są od tej osoby, administrator podczas pozyskiwania danych osobowych
|
|
||||||
musi podać jej swoją tożsamość i dane kontaktowe.
|
|
||||||
</p>
|
|
||||||
{mode == 'email' ? (
|
|
||||||
tone == 'official' ? (
|
|
||||||
<p>
|
|
||||||
Zwracam się zatem z pytaniem:{' '}
|
|
||||||
<strong>jaka jest tożsamość administratora tej strony?</strong>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Apeluję o dodanie do Państwa strony informacji o tym, kto (np. pełna
|
|
||||||
nazwa firmy + NIP oraz dane kontaktowe) jest administratorem danych
|
|
||||||
osobowych przetwarzanych przez tę stronę.
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Zalecane jest dodanie informacji o administratorze strony (pełna nazwa firmy
|
|
||||||
+ NIP i dane kontaktowe) w łatwo dostępnym miejscu na stronie.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
import { RequestCluster } from '../../../request-cluster';
|
|
||||||
import { ExplainerKey } from '../explainers';
|
|
||||||
import { ParsedHostAnswers } from '../parse-answers';
|
|
||||||
import { v } from '../verbs';
|
|
||||||
import { Problem } from './problem';
|
|
||||||
|
|
||||||
const testCluster: (cluster: RequestCluster, answers: ParsedHostAnswers | undefined) => boolean = (
|
|
||||||
cluster,
|
|
||||||
hostAnswers
|
|
||||||
) => {
|
|
||||||
if (!hostAnswers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (cluster.hasMarkedCookies()) {
|
|
||||||
/* if it has cookies, it will be picked up by the UnlawfulCookieAccess problem, and that one
|
|
||||||
is pretty detailed, so no need to mention it here. */
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hostAnswers.legal_basis_type == 'not_mentioned';
|
|
||||||
};
|
|
||||||
|
|
||||||
export class UnknownLegalBasis extends Problem {
|
|
||||||
getNecessaryExplainers(): ExplainerKey[] {
|
|
||||||
const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
|
|
||||||
return [
|
|
||||||
'responsibility_for_third_parties',
|
|
||||||
...(has_cookies ? ['cookies_are_pii' as ExplainerKey] : []),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
qualifies(): boolean {
|
|
||||||
return Object.values(this.clusters).some((cluster) =>
|
|
||||||
testCluster(cluster, this.answers.hosts[cluster.id])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRelatedClusters() {
|
|
||||||
return Object.values(this.clusters).filter((cluster) =>
|
|
||||||
testCluster(cluster, this.answers.hosts[cluster.id])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
|
|
||||||
const clusters = this.getRelatedClusters();
|
|
||||||
const _ = (key: string) => v(key, this.answers.zaimek);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Przetwarzanie danych osobowych bez podania podstawy prawnej</h2>
|
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>Państwa strona przetworzyła {_('moje')} dane osobowe poprzez ujawnienie:</p>
|
|
||||||
) : (
|
|
||||||
<p>Państwa strona przetwarza dane osobowe użytkowników poprzez ujawnienie</p>
|
|
||||||
)}
|
|
||||||
<ul>
|
|
||||||
{clusters.map((cluster) => (
|
|
||||||
<li key={cluster.id}>
|
|
||||||
właścicielowi domeny <strong>{cluster.id}</strong>:{' '}
|
|
||||||
{cluster.getDataTypeDescription(mode == 'email' ? 'mojej' : '')}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{mode == 'email' ? (
|
|
||||||
<p>
|
|
||||||
{_('Moja')} historia przeglądania stanowi {_('moje')} dane osobowe. Zgodnie
|
|
||||||
z treścią Artykułu 13 p. 1 lit. c){' '}
|
|
||||||
<a href="https://eur-lex.europa.eu/legal-content/PL/TXT/HTML/?uri=CELEX:32016R0679&qid=1632163985520&from=PL#d1e1822-1-1">
|
|
||||||
RODO
|
|
||||||
</a>
|
|
||||||
, aby przetwarzać dane osobowe, trzeba poinformować osobę, której dane
|
|
||||||
dotyczą, o tym, jaka jest podstawa prawna takiego przetwarzania danych.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Na stronie nie znajdują się informacje o tym, jaka jest podstawa prawna
|
|
||||||
takiego przetwarzania danych osobowych, jakimi jest część historii
|
|
||||||
przeglądania. Zgodnie z treścią Artykułu 13. p. 1 lit. c) RODO, aby
|
|
||||||
przetwarzać dane osobowe, trzeba poinformować osobę, której dane dotyczą, o
|
|
||||||
tym, jaka jest podstawa prawna takiego przetwarzania danych.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{mode == 'email' ? (
|
|
||||||
tone == 'official' ? (
|
|
||||||
<p>
|
|
||||||
Zwracam się zatem z pytaniem:{' '}
|
|
||||||
<strong>
|
|
||||||
jakie były podstawy prawne ujawnienia moich danych każdemu z wyżej
|
|
||||||
wymienionych podmiotów przez Państwa stronę?
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Dodanie do Państwa strony informacji o tym, jakie są podstawy prawne (w
|
|
||||||
znaczeniu Art. 6 pkt. 1 RODO) dla każdego z tych procesów przetwarzania
|
|
||||||
miałoby pozytywny wpływ na przejrzystość informacji dla użytkowników
|
|
||||||
końcowych, jak i na zgodność strony z obowiązującymi przepisami.
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p>Możliwe działania:</p>
|
|
||||||
<ul>
|
|
||||||
<li>rezygnacja z niektórych skryptów śledzących;</li>
|
|
||||||
<li>
|
|
||||||
przeniesienie assetów z CDN-a na samohostowanie (przy korzystaniu z
|
|
||||||
HTTP2 to może dać zwiększoną wydajność wzgłędem CDN);
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
konfiguracja nagłówka{' '}
|
|
||||||
<a href="https://developer.mozilla.org/pl/docs/Web/HTTP/Headers/Referrer-Policy">
|
|
||||||
Referrer-Policy
|
|
||||||
</a>{' '}
|
|
||||||
tak, aby nie ujawniać historii przeglądania właścicielom zasobów z
|
|
||||||
domen podmiotów trzecich.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
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[] {
|
|
||||||
const has_cookies = this.getAffectedClusters().some((cluster) => cluster.hasCookies());
|
|
||||||
if (has_cookies) {
|
|
||||||
return ['cookies_are_pii'];
|
|
||||||
} else {
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +1,31 @@
|
||||||
|
import { RequestCluster } from '../../../request-cluster';
|
||||||
import { wordlist } from '../../../util';
|
import { wordlist } from '../../../util';
|
||||||
import { ExplainerKey } from '../explainers';
|
import { ExplainerKey } from '../explainers';
|
||||||
|
import { ParsedAnswers } from '../parse-answers';
|
||||||
import { v } from '../verbs';
|
import { v } from '../verbs';
|
||||||
import { Problem } from './problem';
|
import { Problem } from './problem';
|
||||||
|
|
||||||
export class UnlawfulCookieAccess extends Problem {
|
export class UnlawfulCookieAccess extends Problem {
|
||||||
getNecessaryExplainers(): ExplainerKey[] {
|
getNecessaryExplainers(): ExplainerKey[] {
|
||||||
return ['cookies_are_pii', 'responsibility_for_third_parties'];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
qualifies(): boolean {
|
static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {
|
||||||
// są cookiesy, nie było zgody, nie są konieczne do działania strony
|
// są cookiesy, nie było zgody, nie są konieczne do działania strony
|
||||||
const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
|
const cookie_clusters = Object.values(clusters).filter((c) => c.hasMarkedCookies());
|
||||||
return cookie_clusters.some((cluster) => {
|
return cookie_clusters.some((cluster) => {
|
||||||
const hostAnswers = this.answers.hosts[cluster.id];
|
const hostAnswers = answers.hosts[cluster.id];
|
||||||
return (
|
return (
|
||||||
(hostAnswers.present == 'not_mentioned' ||
|
(hostAnswers.present == 'not_mentioned' ||
|
||||||
hostAnswers.present == 'not_before_making_a_choice' ||
|
hostAnswers.present == 'not_before_making_a_choice' ||
|
||||||
['none', 'closed_popup', 'deny_all'].includes(this.answers.popup_action) ||
|
['none', 'closed_popup', 'deny_all'].includes(answers.popup_action) ||
|
||||||
this.answers.popup_type === 'none') &&
|
answers.popup_type === 'none') &&
|
||||||
hostAnswers.was_processing_necessary != 'yes'
|
hostAnswers.was_processing_necessary != 'yes'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
|
getEmailContent() {
|
||||||
const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
|
const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
|
||||||
const unnecessary_hosts = Object.entries(this.answers.hosts)
|
const unnecessary_hosts = Object.entries(this.answers.hosts)
|
||||||
.filter(([, answers]) => answers.was_processing_necessary === 'no')
|
.filter(([, answers]) => answers.was_processing_necessary === 'no')
|
||||||
|
@ -36,12 +38,8 @@ export class UnlawfulCookieAccess extends Problem {
|
||||||
<>
|
<>
|
||||||
<h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2>
|
<h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2>
|
||||||
<p>
|
<p>
|
||||||
Państwa strona {mode == 'email' ? 'dokonała' : 'dokonuje'} odczytu plików Cookie
|
Państwa strona dokonała odczytu plików Cookie zapisanych na dysku twardym mojego
|
||||||
zapisanych na dysku twardym{' '}
|
komputera. Dotyczy to plików cookie przypisanych do domen:
|
||||||
{mode === 'email'
|
|
||||||
? _('mojego') + ' komputera.'
|
|
||||||
: 'komputerach użytkowników końcowych.'}
|
|
||||||
. Dotyczy to plików cookie przypisanych do domen:
|
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
{cookie_clusters.map((cluster, index) => {
|
{cookie_clusters.map((cluster, index) => {
|
||||||
|
@ -78,9 +76,7 @@ export class UnlawfulCookieAccess extends Problem {
|
||||||
<li>
|
<li>
|
||||||
Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak
|
Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak
|
||||||
został poinformowany bezpośrednio o celu uzyskania dostępu do tej
|
został poinformowany bezpośrednio o celu uzyskania dostępu do tej
|
||||||
informacji. Zgodnie z Art. 174 ustawy Prawo Telekomunikacyjne, taka zgoda
|
informacji;
|
||||||
musi spełniać warunki zgody ustalone przez RODO, aby mogła być jako podstawa
|
|
||||||
prawna uzyskania dostępu do cookies i podobnych technologii w przeglądarce;
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Dostęp do treści plików cookies jest konieczny do dostarczania usługi
|
Dostęp do treści plików cookies jest konieczny do dostarczania usługi
|
||||||
|
@ -89,47 +85,25 @@ export class UnlawfulCookieAccess extends Problem {
|
||||||
</ol>
|
</ol>
|
||||||
{(() => {
|
{(() => {
|
||||||
if (this.answers.popup_type == 'none' || this.answers.popup_type == 'page') {
|
if (this.answers.popup_type == 'none' || this.answers.popup_type == 'page') {
|
||||||
return mode === 'email' ? (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Jako, że strona nie pytała {_('mnie')} nigdy o zgodę, nie jest
|
Jako, że strona nie pytała {_('mnie')} nigdy o zgodę, nie jest
|
||||||
spełniony warunek 1.
|
spełniony warunek 1.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Strona nie ma zaimplementowanego mechanizmu pozyskiwania zgód, zatem
|
|
||||||
nie spełnia warunku opisanego w punkcie 1.
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
} else if (this.answers.popup_type === 'passive_popup') {
|
} else if (this.answers.popup_type === 'passive_popup') {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{mode === 'email' ? (
|
Państwa strona nie dała mi nigdy faktycznego wyboru dotyczącego
|
||||||
<>
|
wyrażenia lub odmówienia zgody na takie przetwarzanie danych
|
||||||
Państwa strona nie dała mi nigdy faktycznego wyboru
|
osobowych, dlatego nie jest spełniony warunek 1.{' '}
|
||||||
dotyczącego wyrażenia lub odmówienia zgody na takie
|
|
||||||
przetwarzanie danych osobowych. Aby zgoda była ważna w
|
|
||||||
świetle RODO, musi być dobrowolna. Brak możliwości
|
|
||||||
odmówienia zgody sprawia, że tak wyrażona „zgoda” nie jest
|
|
||||||
ważna w świetle RODO. Dlatego nie jest spełniony warunek 1.{' '}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
Aktualnie zaimplementowane okienko o przetwarzaniu danych
|
|
||||||
osobowych nie daje użytkownikom końcowym możliwości odmowy
|
|
||||||
wyrażenia zgody, przez co tak wyrażona „zgoda” nie spełnia
|
|
||||||
warunku dobrowolności opisanego w motywie (32) RODO. Z tego
|
|
||||||
powodu nie jest spełniony warunek opisany w punkcie 1.
|
|
||||||
powyżej, zatem tak pozyskana "zgoda" nie może stanowić
|
|
||||||
podstawy prawnej dostępu do cookiesów użytkownika końcowego.
|
|
||||||
</>
|
|
||||||
)}{' '}
|
|
||||||
{this.answers.mentions_passive_consent ? (
|
{this.answers.mentions_passive_consent ? (
|
||||||
<>
|
<>
|
||||||
Należy nadmienić także, że zgody wyrażonej w sposób bierny
|
Zgody wyrażonej w sposób bierny lub milczący nie można uznać
|
||||||
lub milczący nie można uznać za ważną w świetle
|
za ważną w świetle obowiązujących przepisów rozporządzenia
|
||||||
obowiązujących przepisów rozporządzenia 2016/679. Dlatego
|
2016/679. Dlatego zaniechanie zmiany ustawień przeglądarki
|
||||||
zaniechanie zmiany ustawień przeglądarki lub po prostu
|
lub po prostu korzystanie ze strony nie stanowi ważnej
|
||||||
korzystanie ze strony nie stanowi ważnej zgody. Takie jest{' '}
|
zgody. Takie jest{' '}
|
||||||
<a href="https://assets.midline.pl/pisma/2021-12-16%20odpowiedz%20UODO%20na%20skarg%C4%99%20i(n)Secure.pdf">
|
<a href="https://assets.midline.pl/pisma/2021-12-16%20odpowiedz%20UODO%20na%20skarg%C4%99%20i(n)Secure.pdf">
|
||||||
stanowisko polskiego UODO
|
stanowisko polskiego UODO
|
||||||
</a>
|
</a>
|
||||||
|
@ -142,26 +116,15 @@ export class UnlawfulCookieAccess extends Problem {
|
||||||
);
|
);
|
||||||
} else if (this.answers.popup_type === 'some_choice') {
|
} else if (this.answers.popup_type === 'some_choice') {
|
||||||
if (this.answers.popup_action === 'none') {
|
if (this.answers.popup_action === 'none') {
|
||||||
return mode == 'email' ? (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
|
Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
|
||||||
danych osobowych. W okienku pytającym o zgodę nic nie{' '}
|
danych osobowych. W okienku pytającym o zgodję nic nie{' '}
|
||||||
{_('kliknąłem')}. Nie jest zatem spełniony warunek 1.
|
{_('kliknąłem')}. Nie jest zatem spełniony warunek 1.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Skrypty pozyskujące dostęp do plików cookie uruchamiają się
|
|
||||||
zanim użytkownik końcowy zdąży wybrać jakąkolwiek opcję w
|
|
||||||
okienku pytającym o zgodę. Aby zgoda była ważna, musi być
|
|
||||||
pozyskana <strong>zanim</strong> nastąpi proces przetwarzania
|
|
||||||
danych, którego ta zgoda dotyczy. Z tego powodu nie jest
|
|
||||||
spełniony warunek 1. Nie można używać tak pozyskanej „zgody”
|
|
||||||
jako podstawy prawnej dostępu do plików cookies na urządzeniu
|
|
||||||
użytkownika końcowego.
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
} else if (this.answers.popup_action === 'closed_popup') {
|
} else if (this.answers.popup_action === 'closed_popup') {
|
||||||
return mode == 'email' ? (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
|
Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
|
||||||
danych osobowych. {this.answers.popup_closed_how.trim()}
|
danych osobowych. {this.answers.popup_closed_how.trim()}
|
||||||
|
@ -173,19 +136,9 @@ export class UnlawfulCookieAccess extends Problem {
|
||||||
jednoznaczności opisanego w Art. 4, pkt 11 RODO. Nie jest zatem
|
jednoznaczności opisanego w Art. 4, pkt 11 RODO. Nie jest zatem
|
||||||
spełniony warunek 1.
|
spełniony warunek 1.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Gdy użytkownik końcowy strony nie wyrazi jednoznacznej zgody w
|
|
||||||
wyskakującym okienku, a zamiast tego po prostu zamknie to
|
|
||||||
okienko, strona nadal pozyskuje dostęp do plików cookies na
|
|
||||||
urządzeniu użytkownika. Zamknięcia okienka (np. przyciskiem „x”)
|
|
||||||
nie można uznać za ważną zgodę, gdyż taka czyność nie spełnia
|
|
||||||
warunku jednoznaczności opisanego w Art. 4. pkt 11. RODO. Nie
|
|
||||||
jest zatem spełniony warunek 1.
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
} else if (this.answers.popup_action == 'deny_all') {
|
} else if (this.answers.popup_action == 'deny_all') {
|
||||||
return mode == 'email' ? (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{this.answers.popup_deny_all_how.trim()}
|
{this.answers.popup_deny_all_how.trim()}
|
||||||
{this.answers.popup_closed_how.trim().at(-1) != '.'
|
{this.answers.popup_closed_how.trim().at(-1) != '.'
|
||||||
|
@ -193,80 +146,28 @@ export class UnlawfulCookieAccess extends Problem {
|
||||||
: ''}{' '}
|
: ''}{' '}
|
||||||
Zatem nie jest spełniony warunek 1.
|
Zatem nie jest spełniony warunek 1.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Gdy użytkownik jednoznacznie odmówi zgód na wszystkie cele
|
|
||||||
przetwarzania, strona nadal pozyskuje dostęp do plików cookies
|
|
||||||
na urządzeniu użytkownika. Jeżeli uzytkownik nie odmówił zgody,
|
|
||||||
to nie powinny załączać się procesy przetwarzania powołujące się
|
|
||||||
na zgodę jako podstawę prawną.
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
{unnecessary_hosts.length > 0 ? (
|
{unnecessary_hosts.length > 0 ? (
|
||||||
mode == 'email' ? (
|
|
||||||
<p>
|
<p>
|
||||||
W {_('mojej')} ocenie odczytywanie przez Państwa stronę treści plików
|
W {_('mojej')} ocenie odczytywanie przez Państwa stronę treści plików
|
||||||
cookies z {wordlist(unnecessary_hosts)} nie jest konieczne do
|
cookies z {wordlist(unnecessary_hosts)} nie jest konieczne do wyświetlenia
|
||||||
wyświetlenia treści Państwa strony, dlatego nie jest dla nich spełniony
|
treści Państwa strony, dlatego nie jest dla nich spełniony warunek 2. Jeżeli
|
||||||
warunek 2. Jeżeli według Państwa oceny jest inaczej, {_('proszę')} o
|
Państwa zdaniem jest inaczej, {_('proszę')} o wskazanie, co jest źródłem tej
|
||||||
wskazanie, co jest źródłem tej konieczności i co odróżnia Państwa stronę
|
konieczności i co odróżnia Państwa stronę od wielu innych stron, które
|
||||||
od wielu innych stron, które realizują te same funkcjonalności{' '}
|
realizują te same funkcjonalności <em>bez</em> korzystania z plików Cookie.
|
||||||
<em>bez</em> korzystania z plików Cookie.
|
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Warto, aby informacje na stronie opisywały w zrozumiały sposob, które z
|
|
||||||
podmiotów, których skrypty uruchamiają się na stronie (
|
|
||||||
{wordlist(unnecessary_hosts)}) są konieczne do działania strony, jaki
|
|
||||||
zakres danych przetwarzają i w jakim celu.
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
{mode == 'email' ? (
|
|
||||||
tone === 'official' ? (
|
|
||||||
<p>
|
<p>
|
||||||
{_('Proszę')} o wskazanie,{' '}
|
{_('Proszę')} o wskazanie, czy być może stosowali Państwo inną podstawę prawną
|
||||||
<strong>
|
do takiego przetwarzania {_('moich')} danych osobowych, czy przetwarzali je
|
||||||
czy być może stosowali Państwo inną podstawę prawną do takiego
|
państwo bez ważnej podstawy prawnej?
|
||||||
przetwarzania {_('moich')} danych osobowych, czy być może
|
|
||||||
przetwarzali je Państwo bez ważnej podstawy prawnej?
|
|
||||||
</strong>
|
|
||||||
</p>
|
</p>
|
||||||
) : (
|
{maybe_unnecessary_hosts.length > 1 ? (
|
||||||
<p>
|
|
||||||
Apeluję o wdrożenie zmian na Państwa stronie tak, aby użytkownik miał
|
|
||||||
faktyczny wybór dotyczący procesów przetwarzania jego danych osobowych,
|
|
||||||
jakie zachodzą w trakcie odwiedzin tej strony.
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p>
|
|
||||||
Jeżeli zgoda nadal ma być używana jako podstawa prawna do odczytu plików
|
|
||||||
cookies przez skrypty wyżej wymienionych podmiotów, to należy zmienić
|
|
||||||
mechanizm zgody tak, aby:{' '}
|
|
||||||
</p>{' '}
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
dawał użytkownikowi końcowemu możliwość odmowy zgody w sposób równie
|
|
||||||
łatwy i dostępny, jak na wyrażenie zgody;
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
skrypty śledzące uruchamiały się dopiero po uzyskaniu ważnej zgody;
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
skrypty śledzące nie uruchamiały się, jeżeli użytkownik nie wyraził
|
|
||||||
na nie zgody.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{maybe_unnecessary_hosts.length > 1 && mode == 'email' && tone == 'official' ? (
|
|
||||||
<p>
|
<p>
|
||||||
{_('Proszę')} też o wskazanie, czy dostęp do treści plików cookie z
|
{_('Proszę')} też o wskazanie, czy dostęp do treści plików cookie z
|
||||||
{wordlist(maybe_unnecessary_hosts)} jest konieczny do poprawnego działania
|
{wordlist(maybe_unnecessary_hosts)} jest konieczny do poprawnego działania
|
||||||
|
|
36
components/report-window/problems/unlawful-data.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { RequestCluster } from '../../../request-cluster';
|
||||||
|
import { ExplainerKey } from '../explainers';
|
||||||
|
import { ParsedAnswers, ParsedHostAnswers } from '../parse-answers';
|
||||||
|
import { v } from '../verbs';
|
||||||
|
import { Problem } from './problem';
|
||||||
|
|
||||||
|
type UnlawfulDataClassification = 'no_purpose';
|
||||||
|
|
||||||
|
export function classifyUnlawfulData(
|
||||||
|
hostAnswers: ParsedHostAnswers,
|
||||||
|
cluster: RequestCluster
|
||||||
|
): UnlawfulDataClassification {
|
||||||
|
if (hostAnswers.present == 'not_mentioned' && hostAnswers.was_processing_necessary == 'no') {
|
||||||
|
return 'no_purpose';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnlawfulData extends Problem {
|
||||||
|
static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {}
|
||||||
|
getEmailContent() {
|
||||||
|
const _ = (key: string) => v(key, this.answers.zaimek);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>Przetwarzanie danych osobowych bez ważnej podsawy prawnej</h2>
|
||||||
|
<p>
|
||||||
|
{_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami
|
||||||
|
domen:
|
||||||
|
</p>
|
||||||
|
{this.getRangeDescription()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
getNecessaryExplainers() {
|
||||||
|
return [] as ExplainerKey[];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
import * as Survey from 'survey-react';
|
import * as Survey from 'survey-react';
|
||||||
import { RequestCluster } from '../../request-cluster';
|
|
||||||
import RawAnswers from './raw-answers';
|
import RawAnswers from './raw-answers';
|
||||||
import useSurvey from './use-survey';
|
import useSurvey from './use-survey';
|
||||||
|
|
||||||
export default function Questions({
|
export default function Questions({
|
||||||
clusters,
|
hosts,
|
||||||
onComplete,
|
onComplete,
|
||||||
}: {
|
}: {
|
||||||
clusters: RequestCluster[];
|
hosts: string[];
|
||||||
onComplete: (data: RawAnswers) => void;
|
onComplete: (data: RawAnswers) => void;
|
||||||
}) {
|
}) {
|
||||||
const survey = useSurvey(clusters, {
|
const survey = useSurvey(hosts, {
|
||||||
onComplete: (sender) => onComplete(sender.data),
|
onComplete: (sender) => onComplete(sender.data),
|
||||||
});
|
});
|
||||||
if (!survey) {
|
if (!survey) {
|
||||||
|
|
|
@ -16,8 +16,6 @@ export type HostRawAnswers = {
|
||||||
|
|
||||||
export type BasicRawAnswers = {
|
export type BasicRawAnswers = {
|
||||||
zaimek: 0 | 1 | 2 | 3;
|
zaimek: 0 | 1 | 2 | 3;
|
||||||
user_role: 'user' | 'admin';
|
|
||||||
email_type: 'polite_information' | 'official_request';
|
|
||||||
is_incognito_different: [] | ['incognito_is_the_same'];
|
is_incognito_different: [] | ['incognito_is_the_same'];
|
||||||
policy_readable: 'yes' | 'vague' | 'cant_find';
|
policy_readable: 'yes' | 'vague' | 'cant_find';
|
||||||
popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other';
|
popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other';
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export function reportIntro(visited_url: string) {
|
|
||||||
return <h2>Analiza skryptów śledzących na {visited_url} - raport</h2>;
|
|
||||||
}
|
|
|
@ -10,11 +10,11 @@
|
||||||
>
|
>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="/node_modules/survey-react/survey.min.css"
|
href="/node_modules/survey-react/survey.css"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="/node_modules/survey-react/modern.min.css"
|
href="/node_modules/survey-react/modern.css"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
@ -29,9 +29,8 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="/node_modules/react/umd/react.production.min.js"></script>
|
<script src="/node_modules/react/umd/react.development.js"></script>
|
||||||
<script src="/node_modules/react-dom/umd/react-dom.production.min.js"></script>
|
<script src="/node_modules/react-dom/umd/react-dom.development.js"></script>
|
||||||
<script src="/node_modules/survey-react/survey.react.min.js"></script>
|
|
||||||
<script src="/lib/components/report-window/report-window.js"></script>
|
<script src="/lib/components/report-window/report-window.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,6 @@
|
||||||
font-family: 'OpenSans' !important;
|
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 {
|
html {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -116,10 +105,6 @@ h1 {
|
||||||
.sv_body {
|
.sv_body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.sv_nav {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sv_p_root {
|
.sv_p_root {
|
||||||
& > .sv_row {
|
& > .sv_row {
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
|
@ -289,20 +274,3 @@ h1 {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: calc(14 / 16 * 1rem);
|
font-size: calc(14 / 16 * 1rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.generator-container {
|
|
||||||
max-width: 100ex;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-size: calc(14 / 16 * 1rem);
|
|
||||||
margin-top: 3rem;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $ultra-black-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.diag-toolbox {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 10px;
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,33 +9,18 @@ import EmailContent from './email-content';
|
||||||
import { parseAnswers, ParsedAnswers } from './parse-answers';
|
import { parseAnswers, ParsedAnswers } from './parse-answers';
|
||||||
import ScreenshotGenerator from './screenshot-generator';
|
import ScreenshotGenerator from './screenshot-generator';
|
||||||
|
|
||||||
function downloadFiles(link: string) {
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.setAttribute('href', link);
|
|
||||||
a.setAttribute('download', '');
|
|
||||||
a.setAttribute('target', '_blank');
|
|
||||||
a.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function Report() {
|
function Report() {
|
||||||
try {
|
try {
|
||||||
const url = new URL(document.location.toString());
|
const url = new URL(document.location.toString());
|
||||||
const origin = url.searchParams.get('origin');
|
const origin = url.searchParams.get('origin');
|
||||||
if (!origin) {
|
|
||||||
return <div>Błąd: brak parametru "origin"</div>;
|
|
||||||
}
|
|
||||||
const [counter] = useEmitter(getMemory());
|
const [counter] = useEmitter(getMemory());
|
||||||
const rawAnswers = url.searchParams.get('answers');
|
|
||||||
const [answers, setAnswers] = React.useState<ParsedAnswers>(
|
const [answers, setAnswers] = React.useState<ParsedAnswers>(
|
||||||
rawAnswers ? JSON.parse(rawAnswers) : null
|
url.searchParams.get('answers') ? JSON.parse(url.searchParams.get('answers')) : null
|
||||||
);
|
);
|
||||||
const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey');
|
const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey');
|
||||||
const [scrRequestPath, setScrRequestPath] = React.useState('');
|
const clusters = getMemory().getClustersForOrigin(origin);
|
||||||
|
|
||||||
const clusters = getMemory().getClustersForOrigin(origin || '');
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!origin) return;
|
|
||||||
const url = new URL(document.location.toString());
|
const url = new URL(document.location.toString());
|
||||||
url.searchParams.set('origin', origin);
|
url.searchParams.set('origin', origin);
|
||||||
url.searchParams.set('answers', JSON.stringify(answers));
|
url.searchParams.set('answers', JSON.stringify(answers));
|
||||||
|
@ -43,20 +28,16 @@ function Report() {
|
||||||
history.pushState({}, 'Rentgen', url.toString());
|
history.pushState({}, 'Rentgen', url.toString());
|
||||||
}, [mode, answers, origin]);
|
}, [mode, answers, origin]);
|
||||||
const visited_url = Object.values(clusters)
|
const visited_url = Object.values(clusters)
|
||||||
.sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1))
|
.find((cluster) => cluster.getMarkedRequests().length > 0)
|
||||||
.find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl;
|
?.getMarkedRequests()[0].originalURL;
|
||||||
|
|
||||||
if (!visited_url) {
|
|
||||||
return <div>Wczytywanie...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = (
|
const result = (
|
||||||
<div {...{ 'data-version': counter }}>
|
<div {...{ 'data-version': counter }}>
|
||||||
{mode === 'survey' ? (
|
{mode === 'survey' ? (
|
||||||
<Questions
|
<Questions
|
||||||
clusters={Object.values(clusters).filter(
|
hosts={Object.values(clusters)
|
||||||
(cluster) => cluster.getMarkedRequests().length > 0
|
.filter((cluster) => cluster.getMarkedRequests().length > 0)
|
||||||
)}
|
.map((cluster) => cluster.id)}
|
||||||
onComplete={(answers) => {
|
onComplete={(answers) => {
|
||||||
setAnswers(parseAnswers(answers));
|
setAnswers(parseAnswers(answers));
|
||||||
setMode('screenshots');
|
setMode('screenshots');
|
||||||
|
@ -67,32 +48,13 @@ function Report() {
|
||||||
)}
|
)}
|
||||||
{mode === 'screenshots' ? (
|
{mode === 'screenshots' ? (
|
||||||
<ScreenshotGenerator
|
<ScreenshotGenerator
|
||||||
{...{
|
{...{ visited_url, clusters, setReportWindowMode: setMode }}
|
||||||
visited_url,
|
|
||||||
clusters,
|
|
||||||
setReportWindowMode: setMode,
|
|
||||||
setRequestPath: setScrRequestPath,
|
|
||||||
downloadFiles: downloadFiles,
|
|
||||||
user_role: answers.user_role,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{mode === 'preview' ? (
|
|
||||||
<EmailContent
|
|
||||||
{...{
|
|
||||||
answers,
|
|
||||||
visited_url,
|
|
||||||
clusters,
|
|
||||||
scrRequestPath,
|
|
||||||
downloadFiles: downloadFiles,
|
|
||||||
user_role: answers.user_role,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
|
{mode === 'preview' ? <EmailContent {...{ answers, visited_url, clusters }} /> : ''}
|
||||||
|
{/* <HARConverter {...{ entries }} /> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
@ -110,7 +72,7 @@ function Report() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section id="main-section">{result}</section>
|
<section>{result}</section>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -23,6 +23,16 @@ h2 {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.generator-container {
|
||||||
|
max-width: 100ex;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: calc(14 / 16 * 1rem);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $ultra-black-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.buttons-container {
|
.buttons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -39,34 +49,20 @@ h2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser {
|
.browser {
|
||||||
height: 9.267rem;
|
height: 8.667rem;
|
||||||
font-weight: 800 !important;
|
font-weight: 800 !important;
|
||||||
color: $disabled-grey !important;
|
color: $disabled-grey !important;
|
||||||
border: 1px solid $disabled-grey;
|
border: 1px solid $disabled-grey;
|
||||||
background: linear-gradient(to bottom, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
|
background-image: linear-gradient(to bottom, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
|
||||||
background-size: 100%;
|
animation: xray 2s cubic-bezier(0, 1.43, 0.39, 1.43) infinite;
|
||||||
background-position-y: 26.5px;
|
|
||||||
|
|
||||||
&--filled {
|
&--filled {
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
background-position-y: 26.5px;
|
background-position-y: 19px;
|
||||||
animation: none;
|
animation: none;
|
||||||
|
|
||||||
&--address-bar {
|
.browser__header {
|
||||||
border: 1px solid #8a949f;
|
background-color: #fff;
|
||||||
height: 1rem;
|
|
||||||
width: 10rem;
|
|
||||||
font-size: 0.667rem;
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 0 0.25rem;
|
|
||||||
color: #000;
|
|
||||||
overflow: hidden;
|
|
||||||
word-break: normal;
|
|
||||||
inline-size: 10rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
background: linear-gradient(to left, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
|
|
||||||
animation: xray-header 2s cubic-bezier(0, 1.43, 0.39, 1.43) infinite;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,42 +72,28 @@ h2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes xray-header {
|
|
||||||
to {
|
|
||||||
background-position-x: 11.1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
height: 1.667rem;
|
height: 1.667rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #fff;
|
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
border-bottom: 1px solid $disabled-grey;
|
border-bottom: 1px solid $disabled-grey;
|
||||||
|
|
||||||
&--in_progress {
|
|
||||||
.browser__header--address-bar {
|
|
||||||
background: linear-gradient(to left, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
|
|
||||||
animation: xray-header 2s cubic-bezier(0, 1.43, 0.39, 1.43) infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--address-bar {
|
&--address-bar {
|
||||||
border: 1px solid #8a949f;
|
border: 1px solid $disabled-grey;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
font-size: 0.667rem;
|
font-size: 0.667rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 0 0.25rem;
|
padding-left: 0.25rem;
|
||||||
color: #000;
|
color: #000;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
word-break: normal;
|
word-break: break-all;
|
||||||
inline-size: 10rem;
|
inline-size: 10rem;
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--controls {
|
&--controls {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
||||||
import { RequestCluster } from '../../request-cluster';
|
import { RequestCluster } from '../../request-cluster';
|
||||||
import './screenshot-generator.scss';
|
import './screenshot-generator.scss';
|
||||||
|
|
||||||
const SS_URL = 'https://screenshot-service.internet-czas-dzialac.pl';
|
const SS_URL = 'http://65.108.60.135:3000';
|
||||||
|
|
||||||
enum taskState {
|
enum taskState {
|
||||||
WAITING = 'waiting',
|
WAITING = 'waiting',
|
||||||
|
@ -12,33 +12,21 @@ enum taskState {
|
||||||
|
|
||||||
type Screenshot = {
|
type Screenshot = {
|
||||||
url: string;
|
url: string;
|
||||||
thumb_url: string;
|
|
||||||
domain: string;
|
domain: string;
|
||||||
filename: string;
|
|
||||||
found_headers: string[];
|
found_headers: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface screenshotTask {
|
interface screenshotTask {
|
||||||
domains: string[];
|
|
||||||
elapsed_time_s: number;
|
|
||||||
current_action: string;
|
|
||||||
finished_time: number;
|
|
||||||
id: string;
|
|
||||||
images: Screenshot[];
|
|
||||||
jobs_ahead: number;
|
|
||||||
output: string;
|
|
||||||
processing_took: number;
|
|
||||||
request_time: number;
|
|
||||||
started_time: number;
|
|
||||||
status: taskState;
|
|
||||||
url: string;
|
url: string;
|
||||||
waiting_took: number;
|
domains: string[];
|
||||||
zip_url: string;
|
id: string;
|
||||||
preview: string;
|
status: taskState;
|
||||||
|
output: string;
|
||||||
|
images: Screenshot[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTaskEndpoint(visited_url: string, domains: string[]) {
|
function createTaskEndpoint(visited_url: string, domains: string[]) {
|
||||||
return `${SS_URL}/api/requests?url=${encodeURIComponent(visited_url)}${domains.reduce(
|
return `${SS_URL}/api/requests?url=${visited_url}${domains.reduce(
|
||||||
(prev: string, curr: string) => prev + '&domains[]=' + curr,
|
(prev: string, curr: string) => prev + '&domains[]=' + curr,
|
||||||
''
|
''
|
||||||
)}`;
|
)}`;
|
||||||
|
@ -56,38 +44,23 @@ export default function ScreenshotGenerator({
|
||||||
visited_url,
|
visited_url,
|
||||||
clusters,
|
clusters,
|
||||||
setReportWindowMode,
|
setReportWindowMode,
|
||||||
setRequestPath,
|
|
||||||
downloadFiles,
|
|
||||||
user_role,
|
|
||||||
}: {
|
}: {
|
||||||
visited_url: string;
|
visited_url: string;
|
||||||
clusters: Record<string, RequestCluster>;
|
clusters: Record<string, RequestCluster>;
|
||||||
setReportWindowMode: Function;
|
setReportWindowMode: Function;
|
||||||
setRequestPath: Function;
|
|
||||||
downloadFiles: Function;
|
|
||||||
user_role: string;
|
|
||||||
}) {
|
}) {
|
||||||
const [mode, setMode] = React.useState<string>('idle');
|
const [mode, setMode] = React.useState<string>('idle');
|
||||||
const [images, setImages] = React.useState<Screenshot[]>([]);
|
const [images, setImages] = React.useState<Screenshot[]>([]);
|
||||||
const [taskId, setTaskId] = React.useState<string | null>(null);
|
const [taskId, setTaskId] = React.useState<string>(null);
|
||||||
const [output, setOutput] = React.useState<any>({});
|
|
||||||
const [currentAction, setCurrentAction] = React.useState<string>('');
|
|
||||||
const [preview, setPreview] = React.useState<string>('');
|
|
||||||
const [lastPreview, setLastPreview] = React.useState<string>('');
|
|
||||||
|
|
||||||
async function subscribeTask(path: string): Promise<screenshotTask> {
|
async function subscribeTask(path: string): Promise<screenshotTask> {
|
||||||
let response = { status: taskState.WAITING };
|
let response = { status: taskState.WAITING };
|
||||||
let last_preview = '';
|
|
||||||
while (response.status === taskState.WAITING || response.status === taskState.RUNNING) {
|
while (response.status === taskState.WAITING || response.status === taskState.RUNNING) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
response = await (await pollTask(path)).json();
|
response = await (await pollTask(path)).json();
|
||||||
setImages((response as screenshotTask)?.images);
|
setImages((response as screenshotTask)?.images);
|
||||||
setCurrentAction((response as screenshotTask)?.current_action);
|
document.querySelector('.images').scrollTo({
|
||||||
setLastPreview(last_preview);
|
top: document.querySelector('.images').scrollHeight,
|
||||||
setPreview((response as screenshotTask)?.preview);
|
|
||||||
last_preview = (response as screenshotTask)?.preview;
|
|
||||||
document.querySelector('.images')?.scrollTo({
|
|
||||||
top: document.querySelector('.images')?.scrollHeight,
|
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -98,6 +71,19 @@ export default function ScreenshotGenerator({
|
||||||
return response as screenshotTask;
|
return response as screenshotTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadFiles() {
|
||||||
|
const urls = images.map((el) => `${SS_URL}${el}`);
|
||||||
|
|
||||||
|
for (const url of urls) {
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.setAttribute('href', url);
|
||||||
|
a.setAttribute('download', '');
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
a.click();
|
||||||
|
}
|
||||||
|
setReportWindowMode('preview');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="generator-container">
|
<div className="generator-container">
|
||||||
{mode === 'idle' ? (
|
{mode === 'idle' ? (
|
||||||
|
@ -105,42 +91,16 @@ export default function ScreenshotGenerator({
|
||||||
<h1>Przygotowanie zrzutów ekranów</h1>
|
<h1>Przygotowanie zrzutów ekranów</h1>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h2>Notka informacyjna</h2>
|
<h2>Notka informacyjna</h2>
|
||||||
<img
|
|
||||||
src="/assets/doctor_welcome.png"
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: '360px',
|
|
||||||
float: 'right',
|
|
||||||
position: 'relative',
|
|
||||||
top: '-10px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Fragment>
|
|
||||||
<p>
|
<p>
|
||||||
W celu udokumentowania procesów przetwarzania danych, jakie wykryła
|
Dla potwierdzenia przechwyconych danych, warto załączyć zrzuty ekranów
|
||||||
nasza wtyczka na tej stronie, warto wykonać zrzuty ekranu, na
|
narzędzi deweloperskich do maila dla administratora oraz Urzędu Ochrony
|
||||||
których widać przeglądarkę z otwartymi narzędziami deweloperskimi,
|
Danych Osobowych.
|
||||||
ukazując wybrane elementy ruchu sieciowego generowanego przez
|
|
||||||
stronę.
|
|
||||||
</p>
|
|
||||||
<p>Jeżeli chcesz, wtyczka Rentgen może wygenerować je za Ciebie.</p>
|
|
||||||
<p>
|
|
||||||
Uwaga: aby to zrobić, adres aktualnie odwiedzonej podstrony
|
|
||||||
analizowanej witryny będzie wysłany na nasz serwer, aby na nim
|
|
||||||
odwiedzić tę podstronę i wykonać zrzuty ekranu.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Serwer, na którym jest wykonywana analiza należy do inicjatywy{' '}
|
Jeżeli nie wiesz jak wykonać zrzuty ekranów, skorzystaj z{' '}
|
||||||
<a href="https://www.internet-czas-dzialac.pl/contact/">
|
<a href="">naszej instrukcji</a> lub wtyczka Rentgen może wygenerować je
|
||||||
<i>Internet. Czas działać!</i>
|
za Ciebie.
|
||||||
</a>
|
|
||||||
. Zebrane dane nie są wysyłane do żadnych podmiotów trzecich i są
|
|
||||||
usuwane z serwera po 24 godzinach. Wysłanie na serwer informacji o
|
|
||||||
adresie przeglądanej strony jest konieczne, aby wykonać te zrzuty
|
|
||||||
ekranu w sposób automatyczny. Jeżeli nie chcesz korzystać z opcji
|
|
||||||
automatycznej, zachęcamy do wykonania zrzutów ekranu samodzielnie.
|
|
||||||
</p>
|
</p>
|
||||||
</Fragment>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="buttons-container">
|
<div className="buttons-container">
|
||||||
|
@ -148,7 +108,6 @@ export default function ScreenshotGenerator({
|
||||||
className="sv_prev_btn"
|
className="sv_prev_btn"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setReportWindowMode('preview');
|
setReportWindowMode('preview');
|
||||||
setRequestPath(null);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Pomiń
|
Pomiń
|
||||||
|
@ -157,20 +116,13 @@ export default function ScreenshotGenerator({
|
||||||
className="sv_next_btn"
|
className="sv_next_btn"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setMode('in_progress');
|
setMode('in_progress');
|
||||||
const task = await createTask(
|
const task = await createTask(visited_url, Object.keys(clusters));
|
||||||
visited_url,
|
|
||||||
Object.values(clusters)
|
|
||||||
.filter((cluster) => cluster.hasMarks())
|
|
||||||
.map((cluster) => cluster.id)
|
|
||||||
);
|
|
||||||
const urlArr = task.url.split('/');
|
const urlArr = task.url.split('/');
|
||||||
setTaskId(urlArr[urlArr.length - 1]);
|
setTaskId(urlArr[urlArr.length - 1]);
|
||||||
const response = await subscribeTask(task.url);
|
const response = await subscribeTask(task.url);
|
||||||
setImages(response.images);
|
setImages(response.images);
|
||||||
setLastPreview(preview);
|
console.log('response.images', response.images);
|
||||||
setPreview(response.preview);
|
console.log('output', response);
|
||||||
setOutput(response);
|
|
||||||
setRequestPath(response.zip_url);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Wygeneruj
|
Wygeneruj
|
||||||
|
@ -190,7 +142,6 @@ export default function ScreenshotGenerator({
|
||||||
Nasz serwer właśnie odwiedza wskazaną przez Ciebie stronę
|
Nasz serwer właśnie odwiedza wskazaną przez Ciebie stronę
|
||||||
i przygotowuje zrzuty ekranów narzędzi deweloperskich.
|
i przygotowuje zrzuty ekranów narzędzi deweloperskich.
|
||||||
</p>
|
</p>
|
||||||
<div>{currentAction}</div>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
{mode === 'finished' ? (
|
{mode === 'finished' ? (
|
||||||
|
@ -201,34 +152,18 @@ export default function ScreenshotGenerator({
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="images">
|
<div className="images">
|
||||||
{mode === 'in_progress' ? (
|
|
||||||
<div
|
|
||||||
className="browser"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${SS_URL}${preview})${
|
|
||||||
lastPreview ? `, url(${SS_URL}${lastPreview})` : ''
|
|
||||||
}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="browser__header browser__header--in_progress">
|
|
||||||
<div className="browser__header--address-bar"></div>
|
|
||||||
<div className="browser__header--controls">· · ·</div>
|
|
||||||
</div>
|
|
||||||
<div className="browser__content"></div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{images.map((screenshot) => {
|
{images.map((screenshot) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${taskId}_${screenshot.url}`}
|
key={`${taskId}_${screenshot.url}`}
|
||||||
className="browser browser--filled"
|
className="browser browser--filled"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${SS_URL}${screenshot.thumb_url})`,
|
backgroundImage: `url(${SS_URL}${screenshot.url})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="browser__header">
|
<div className="browser__header">
|
||||||
<div className="browser__header--address-bar">
|
<div className="browser__header--address-bar">
|
||||||
🕸 {screenshot.domain}
|
{screenshot.url.split('-').slice(-2).join('-')}
|
||||||
</div>
|
</div>
|
||||||
<div className="browser__header--controls">· · ·</div>
|
<div className="browser__header--controls">· · ·</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -236,18 +171,22 @@ export default function ScreenshotGenerator({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{mode === 'in_progress' ? (
|
||||||
|
<div className="browser">
|
||||||
|
<div className="browser__header">
|
||||||
|
<div className="browser__header--address-bar"></div>
|
||||||
|
<div className="browser__header--controls">· · ·</div>
|
||||||
|
</div>
|
||||||
|
<div className="browser__content"></div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="buttons-container">
|
<div className="buttons-container">
|
||||||
{mode === 'finished' ? (
|
{mode === 'finished' ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<button
|
<button className="sv_next_btn" onClick={() => downloadFiles()}>
|
||||||
className="sv_next_btn"
|
|
||||||
onClick={() => {
|
|
||||||
downloadFiles(`${SS_URL}${output.zip_url}`);
|
|
||||||
setReportWindowMode('preview');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Pobierz zrzuty ekranów i przejdź dalej
|
Pobierz zrzuty ekranów i przejdź dalej
|
||||||
</button>
|
</button>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as Survey from 'survey-react';
|
import * as Survey from 'survey-react';
|
||||||
import { RequestCluster } from '../../request-cluster';
|
|
||||||
import generateSurveyQuestions from './generate-survey-questions';
|
import generateSurveyQuestions from './generate-survey-questions';
|
||||||
import RawAnswers from './raw-answers';
|
import RawAnswers from './raw-answers';
|
||||||
import verbs, { v } from './verbs';
|
import verbs, { v } from './verbs';
|
||||||
|
|
||||||
export default function useSurvey(
|
export default function useSurvey(
|
||||||
clusters: RequestCluster[],
|
hosts: string[],
|
||||||
{ onComplete }: { onComplete: (sender: { data: RawAnswers }) => void }
|
{ onComplete }: { onComplete: (sender: { data: RawAnswers }) => void }
|
||||||
): Survey.ReactSurveyModel | null {
|
): Survey.ReactSurveyModel {
|
||||||
const [survey, setSurvey] = React.useState<Survey.Model | null>(null);
|
const [survey, setSurvey] = React.useState<Survey.Model>(null);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const model = generateSurveyQuestions(clusters);
|
const model = generateSurveyQuestions(hosts);
|
||||||
|
console.log(model);
|
||||||
const survey = new Survey.Model(model);
|
const survey = new Survey.Model(model);
|
||||||
survey.onProcessTextValue.add(function (
|
survey.onProcessTextValue.add(function (
|
||||||
sender: Survey.SurveyModel,
|
sender: Survey.SurveyModel,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const words = {
|
const words = {
|
||||||
ciebie: ['ciebie', 'ciebie', 'ciebie', 'was'],
|
ciebie: ['ciebie', 'ciebie', 'ciebie', 'was'],
|
||||||
chciałbym: ['chciałbym', 'chciałabym', 'chciałobym', 'chcielibyśmy'],
|
|
||||||
dokonałeś: ['dokonałeś', 'dokonałaś', 'dokonałoś', 'dokonaliście'],
|
dokonałeś: ['dokonałeś', 'dokonałaś', 'dokonałoś', 'dokonaliście'],
|
||||||
jesteś: ['jesteś', 'jesteś', 'jesteś', 'jesteście'],
|
jesteś: ['jesteś', 'jesteś', 'jesteś', 'jesteście'],
|
||||||
kliknąłem: ['kliknąłem', 'kliknęłam', 'klinkęłom', 'kliknęliśmy'],
|
kliknąłem: ['kliknąłem', 'kliknęłam', 'klinkęłom', 'kliknęliśmy'],
|
||||||
|
@ -9,8 +8,6 @@ const words = {
|
||||||
mnie: ['mnie', 'mnie', 'mnie', 'nas'],
|
mnie: ['mnie', 'mnie', 'mnie', 'nas'],
|
||||||
moich: ['moich', 'moich', 'moich', 'naszych'],
|
moich: ['moich', 'moich', 'moich', 'naszych'],
|
||||||
moje: ['moje', 'moje', 'moje', 'nasze'],
|
moje: ['moje', 'moje', 'moje', 'nasze'],
|
||||||
mojego: ['mojego', 'mojego', 'mojego', 'naszego'],
|
|
||||||
moja: ['moja', 'moja', 'moja', 'nasza'],
|
|
||||||
mojej: ['mojej', 'mojej', 'mojej', 'naszej'],
|
mojej: ['mojej', 'mojej', 'mojej', 'naszej'],
|
||||||
muszę: ['muszę', 'muszę', 'muszę', 'musimy'],
|
muszę: ['muszę', 'muszę', 'muszę', 'musimy'],
|
||||||
odkliknąłeś: ['odkliknąłeś', 'odkliknęłaś', 'odklikęłoś', 'odkliknęliście'],
|
odkliknąłeś: ['odkliknąłeś', 'odkliknęłaś', 'odklikęłoś', 'odkliknęliście'],
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import { getMemory } from '../../memory';
|
|
||||||
import Options from '../../options';
|
import Options from '../../options';
|
||||||
import { useEmitter } from '../../util';
|
import { StolenData } from './stolen-data';
|
||||||
|
import { getshorthost, useEmitter } from '../../util';
|
||||||
|
import { getMemory } from '../../memory';
|
||||||
|
|
||||||
|
async function getCurrentTab() {
|
||||||
|
const [tab] = await browser.tabs.query({
|
||||||
|
active: true,
|
||||||
|
windowId: browser.windows.WINDOW_ID_CURRENT,
|
||||||
|
});
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
import './../../styles/global.scss';
|
import './../../styles/global.scss';
|
||||||
import './sidebar.scss';
|
import './sidebar.scss';
|
||||||
import { StolenData } from './stolen-data';
|
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const url = new URL(document.location.toString());
|
const url = new URL(document.location.toString());
|
||||||
|
@ -20,8 +28,8 @@ const Sidebar = () => {
|
||||||
const [cookiesOnly, setCookiesOnly] = React.useState<boolean>(false);
|
const [cookiesOnly, setCookiesOnly] = React.useState<boolean>(false);
|
||||||
const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
|
const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
|
||||||
const [cookiesOrOriginOnly, setCookiesOrOriginOnly] = React.useState<boolean>(false);
|
const [cookiesOrOriginOnly, setCookiesOrOriginOnly] = React.useState<boolean>(false);
|
||||||
const [eventCounts] = useEmitter(getMemory());
|
const [eventCounts, setEventCounts] = useEmitter(getMemory());
|
||||||
const [_, setMarksOccurrence] = React.useState<boolean>(false);
|
const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false);
|
||||||
const [infoDataDialogAck, setInfoDataDialogAck] = React.useState<boolean>(
|
const [infoDataDialogAck, setInfoDataDialogAck] = React.useState<boolean>(
|
||||||
localStorage.getItem('infoDataDialogAck') === null
|
localStorage.getItem('infoDataDialogAck') === null
|
||||||
? true
|
? true
|
||||||
|
@ -45,7 +53,6 @@ const Sidebar = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!origin) return;
|
|
||||||
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
|
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
|
||||||
if (cluster.hasMarks()) {
|
if (cluster.hasMarks()) {
|
||||||
return setMarksOccurrence(true);
|
return setMarksOccurrence(true);
|
||||||
|
@ -55,7 +62,6 @@ const Sidebar = () => {
|
||||||
return setMarksOccurrence(false);
|
return setMarksOccurrence(false);
|
||||||
}, [eventCounts['*']]);
|
}, [eventCounts['*']]);
|
||||||
|
|
||||||
if (!origin) return <div>Błąd: Brak parametru "origin"</div>;
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<header className="header">
|
<header className="header">
|
||||||
|
@ -198,7 +204,7 @@ const Sidebar = () => {
|
||||||
<StolenData
|
<StolenData
|
||||||
origin={origin}
|
origin={origin}
|
||||||
eventCounts={eventCounts}
|
eventCounts={eventCounts}
|
||||||
minValueLength={minValueLength === null ? 7 : minValueLength}
|
minValueLength={minValueLength}
|
||||||
cookiesOnly={cookiesOnly}
|
cookiesOnly={cookiesOnly}
|
||||||
cookiesOrOriginOnly={cookiesOrOriginOnly}
|
cookiesOrOriginOnly={cookiesOrOriginOnly}
|
||||||
detailsVisibility={detailsVisibility}
|
detailsVisibility={detailsVisibility}
|
||||||
|
@ -206,7 +212,7 @@ const Sidebar = () => {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Options
|
<Options
|
||||||
minValueLength={minValueLength === null ? 7 : minValueLength}
|
minValueLength={minValueLength}
|
||||||
setMinValueLength={setMinValueLength}
|
setMinValueLength={setMinValueLength}
|
||||||
cookiesOnly={cookiesOnly}
|
cookiesOnly={cookiesOnly}
|
||||||
setCookiesOnly={setCookiesOnly}
|
setCookiesOnly={setCookiesOnly}
|
||||||
|
|
|
@ -7,15 +7,6 @@
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon.cookie-data {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.domain-checkbox {
|
.domain-checkbox {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
width: 0.875rem;
|
width: 0.875rem;
|
||||||
|
|
|
@ -2,10 +2,12 @@ import React from 'react';
|
||||||
import { getMemory } from '../../memory';
|
import { getMemory } from '../../memory';
|
||||||
import { StolenDataEntry } from '../../stolen-data-entry';
|
import { StolenDataEntry } from '../../stolen-data-entry';
|
||||||
|
|
||||||
import { useEmitter } from '../../util';
|
import { maskString, useEmitter } from '../../util';
|
||||||
|
|
||||||
import './stolen-data-cluster.scss';
|
import './stolen-data-cluster.scss';
|
||||||
|
|
||||||
|
const MAX_STRING_VALUE_LENGTH = 100;
|
||||||
|
|
||||||
function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string }) {
|
function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string }) {
|
||||||
const [version] = useEmitter(entry);
|
const [version] = useEmitter(entry);
|
||||||
let body = null;
|
let body = null;
|
||||||
|
@ -41,16 +43,13 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={entry.isMarked}
|
checked={entry.isMarked}
|
||||||
id={entry.id.toString()}
|
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
entry.toggleMark();
|
entry.toggleMark();
|
||||||
getMemory().emit('change', entry.request.shorthost);
|
getMemory().emit('change', entry.request.shorthost);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>
|
<th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>{entry.name}</th>
|
||||||
<label htmlFor={entry.id.toString()}>{entry.name}</label>
|
|
||||||
</th>
|
|
||||||
<td className="icons">
|
<td className="icons">
|
||||||
{entry.source === 'cookie' ? (
|
{entry.source === 'cookie' ? (
|
||||||
<span title="Dane przechowywane w Cookies">
|
<span title="Dane przechowywane w Cookies">
|
||||||
|
@ -104,11 +103,13 @@ export default function StolenDataCluster({
|
||||||
shorthost,
|
shorthost,
|
||||||
minValueLength,
|
minValueLength,
|
||||||
cookiesOnly,
|
cookiesOnly,
|
||||||
|
refreshToken,
|
||||||
cookiesOrOriginOnly,
|
cookiesOrOriginOnly,
|
||||||
detailsVisibility,
|
detailsVisibility,
|
||||||
}: {
|
}: {
|
||||||
origin: string;
|
origin: string;
|
||||||
shorthost: string;
|
shorthost: string;
|
||||||
|
refreshToken: number;
|
||||||
minValueLength: number;
|
minValueLength: number;
|
||||||
cookiesOnly: boolean;
|
cookiesOnly: boolean;
|
||||||
cookiesOrOriginOnly: boolean;
|
cookiesOrOriginOnly: boolean;
|
||||||
|
@ -118,37 +119,27 @@ export default function StolenDataCluster({
|
||||||
const fullHosts = cluster.getFullHosts();
|
const fullHosts = cluster.getFullHosts();
|
||||||
const [version] = useEmitter(cluster);
|
const [version] = useEmitter(cluster);
|
||||||
|
|
||||||
|
/* console.log('Stolen data cluster!', shorthost, refreshToken); */
|
||||||
|
|
||||||
|
console.log(cluster.getMarkedEntries());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="stolen-data-cluster-container">
|
<div className="stolen-data-cluster-container">
|
||||||
<header className="domains-container">
|
<header className="domains-container">
|
||||||
<div className="domains-container__header">
|
<div>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="domain-checkbox"
|
className="domain-checkbox"
|
||||||
data-version={version}
|
data-version={version}
|
||||||
checked={cluster.hasMarks()}
|
checked={cluster.hasMarks()}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
console.log('Clicked checkbox!', {
|
|
||||||
cluster_id: cluster.id,
|
|
||||||
has_marks: cluster.hasMarks(),
|
|
||||||
});
|
|
||||||
cluster.hasMarks() ? cluster.undoMark() : cluster.autoMark();
|
cluster.hasMarks() ? cluster.undoMark() : cluster.autoMark();
|
||||||
getMemory().emit('change', cluster.id);
|
getMemory().emit('change', cluster.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<a className="domain" href={'https://' + cluster.id} target="_blank">
|
<a className="domain" href={'https://' + cluster.id} target="_blank">
|
||||||
{cluster.id}
|
{cluster.id}
|
||||||
</a>{' '}
|
</a>
|
||||||
{cluster.hasCookies() ? (
|
|
||||||
<img
|
|
||||||
src="/assets/icons/cookie.svg"
|
|
||||||
height={16}
|
|
||||||
width={16}
|
|
||||||
className="icon cookie-data"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="subdomains-container">
|
<div className="subdomains-container">
|
||||||
{fullHosts.map((host, index) => (
|
{fullHosts.map((host, index) => (
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function StolenData({
|
||||||
detailsVisibility,
|
detailsVisibility,
|
||||||
}: {
|
}: {
|
||||||
origin: string;
|
origin: string;
|
||||||
eventCounts: Record<string, number | undefined>;
|
eventCounts: Record<string, number>;
|
||||||
minValueLength: number;
|
minValueLength: number;
|
||||||
cookiesOnly: boolean;
|
cookiesOnly: boolean;
|
||||||
cookiesOrOriginOnly: boolean;
|
cookiesOrOriginOnly: boolean;
|
||||||
|
@ -43,7 +43,7 @@ export function StolenData({
|
||||||
origin={origin}
|
origin={origin}
|
||||||
shorthost={cluster.id}
|
shorthost={cluster.id}
|
||||||
key={cluster.id + origin}
|
key={cluster.id + origin}
|
||||||
refreshToken={eventCounts[cluster.id] || 0}
|
refreshToken={eventCounts[cluster.id]}
|
||||||
minValueLength={minValueLength}
|
minValueLength={minValueLength}
|
||||||
cookiesOnly={cookiesOnly}
|
cookiesOnly={cookiesOnly}
|
||||||
cookiesOrOriginOnly={cookiesOrOriginOnly}
|
cookiesOrOriginOnly={cookiesOrOriginOnly}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tab } from '../../util';
|
|
||||||
|
|
||||||
export default function TabDropdown({
|
export default function TabDropdown({
|
||||||
setPickedTab,
|
setPickedTab,
|
||||||
|
@ -8,7 +7,7 @@ export default function TabDropdown({
|
||||||
setPickedTab: (tab_id: number) => void;
|
setPickedTab: (tab_id: number) => void;
|
||||||
pickedTab: number;
|
pickedTab: number;
|
||||||
}) {
|
}) {
|
||||||
const [tabs, setTabs] = React.useState<Tab[]>([]);
|
const [tabs, setTabs] = React.useState([]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
browser.tabs.query({ currentWindow: true }).then(setTabs);
|
browser.tabs.query({ currentWindow: true }).then(setTabs);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -22,10 +22,6 @@ body {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&--no-page {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.webpage-metadata {
|
.webpage-metadata {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -14,20 +14,12 @@ async function getCurrentTab() {
|
||||||
import './../../styles/global.scss';
|
import './../../styles/global.scss';
|
||||||
import './toolbar.scss';
|
import './toolbar.scss';
|
||||||
|
|
||||||
function isDomainHighlySuspicious(domain: string): boolean {
|
|
||||||
return (
|
|
||||||
domain.includes('facebook') ||
|
|
||||||
domain.includes('twitter') ||
|
|
||||||
domain.includes('linkedin') ||
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Toolbar = () => {
|
const Toolbar = () => {
|
||||||
const [origin, setOrigin] = React.useState<string | null>(null);
|
const [origin, setOrigin] = React.useState<string | null>(null);
|
||||||
const [eventCounts] = useEmitter(getMemory());
|
const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
|
||||||
|
const [eventCounts, setEventCounts] = useEmitter(getMemory());
|
||||||
const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null);
|
const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null);
|
||||||
const [_, setMarksOccurrence] = React.useState<boolean>(false);
|
const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false);
|
||||||
const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>(
|
const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
@ -40,7 +32,7 @@ const Toolbar = () => {
|
||||||
const listener = async () => {
|
const listener = async () => {
|
||||||
const tab = await getCurrentTab();
|
const tab = await getCurrentTab();
|
||||||
|
|
||||||
if (tab !== undefined && tab.url) {
|
if (tab !== undefined) {
|
||||||
const url = new URL(tab.url);
|
const url = new URL(tab.url);
|
||||||
if (url.origin.startsWith('moz-extension')) {
|
if (url.origin.startsWith('moz-extension')) {
|
||||||
return;
|
return;
|
||||||
|
@ -59,85 +51,60 @@ const Toolbar = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!origin) return;
|
|
||||||
const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
|
const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
|
||||||
.filter((cluster) => cluster.exposesOrigin())
|
.filter((cluster) => cluster.exposesOrigin())
|
||||||
.sort((cluster1, cluster2) =>
|
|
||||||
isDomainHighlySuspicious(cluster1.id)
|
|
||||||
? -1
|
|
||||||
: isDomainHighlySuspicious(cluster2.id)
|
|
||||||
? 1
|
|
||||||
: 0
|
|
||||||
)
|
|
||||||
.map((cluster) => cluster.id);
|
.map((cluster) => cluster.id);
|
||||||
setExposedOriginDomainCopy('');
|
setExposedOriginDomainCopy('');
|
||||||
|
|
||||||
switch (exposedOriginDomains.length) {
|
switch (exposedOriginDomains.length) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
return null;
|
||||||
case 1:
|
case 1:
|
||||||
setExposedOriginDomainCopy(`${exposedOriginDomains[0]}.`);
|
return setExposedOriginDomainCopy(`${exposedOriginDomains[0]}.`);
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
setExposedOriginDomainCopy(
|
return setExposedOriginDomainCopy(
|
||||||
`${exposedOriginDomains[0]} oraz ${exposedOriginDomains[1]}.`
|
`${exposedOriginDomains[0]} oraz ${exposedOriginDomains[1]}.`
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
case 3:
|
case 3:
|
||||||
setExposedOriginDomainCopy(
|
return setExposedOriginDomainCopy(
|
||||||
`${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} oraz ${exposedOriginDomains[2]}.`
|
`${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} oraz ${exposedOriginDomains[2]}.`
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
setExposedOriginDomainCopy(
|
return setExposedOriginDomainCopy(
|
||||||
`${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} (i ${
|
`${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} (i ${
|
||||||
exposedOriginDomains.length - 2 < 2 ? 2 : exposedOriginDomains.length - 2
|
exposedOriginDomains.length - 2 < 2 ? 2 : exposedOriginDomains.length - 2
|
||||||
} innych).`
|
} innych).`
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}, [eventCounts['*'], origin]);
|
}, [eventCounts['*'], origin]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!origin) return;
|
|
||||||
const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
|
const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
|
||||||
.filter((cluster) => cluster.hasCookies())
|
.filter((cluster) => cluster.hasCookies())
|
||||||
.sort((cluster1, cluster2) =>
|
|
||||||
isDomainHighlySuspicious(cluster1.id)
|
|
||||||
? -1
|
|
||||||
: isDomainHighlySuspicious(cluster2.id)
|
|
||||||
? 1
|
|
||||||
: 0
|
|
||||||
)
|
|
||||||
.map((cluster) => cluster.id);
|
.map((cluster) => cluster.id);
|
||||||
setCookieDomainCopy('');
|
setCookieDomainCopy('');
|
||||||
|
|
||||||
switch (cookieDomains.length) {
|
switch (cookieDomains.length) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
return null;
|
||||||
case 1:
|
case 1:
|
||||||
setCookieDomainCopy(`${cookieDomains[0]}.`);
|
return setCookieDomainCopy(`${cookieDomains[0]}.`);
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
setCookieDomainCopy(`${cookieDomains[0]} oraz ${cookieDomains[1]}.`);
|
return setCookieDomainCopy(`${cookieDomains[0]} oraz ${cookieDomains[1]}.`);
|
||||||
break;
|
|
||||||
case 3:
|
case 3:
|
||||||
setCookieDomainCopy(
|
return setCookieDomainCopy(
|
||||||
`${cookieDomains[0]}, ${cookieDomains[1]} oraz ${cookieDomains[2]}.`
|
`${cookieDomains[0]}, ${cookieDomains[1]} oraz ${cookieDomains[2]}.`
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
setCookieDomainCopy(
|
return setCookieDomainCopy(
|
||||||
`${cookieDomains[0]}, ${cookieDomains[1]} (i ${
|
`${cookieDomains[0]}, ${cookieDomains[1]} (i ${
|
||||||
cookieDomains.length - 2 < 2 ? 2 : cookieDomains.length - 2
|
cookieDomains.length - 2 < 2 ? 2 : cookieDomains.length - 2
|
||||||
} innych).`
|
} innych).`
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}, [eventCounts['*'], origin]);
|
}, [eventCounts['*'], origin]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!origin) return;
|
|
||||||
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
|
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
|
||||||
if (cluster.hasMarks()) {
|
if (cluster.hasMarks()) {
|
||||||
return setMarksOccurrence(true);
|
return setMarksOccurrence(true);
|
||||||
|
@ -148,7 +115,6 @@ const Toolbar = () => {
|
||||||
}, [eventCounts['*']]);
|
}, [eventCounts['*']]);
|
||||||
|
|
||||||
function autoMark() {
|
function autoMark() {
|
||||||
if (!origin) return;
|
|
||||||
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
|
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
|
||||||
cluster.autoMark();
|
cluster.autoMark();
|
||||||
}
|
}
|
||||||
|
@ -157,7 +123,7 @@ const Toolbar = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="toolbar">
|
<div className="toolbar">
|
||||||
<header className={origin ? 'header' : 'header header--no-page'}>
|
<header className="header">
|
||||||
<img src="../../assets/icon-addon.svg" height={32}></img>
|
<img src="../../assets/icon-addon.svg" height={32}></img>
|
||||||
<div className="webpage-metadata">
|
<div className="webpage-metadata">
|
||||||
{origin ? (
|
{origin ? (
|
||||||
|
@ -176,37 +142,26 @@ const Toolbar = () => {
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{origin ? (
|
|
||||||
<Fragment>
|
|
||||||
{' '}
|
|
||||||
<section className="summary">
|
<section className="summary">
|
||||||
<div className="counters-wrapper">
|
<div className="counters-wrapper">
|
||||||
<div className="counters">
|
<div className="counters">
|
||||||
<div className="counter counter--cookies">
|
<div className="counter counter--browser-history">
|
||||||
<img
|
<img src="/assets/icons/warning.svg#color" width="24" height="24" />
|
||||||
src="/assets/icons/cookie.svg#color"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
/>
|
|
||||||
<span data-event={`${eventCounts['*']}`}>
|
<span data-event={`${eventCounts['*']}`}>
|
||||||
{
|
{
|
||||||
Object.values(
|
Object.values(getMemory().getClustersForOrigin(origin)).filter(
|
||||||
getMemory().getClustersForOrigin(origin)
|
(cluster) => cluster.exposesOrigin()
|
||||||
).filter((cluster) => cluster.hasCookies()).length
|
).length
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="counter counter--browser-history">
|
<div className="counter counter--cookies">
|
||||||
<img
|
<img src="/assets/icons/cookie.svg#color" width="24" height="24" />
|
||||||
src="/assets/icons/warning.svg#color"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
/>
|
|
||||||
<span data-event={`${eventCounts['*']}`}>
|
<span data-event={`${eventCounts['*']}`}>
|
||||||
{
|
{
|
||||||
Object.values(
|
Object.values(getMemory().getClustersForOrigin(origin)).filter(
|
||||||
getMemory().getClustersForOrigin(origin)
|
(cluster) => cluster.hasCookies()
|
||||||
).filter((cluster) => cluster.exposesOrigin()).length
|
).length
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -217,19 +172,8 @@ const Toolbar = () => {
|
||||||
</div>
|
</div>
|
||||||
<span className="notice">Liczba wykrytych domen podmiotów trzecich</span>
|
<span className="notice">Liczba wykrytych domen podmiotów trzecich</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="details">
|
<section className="details">
|
||||||
{cookieDomainCopy ? (
|
|
||||||
<p
|
|
||||||
data-event={`${eventCounts['*']}`}
|
|
||||||
title={Object.values(getMemory().getClustersForOrigin(origin))
|
|
||||||
.filter((cluster) => cluster.hasCookies())
|
|
||||||
.map((domain) => domain.id)
|
|
||||||
.join(', ')}
|
|
||||||
>
|
|
||||||
{first_sentence_cookie}
|
|
||||||
<strong>{cookieDomainCopy}</strong>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{exposedOriginDomainCopy ? (
|
{exposedOriginDomainCopy ? (
|
||||||
<p
|
<p
|
||||||
data-event={`${eventCounts['*']}`}
|
data-event={`${eventCounts['*']}`}
|
||||||
|
@ -238,18 +182,30 @@ const Toolbar = () => {
|
||||||
.map((domain) => domain.id)
|
.map((domain) => domain.id)
|
||||||
.join(', ')}
|
.join(', ')}
|
||||||
>
|
>
|
||||||
{first_sentence_history}
|
{first_sentence_cookie}
|
||||||
<strong>{exposedOriginDomainCopy}</strong>
|
<strong>{exposedOriginDomainCopy}</strong>
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
{cookieDomainCopy ? (
|
||||||
|
<p
|
||||||
|
data-event={`${eventCounts['*']}`}
|
||||||
|
title={Object.values(getMemory().getClustersForOrigin(origin))
|
||||||
|
.filter((cluster) => cluster.hasCookies())
|
||||||
|
.map((domain) => domain.id)
|
||||||
|
.join(', ')}
|
||||||
|
>
|
||||||
|
{first_sentence_history}
|
||||||
|
<strong>{cookieDomainCopy}</strong>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{exposedOriginDomainCopy || cookieDomainCopy ? (
|
{exposedOriginDomainCopy || cookieDomainCopy ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<section className="about">
|
<section className="about">
|
||||||
<p>
|
<p>
|
||||||
Takie przetwarzanie danych może być niezgodne z prawem. Przejdź
|
Takie przetwarzanie danych może być niezgodne z prawem. Przejdź do
|
||||||
do analizy aby pomóc ustalić, czy ta strona nie narusza RODO lub
|
analizy aby pomóc ustalić, czy ta strona nie narusza RODO.
|
||||||
ustawy Prawo Telekomunikacyjne.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section className="actions">
|
<section className="actions">
|
||||||
|
@ -275,8 +231,6 @@ const Toolbar = () => {
|
||||||
</section>
|
</section>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
13
diag.html
|
@ -1,13 +0,0 @@
|
||||||
<!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
|
@ -1,65 +0,0 @@
|
||||||
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')
|
|
||||||
);
|
|
|
@ -12,32 +12,32 @@ const watch = process.argv.includes('--watch') && {
|
||||||
let skipReactImports = {
|
let skipReactImports = {
|
||||||
name: 'skipReactImports',
|
name: 'skipReactImports',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
build.onResolve({ filter: /^(react(-dom)?|survey-react)$/ }, (args) => {
|
build.onResolve({ filter: /^react(-dom)?$/ }, (args) => {
|
||||||
return {
|
return {
|
||||||
path: args.path,
|
path: args.path,
|
||||||
namespace: `globalExternal_${args.path}`,
|
namespace: `globalExternal_${args.path}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'globalExternal_react' }, () => {
|
build.onLoad(
|
||||||
|
{ filter: /.*/, namespace: 'globalExternal_react' },
|
||||||
|
() => {
|
||||||
return {
|
return {
|
||||||
contents: `module.exports = globalThis.React`,
|
contents: `module.exports = globalThis.React`,
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'globalExternal_react-dom' }, () => {
|
build.onLoad(
|
||||||
|
{ filter: /.*/, namespace: 'globalExternal_react-dom' },
|
||||||
|
() => {
|
||||||
return {
|
return {
|
||||||
contents: `module.exports = globalThis.ReactDOM`,
|
contents: `module.exports = globalThis.ReactDOM`,
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => {
|
);
|
||||||
return {
|
|
||||||
contents: `module.exports = globalThis.Survey`,
|
|
||||||
loader: 'js',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,11 +46,11 @@ esbuild
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
'components/toolbar/toolbar.tsx',
|
'components/toolbar/toolbar.tsx',
|
||||||
'components/sidebar/sidebar.tsx',
|
'components/sidebar/sidebar.tsx',
|
||||||
|
'test.ts',
|
||||||
'components/report-window/report-window.tsx',
|
'components/report-window/report-window.tsx',
|
||||||
'background.ts',
|
'background.ts',
|
||||||
'diag.tsx',
|
|
||||||
'styles/global.scss',
|
'styles/global.scss',
|
||||||
'styles/fonts.scss',
|
'styles/fonts.scss'
|
||||||
],
|
],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
// minify: true,
|
// minify: true,
|
||||||
|
@ -59,9 +59,9 @@ esbuild
|
||||||
plugins: [scss(), skipReactImports],
|
plugins: [scss(), skipReactImports],
|
||||||
define: {
|
define: {
|
||||||
PLUGIN_NAME: '"Rentgen"',
|
PLUGIN_NAME: '"Rentgen"',
|
||||||
PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
|
PLUGIN_URL: '"https://git.internet-czas-dzialac.pl/icd/rentgen"',
|
||||||
},
|
},
|
||||||
external: ['react', 'react-dom', 'survey-react'],
|
external: ['react', 'react-dom'],
|
||||||
watch,
|
watch,
|
||||||
})
|
})
|
||||||
.then(() => console.log('Add-on was built'))
|
.then(() => console.log('Add-on was built'))
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
'use strict';
|
import { StolenDataEntry } from './stolen-data-entry';
|
||||||
import { DataLocation, StolenDataEntry } from './stolen-data-entry';
|
|
||||||
import {
|
import {
|
||||||
flattenObjectEntries,
|
flattenObjectEntries,
|
||||||
getshorthost,
|
getshorthost,
|
||||||
|
@ -76,13 +75,12 @@ export default class ExtendedRequest {
|
||||||
public tabId: number;
|
public tabId: number;
|
||||||
public url: string;
|
public url: string;
|
||||||
public shorthost: string;
|
public shorthost: string;
|
||||||
public requestHeaders: { name: string; value?: string; binaryValue?: number[] }[] = [];
|
public requestHeaders: Request['requestHeaders'] = [];
|
||||||
|
public originalURL: string;
|
||||||
public origin: string;
|
public origin: string;
|
||||||
public initialized = false;
|
public initialized = false;
|
||||||
public stolenData: StolenDataEntry[] = [];
|
public stolenData: StolenDataEntry[];
|
||||||
public originalURL: string | null = null; // sometimes we can only establish that the given request applied to a certain origin, not a full URL from the address bar - in case of service workers, for example. Hence the null
|
public originalPathname: string;
|
||||||
public originalPathname: string | null = null; // same as above
|
|
||||||
public originalHost: string;
|
|
||||||
public requestBody: RequestBody;
|
public requestBody: RequestBody;
|
||||||
|
|
||||||
static by_id = {} as Record<string, ExtendedRequest>;
|
static by_id = {} as Record<string, ExtendedRequest>;
|
||||||
|
@ -93,67 +91,67 @@ export default class ExtendedRequest {
|
||||||
this.url = data.url;
|
this.url = data.url;
|
||||||
this.shorthost = getshorthost(data.url);
|
this.shorthost = getshorthost(data.url);
|
||||||
this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {};
|
this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {};
|
||||||
|
if (this.url.includes('criteo')) {
|
||||||
|
console.log(this);
|
||||||
|
}
|
||||||
ExtendedRequest.by_id[data.requestId] = this;
|
ExtendedRequest.by_id[data.requestId] = this;
|
||||||
|
|
||||||
this.data = Object.assign({}, data);
|
this.data = Object.assign({}, data);
|
||||||
(this.data as any).frameAncestors = [
|
(this.data as any).frameAncestors = [
|
||||||
...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []),
|
...(data as any).frameAncestors.map((e: any) => ({ url: e.url })),
|
||||||
]; // making a copy?
|
];
|
||||||
|
|
||||||
// console.log('→→→',(this.data as any).frameAncestors, (data as any).frameAncestors);
|
// console.log('→→→',(this.data as any).frameAncestors, (data as any).frameAncestors);
|
||||||
|
|
||||||
let url: string;
|
|
||||||
let is_full_url = true;
|
|
||||||
let url_comes_from: string;
|
|
||||||
if (this.data.type === 'main_frame') {
|
|
||||||
url = this.data.url;
|
|
||||||
url_comes_from = 'main_frame';
|
|
||||||
} else if (this.data.frameId === 0 && this.data.documentUrl) {
|
|
||||||
url = this.data.documentUrl;
|
|
||||||
url_comes_from = 'documentUrl';
|
|
||||||
if (this.data.tabId == -1) {
|
|
||||||
//a service worker?
|
|
||||||
url_comes_from = 'documentUrl (webworker)';
|
|
||||||
is_full_url = false;
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
(this.data as any)?.frameAncestors &&
|
|
||||||
(this.data as any).frameAncestors[0] !== undefined
|
|
||||||
) {
|
|
||||||
url = (this.data as any).frameAncestors.at(-1).url || '';
|
|
||||||
url_comes_from = 'frameAncestors';
|
|
||||||
} else {
|
|
||||||
url = this.data.documentUrl || this.data.originUrl;
|
|
||||||
url_comes_from = 'last resort';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.originalURL = is_full_url ? url : null;
|
|
||||||
this.origin = new URL(url).origin;
|
|
||||||
|
|
||||||
this.originalHost = new URL(url).host;
|
|
||||||
this.originalPathname = is_full_url ? new URL(url).pathname : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addHeaders(headers: Request['requestHeaders']) {
|
addHeaders(headers: Request['requestHeaders']) {
|
||||||
this.requestHeaders = headers || [];
|
this.requestHeaders = headers;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
async init() {
|
||||||
|
await this.cacheOrigin();
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
this.stolenData = this.getAllStolenData();
|
this.stolenData = this.getAllStolenData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cacheOrigin(): Promise<void> {
|
||||||
|
let url: string;
|
||||||
|
if (this.data.tabId && this.data.tabId >= 0) {
|
||||||
|
const tab = await browser.tabs.get(this.data.tabId);
|
||||||
|
url = tab.url;
|
||||||
|
} else if (
|
||||||
|
(this.data as any)?.frameAncestors &&
|
||||||
|
(this.data as any).frameAncestors[0] !== undefined
|
||||||
|
) {
|
||||||
|
url = (this.data as any).frameAncestors[0].url || '';
|
||||||
|
} else {
|
||||||
|
const headers = Object.fromEntries(
|
||||||
|
this.requestHeaders.map(({ name, value }) => [name, value])
|
||||||
|
);
|
||||||
|
if (headers.Referer) {
|
||||||
|
url = headers.Referer;
|
||||||
|
} else {
|
||||||
|
url = this.data.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.originalURL = url;
|
||||||
|
this.origin = new URL(url).origin;
|
||||||
|
this.originalPathname = new URL(url).pathname;
|
||||||
|
}
|
||||||
|
|
||||||
isThirdParty() {
|
isThirdParty() {
|
||||||
const request_url = new URL(this.data.url);
|
const request_url = new URL(this.data.url);
|
||||||
if (request_url.host.includes(this.originalHost)) {
|
const origin_url = new URL(this.originalURL);
|
||||||
|
if (request_url.host.includes(origin_url.host)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (getshorthost(request_url.host) == getshorthost(this.originalHost)) {
|
if (getshorthost(request_url.host) == getshorthost(origin_url.host)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
request_url.origin != this.origin ||
|
request_url.origin != origin_url.origin ||
|
||||||
(this.data as any).urlClassification.thirdParty.length > 0
|
(this.data as any).urlClassification.thirdParty.length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -164,12 +162,13 @@ export default class ExtendedRequest {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
exposesOriginWhere(): null | DataLocation {
|
exposesOrigin() {
|
||||||
const host = this.originalHost;
|
const url = new URL(this.originalURL);
|
||||||
const path = this.originalPathname || '/';
|
const host = url.host;
|
||||||
|
const path = url.pathname;
|
||||||
const shorthost = getshorthost(host);
|
const shorthost = getshorthost(host);
|
||||||
if (this.getReferer().includes(shorthost)) {
|
if (this.getReferer().includes(shorthost)) {
|
||||||
return { path: this.url, source: 'header', key: 'Referer' };
|
return true;
|
||||||
}
|
}
|
||||||
for (const entry of this.stolenData) {
|
for (const entry of this.stolenData) {
|
||||||
if (
|
if (
|
||||||
|
@ -177,14 +176,10 @@ export default class ExtendedRequest {
|
||||||
entry.value.includes(path) ||
|
entry.value.includes(path) ||
|
||||||
entry.value.includes(shorthost)
|
entry.value.includes(shorthost)
|
||||||
) {
|
) {
|
||||||
return entry.toDataLocation();
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
exposesOrigin() {
|
|
||||||
return this.exposesOriginWhere() !== null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAllStolenData(): StolenDataEntry[] {
|
private getAllStolenData(): StolenDataEntry[] {
|
||||||
|
@ -222,10 +217,7 @@ export default class ExtendedRequest {
|
||||||
if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) {
|
if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) {
|
||||||
return ['requestBody', key];
|
return ['requestBody', key];
|
||||||
} else if (!Array.isArray(value)) {
|
} else if (!Array.isArray(value)) {
|
||||||
return [
|
return ['raw', String.fromCharCode.apply(null, new Uint8Array(value.bytes))];
|
||||||
'raw',
|
|
||||||
String.fromCharCode.apply(null, Array.from(new Uint8Array(value.bytes))),
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
return [key, value || ''];
|
return [key, value || ''];
|
||||||
}
|
}
|
||||||
|
@ -244,7 +236,7 @@ export default class ExtendedRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCookie(): string {
|
getCookie(): string {
|
||||||
return this.requestHeaders.find((h) => h.name == 'Cookie')?.value || '';
|
return this.requestHeaders.find((h) => h.name == 'Cookie')?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPathParams(): StolenDataEntry[] {
|
getPathParams(): StolenDataEntry[] {
|
||||||
|
@ -267,8 +259,8 @@ export default class ExtendedRequest {
|
||||||
getQueryParams(): StolenDataEntry[] {
|
getQueryParams(): StolenDataEntry[] {
|
||||||
const url = new URL(this.data.url);
|
const url = new URL(this.data.url);
|
||||||
return flattenObjectEntries(
|
return flattenObjectEntries(
|
||||||
(Array.from((url.searchParams as any).entries()) as [string, string][])
|
Array.from((url.searchParams as any).entries())
|
||||||
.map(([key, value]: [string, string]) => [key, value || ''])
|
.map(([key, value]) => [key, value || ''])
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
return [key, StolenDataEntry.parseValue(safeDecodeURIComponent(value))];
|
return [key, StolenDataEntry.parseValue(safeDecodeURIComponent(value))];
|
||||||
})
|
})
|
||||||
|
@ -291,7 +283,7 @@ export default class ExtendedRequest {
|
||||||
.map((header) => {
|
.map((header) => {
|
||||||
return [
|
return [
|
||||||
header.name,
|
header.name,
|
||||||
StolenDataEntry.parseValue(safeDecodeURIComponent(header.value || '')),
|
StolenDataEntry.parseValue(safeDecodeURIComponent(header.value)),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
).map(([key, value]) => new StolenDataEntry(this, 'header', key, value));
|
).map(([key, value]) => new StolenDataEntry(this, 'header', key, value));
|
||||||
|
@ -305,10 +297,6 @@ export default class ExtendedRequest {
|
||||||
return this.stolenData.filter((data) => data.isMarked);
|
return this.stolenData.filter((data) => data.isMarked);
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarkAllEntries() {
|
|
||||||
this.stolenData.forEach((entry) => entry.unmark());
|
|
||||||
}
|
|
||||||
|
|
||||||
getHost() {
|
getHost() {
|
||||||
return new URL(this.url).host;
|
return new URL(this.url).host;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"description": "Rentgen to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony.",
|
"description": "Rentgen to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały wykradzione wysłane do podmiotów trzecich przez odwiedzane strony.",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Rentgen",
|
"name": "Rentgen",
|
||||||
"short_name": "Rentgen",
|
"short_name": "Rentgen",
|
||||||
"version": "0.1.10",
|
"version": "0.0.3",
|
||||||
"author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Czas działać!)",
|
"author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Czas działać!)",
|
||||||
"homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen",
|
"homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen",
|
||||||
"background": {
|
"background": {
|
||||||
|
|
11
memory.ts
|
@ -1,9 +1,9 @@
|
||||||
import ExtendedRequest from './extended-request';
|
import ExtendedRequest from './extended-request';
|
||||||
import { getshorthost } from './util';
|
import { getshorthost, makeThrottle } from './util';
|
||||||
import { RequestCluster } from './request-cluster';
|
import { RequestCluster } from './request-cluster';
|
||||||
import { SaferEmitter } from './safer-emitter';
|
import { SaferEmitter } from './safer-emitter';
|
||||||
|
|
||||||
function setDomainsCount(counter: number, tabId: number) {
|
function setDomainsNumber(counter: number, tabId: number) {
|
||||||
browser.browserAction.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId });
|
browser.browserAction.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId });
|
||||||
browser.browserAction.setTitle({
|
browser.browserAction.setTitle({
|
||||||
title: 'Rentgen',
|
title: 'Rentgen',
|
||||||
|
@ -35,13 +35,11 @@ export default class Memory extends SaferEmitter {
|
||||||
? browser.browserAction.setBadgeBackgroundColor({ color: '#ff726b' })
|
? browser.browserAction.setBadgeBackgroundColor({ color: '#ff726b' })
|
||||||
: browser.browserAction.setBadgeBackgroundColor({ color: '#ffb900' });
|
: browser.browserAction.setBadgeBackgroundColor({ color: '#ffb900' });
|
||||||
|
|
||||||
if (request.tabId >= 0) {
|
setDomainsNumber(
|
||||||
setDomainsCount(
|
|
||||||
Object.values(this.getClustersForOrigin(request.origin)).length,
|
Object.values(this.getClustersForOrigin(request.origin)).length,
|
||||||
request.tabId
|
request.tabId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -67,7 +65,7 @@ export default class Memory extends SaferEmitter {
|
||||||
|
|
||||||
emit(eventName: string, data = 'any'): boolean {
|
emit(eventName: string, data = 'any'): boolean {
|
||||||
setTimeout(() => super.emit(eventName, data), 0);
|
setTimeout(() => super.emit(eventName, data), 0);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getClustersForOrigin(origin: string): Record<string, RequestCluster> {
|
getClustersForOrigin(origin: string): Record<string, RequestCluster> {
|
||||||
|
@ -78,6 +76,7 @@ export default class Memory extends SaferEmitter {
|
||||||
if (shorthost) {
|
if (shorthost) {
|
||||||
const cookies = await browser.cookies.getAll({ domain: shorthost });
|
const cookies = await browser.cookies.getAll({ domain: shorthost });
|
||||||
for (const cookie of cookies) {
|
for (const cookie of cookies) {
|
||||||
|
console.log('removing cookie', cookie.name, 'from', cookie.domain);
|
||||||
await browser.cookies.remove({
|
await browser.cookies.remove({
|
||||||
name: cookie.name,
|
name: cookie.name,
|
||||||
url: `https://${cookie.domain}`,
|
url: `https://${cookie.domain}`,
|
||||||
|
|
5054
package-lock.json
generated
10
package.json
|
@ -1,17 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "rentgen",
|
"name": "rentgen",
|
||||||
"version": "0.1.10",
|
"version": "0.0.3",
|
||||||
"description": "Rentgen is an add-on prepared for Firefox-based browsers. This extension will automatically visualize all the data that a given website sends to third parties.",
|
"description": "A simple Firefox extension that visualizes all the data that a given website sends to third parties.",
|
||||||
"main": "esbuild.config.js",
|
"main": "esbuild.config.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node esbuild.config.js",
|
"build": "node esbuild.config.js",
|
||||||
"watch": "node esbuild.config.js --watch",
|
"watch": "node esbuild.config.js --watch",
|
||||||
"ext-test": "web-ext run",
|
"ext-test": "web-ext run",
|
||||||
"build-addon": "npm i && npm run build && npm run create-package",
|
"create-package": "web-ext build",
|
||||||
"create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react' '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest",
|
"typecheck": "tsc --noEmit"
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"lint": "web-ext lint"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FakeRequestClusterData } from './components/report-window/fake-clusters';
|
import { EventEmitter } from 'events';
|
||||||
import ExtendedRequest from './extended-request';
|
import ExtendedRequest from './extended-request';
|
||||||
import { SaferEmitter } from './safer-emitter';
|
import { SaferEmitter } from './safer-emitter';
|
||||||
import { DataLocation, Sources, StolenDataEntry } from './stolen-data-entry';
|
import { Sources, StolenDataEntry } from './stolen-data-entry';
|
||||||
|
|
||||||
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
|
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
|
||||||
|
|
||||||
|
@ -10,20 +10,13 @@ const source_priority: Array<Sources> = ['cookie', 'pathname', 'queryparams', 'h
|
||||||
export class RequestCluster extends SaferEmitter {
|
export class RequestCluster extends SaferEmitter {
|
||||||
public requests: ExtendedRequest[] = [];
|
public requests: ExtendedRequest[] = [];
|
||||||
public representativeStolenData: StolenDataEntry[] = [];
|
public representativeStolenData: StolenDataEntry[] = [];
|
||||||
public expanded: boolean = false;
|
public expanded: boolean;
|
||||||
public lastModified: number = 0;
|
|
||||||
public lastFullUrl: string | null = null;
|
|
||||||
constructor(public id: string) {
|
constructor(public id: string) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
add(request: ExtendedRequest) {
|
add(request: ExtendedRequest) {
|
||||||
this.requests.push(request);
|
this.requests.push(request);
|
||||||
this.emit('change');
|
this.emit('change');
|
||||||
this.lastModified = Date.now();
|
|
||||||
if (request.originalURL) {
|
|
||||||
this.lastFullUrl = request.originalURL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleExpanded(state: boolean) {
|
toggleExpanded(state: boolean) {
|
||||||
|
@ -172,13 +165,7 @@ export class RequestCluster extends SaferEmitter {
|
||||||
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
|
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
exposesOriginWhere(): DataLocation[] {
|
exposesOrigin() {
|
||||||
return this.requests
|
|
||||||
.map((request) => request.exposesOriginWhere())
|
|
||||||
.filter((l) => l !== null) as DataLocation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
exposesOrigin(): boolean {
|
|
||||||
return this.requests.some((request) => request.exposesOrigin());
|
return this.requests.some((request) => request.exposesOrigin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,32 +178,8 @@ export class RequestCluster extends SaferEmitter {
|
||||||
|
|
||||||
undoMark() {
|
undoMark() {
|
||||||
this.calculateRepresentativeStolenData();
|
this.calculateRepresentativeStolenData();
|
||||||
this.requests.forEach((request) => request.unmarkAllEntries());
|
this.representativeStolenData.forEach((entry) => {
|
||||||
}
|
entry.unmark();
|
||||||
|
});
|
||||||
getDataTypeDescription(noun = 'Twojej') {
|
|
||||||
let types_of_data: string[] = [];
|
|
||||||
if (this.exposesOrigin()) {
|
|
||||||
types_of_data.push(`część ${noun} historii przeglądania`);
|
|
||||||
}
|
|
||||||
if (this.hasMarkedCookies()) {
|
|
||||||
types_of_data.push('unikalne ID z cookies');
|
|
||||||
}
|
|
||||||
if (types_of_data.length > 1) {
|
|
||||||
types_of_data[types_of_data.length - 1] =
|
|
||||||
'oraz ' + types_of_data[types_of_data.length - 1];
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ export class SaferEmitter extends EventEmitter {
|
||||||
Reflect.apply(listener, this, args);
|
Reflect.apply(listener, this, args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
debugger;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
import ExtendedRequest, { HAREntry } from './extended-request';
|
import ExtendedRequest, { HAREntry } from './extended-request';
|
||||||
import { SaferEmitter } from './safer-emitter';
|
import { SaferEmitter } from './safer-emitter';
|
||||||
|
|
||||||
|
@ -33,12 +34,6 @@ const id = (function* id() {
|
||||||
|
|
||||||
export type DecodingSchema = 'base64' | 'raw';
|
export type DecodingSchema = 'base64' | 'raw';
|
||||||
|
|
||||||
export type DataLocation = {
|
|
||||||
path: string;
|
|
||||||
source: Sources;
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class StolenDataEntry extends SaferEmitter {
|
export class StolenDataEntry extends SaferEmitter {
|
||||||
public isIAB = false;
|
public isIAB = false;
|
||||||
public id: number;
|
public id: number;
|
||||||
|
@ -64,10 +59,11 @@ export class StolenDataEntry extends SaferEmitter {
|
||||||
getPriority() {
|
getPriority() {
|
||||||
let priority = 0;
|
let priority = 0;
|
||||||
priority += Math.min(this.value.length, 50);
|
priority += Math.min(this.value.length, 50);
|
||||||
if (this.value.includes(this.request.originalHost)) {
|
const url = new URL(this.request.originalURL);
|
||||||
|
if (this.value.includes(url.host)) {
|
||||||
priority += 100;
|
priority += 100;
|
||||||
}
|
}
|
||||||
if (this.request.originalPathname && this.value.includes(this.request.originalPathname)) {
|
if (this.value.includes(url.pathname)) {
|
||||||
priority += 100;
|
priority += 100;
|
||||||
}
|
}
|
||||||
if (this.source === 'cookie') {
|
if (this.source === 'cookie') {
|
||||||
|
@ -137,7 +133,7 @@ export class StolenDataEntry extends SaferEmitter {
|
||||||
} else if (value === null) {
|
} else if (value === null) {
|
||||||
return 'null';
|
return 'null';
|
||||||
} else {
|
} else {
|
||||||
return (value as any).toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,14 +238,10 @@ export class StolenDataEntry extends SaferEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
exposesPath() {
|
exposesPath() {
|
||||||
const pathname = this.request.originalPathname;
|
|
||||||
if (pathname === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
this.request.originalPathname !== '/' &&
|
this.request.originalPathname !== '/' &&
|
||||||
[this.value, safeDecodeURIComponent(this.value)].some((haystack) =>
|
[this.value, safeDecodeURIComponent(this.value)].some((haystack) =>
|
||||||
haystack.includes(pathname)
|
haystack.includes(this.request.originalPathname)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -259,12 +251,4 @@ export class StolenDataEntry extends SaferEmitter {
|
||||||
haystack.includes(getshorthost(this.request.origin))
|
haystack.includes(getshorthost(this.request.origin))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toDataLocation(): DataLocation {
|
|
||||||
return {
|
|
||||||
path: this.request.url,
|
|
||||||
source: this.source,
|
|
||||||
key: this.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
15
test.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import ExtendedRequest from "./extended-request";
|
||||||
|
import { StolenDataEntry } from "./stolen-data-entry";
|
||||||
|
import { flattenObject, maskString } from "./util";
|
||||||
|
|
||||||
|
console.log(flattenObject({ a: { b: { c: [1, 2, 3] } } }));
|
||||||
|
|
||||||
|
console.log(maskString("abcdefghijklmnopqrstuvwxyz", 1 / 3, 5));
|
||||||
|
|
||||||
|
console.log(maskString("abcdefghijklmnopqrstuvwxyz", 1, 30));
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
StolenDataEntry.parseValue(
|
||||||
|
`[{"@context":"https://schema.org/","@type":"Product","image":["/medias/sys_master/root/images/h95/h8b/10873724928030/oppo-reno6-black-front.png","/medias/sys_master/root/images/h15/hb9/10873725681694/oppo-reno6-black-side.png","/medias/sys_master/root/images/hf8/h81/10873728729118/oppo-reno6-black-back.png"],"description":"OPPO Reno6 5G bez umowy lub w abonamencie w sklepie Orange. Zamów do domu lub odbierz w salonie w 24h. Transport gratis!","sku":"1100027218","brand":{"@type":"Thing","name":"OPPO"},"offers":{"@type":"Offer","priceCurrency":"PLN","priceValidUntil":"2099-12-31T00:00:00","itemCondition":"https://schema.org/UsedCondition","availability":"https://schema.org/InStock","url":"/esklep/smartfony/oppo/oppo-reno6-5g","seller":{"@type":"Organization","name":"Orange Polska S.A"},"price":"2199"},"name":"OPPO Reno6 5G"},{"@context":"http://schema.org/","@type":"Product","name":"OPPO Reno6 5G","description":"null","offers":{"@type":"Offer","priceCurrency":"PLN","price":"1248.00"}}]`
|
||||||
|
)
|
||||||
|
);
|
|
@ -8,9 +8,6 @@
|
||||||
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"],
|
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"],
|
||||||
"target": "es2019",
|
"target": "es2019",
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true
|
||||||
"strictNullChecks": true,
|
|
||||||
"strict": true,
|
|
||||||
"alwaysStrict": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
util.ts
|
@ -1,6 +1,5 @@
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DataLocation, Sources } from './stolen-data-entry';
|
|
||||||
|
|
||||||
export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
|
export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
|
||||||
export type Unarray<T> = T extends Array<infer R> ? R : T;
|
export type Unarray<T> = T extends Array<infer R> ? R : T;
|
||||||
|
@ -37,11 +36,7 @@ export function getshorthost(host: string) {
|
||||||
.replace(/^.*:\/\//, '')
|
.replace(/^.*:\/\//, '')
|
||||||
.replace(/\/.*$/, '')
|
.replace(/\/.*$/, '')
|
||||||
.split('.');
|
.split('.');
|
||||||
const second_last = parts.at(-2);
|
let lookback = !['co', 'com'].includes(parts.at(-2)) ? -2 : -3;
|
||||||
if (!second_last) {
|
|
||||||
throw new Error('url too short?');
|
|
||||||
}
|
|
||||||
let lookback = !['co', 'com'].includes(second_last) ? -2 : -3;
|
|
||||||
if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') {
|
if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') {
|
||||||
lookback = -4; // to distinguish between google ads and stats
|
lookback = -4; // to distinguish between google ads and stats
|
||||||
} else if (parts.at(-2) == 'google') {
|
} else if (parts.at(-2) == 'google') {
|
||||||
|
@ -78,7 +73,7 @@ export function useEmitter(
|
||||||
export function parseCookie(cookie: string): Record<string, string> {
|
export function parseCookie(cookie: string): Record<string, string> {
|
||||||
return cookie
|
return cookie
|
||||||
.split(';')
|
.split(';')
|
||||||
.map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)])
|
.map((l) => l.split('='))
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, [key, value]) => ({
|
(acc, [key, value]) => ({
|
||||||
...acc,
|
...acc,
|
||||||
|
@ -94,7 +89,7 @@ export async function getTabByID(id: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseToObject(str: unknown): Record<string | symbol, unknown> {
|
export function parseToObject(str: unknown): Record<string | symbol, unknown> {
|
||||||
let result: Record<string | symbol, unknown> = {};
|
let result: Record<string | symbol, unknown>;
|
||||||
let original_string: string;
|
let original_string: string;
|
||||||
if (typeof str === 'string') {
|
if (typeof str === 'string') {
|
||||||
original_string = str;
|
original_string = str;
|
||||||
|
@ -102,8 +97,6 @@ export function parseToObject(str: unknown): Record<string | symbol, unknown> {
|
||||||
} else if (typeof str == 'object') {
|
} else if (typeof str == 'object') {
|
||||||
result = str as Record<string | symbol, unknown>;
|
result = str as Record<string | symbol, unknown>;
|
||||||
original_string = (result[Symbol.for('originalString')] as string) || JSON.stringify(str);
|
original_string = (result[Symbol.for('originalString')] as string) || JSON.stringify(str);
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
result[Symbol.for('originalString')] = original_string;
|
result[Symbol.for('originalString')] = original_string;
|
||||||
return result;
|
return result;
|
||||||
|
@ -156,13 +149,9 @@ export function getDate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toBase64(file: File): Promise<string> {
|
export function toBase64(file: File): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
const FR = new FileReader();
|
const FR = new FileReader();
|
||||||
FR.addEventListener('load', (e) => {
|
FR.addEventListener('load', (e) => {
|
||||||
const target = e.target;
|
|
||||||
if (!target) {
|
|
||||||
return reject('File missing?');
|
|
||||||
}
|
|
||||||
resolve(e.target.result as string);
|
resolve(e.target.result as string);
|
||||||
});
|
});
|
||||||
FR.readAsDataURL(file);
|
FR.readAsDataURL(file);
|
||||||
|
@ -210,8 +199,7 @@ export function isBase64JSON(s: unknown): s is string {
|
||||||
|
|
||||||
export function flattenObject(
|
export function flattenObject(
|
||||||
obj: unknown,
|
obj: unknown,
|
||||||
parser: (to_parse: { toString: () => string }) => string | Record<string, unknown> = (id) =>
|
parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => id.toString(),
|
||||||
id.toString(),
|
|
||||||
key = '',
|
key = '',
|
||||||
ret = [] as [string, string][],
|
ret = [] as [string, string][],
|
||||||
parsed = false
|
parsed = false
|
||||||
|
@ -232,12 +220,7 @@ export function flattenObject(
|
||||||
flattenObject(value, parser, prefix + subkey, ret);
|
flattenObject(value, parser, prefix + subkey, ret);
|
||||||
}
|
}
|
||||||
} else if (!parsed) {
|
} else if (!parsed) {
|
||||||
try {
|
flattenObject(parser(obj), parser, key, ret, true);
|
||||||
flattenObject(parser(obj as { toString: () => string }), parser, key, ret, true);
|
|
||||||
} catch (e) {
|
|
||||||
//emergency case, mostly for just type safety
|
|
||||||
ret.push([key, JSON.stringify(obj)]);
|
|
||||||
}
|
|
||||||
} else if (typeof obj === 'string') {
|
} else if (typeof obj === 'string') {
|
||||||
ret.push([key, obj]);
|
ret.push([key, obj]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -248,8 +231,7 @@ export function flattenObject(
|
||||||
|
|
||||||
export function flattenObjectEntries(
|
export function flattenObjectEntries(
|
||||||
entries: [string, unknown][],
|
entries: [string, unknown][],
|
||||||
parser: (to_parse: { toString: () => string }) => string | Record<string, unknown> = (id) =>
|
parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => id.toString()
|
||||||
id.toString()
|
|
||||||
): [string, string][] {
|
): [string, string][] {
|
||||||
return flattenObject(Object.fromEntries(entries), parser);
|
return flattenObject(Object.fromEntries(entries), parser);
|
||||||
}
|
}
|
||||||
|
@ -284,37 +266,8 @@ export function normalizeForClassname(string: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wordlist(words: string[]) {
|
export function wordlist(words: string[]) {
|
||||||
return Array.from(new Set(words)).reduce(
|
return words.reduce(
|
||||||
(acc, word, i) =>
|
(acc, word, i) => `${acc}${i > 0 ? (i < words.length - 1 ? ',' : ' i') : ''} ${word}`,
|
||||||
`${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);
|
|
||||||
}
|
|
||||||
|
|