Compare commits
No commits in common. "5cf640e6865e1e6960a5319a5fa3f05701dce31d" and "7330ebf9f26bd4e8219ba78089bf9b496e418955" have entirely different histories.
5cf640e686
...
7330ebf9f2
40
report-window/consent-problems.tsx
Normal file
40
report-window/consent-problems.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* import { EmailTemplate3Config } from './email-template-3';
|
||||||
|
* import hostSettingsRadio from './host-settings-radio';
|
||||||
|
*
|
||||||
|
* export default function ConsentProblems({
|
||||||
|
* settings,
|
||||||
|
* host_id,
|
||||||
|
* setConfig,
|
||||||
|
* pronoun,
|
||||||
|
* }: {
|
||||||
|
* host_id: string;
|
||||||
|
* setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
|
||||||
|
* settings: EmailTemplate3Config['hosts_settings'][string];
|
||||||
|
* pronoun: 0 | 1 | 2 | 3;
|
||||||
|
* }) {
|
||||||
|
* if (settings.legal_basis_type !== 'consent') {
|
||||||
|
* return '';
|
||||||
|
* }
|
||||||
|
* const p = pronoun;
|
||||||
|
* return (
|
||||||
|
* <div>
|
||||||
|
* {hostSettingsRadio({
|
||||||
|
* host_id,
|
||||||
|
* setConfig,
|
||||||
|
* field: 'consent_problems' as const,
|
||||||
|
* value: settings.consent_problems,
|
||||||
|
* options: {
|
||||||
|
* claims_consent_but_sends_before_consent: `Strona wysłała
|
||||||
|
* ${p == 3 ? 'nasze' : 'moje'} dane do ${host_id} zanim
|
||||||
|
* ${['wyraziłem', 'wyraziłam', 'wyraziłom', 'wyraziliśmy'][p]} na to zgodę.`,
|
||||||
|
* claims_consent_but_there_was_no_easy_refuse: `${
|
||||||
|
* ['Kliknąłem', 'Kliknęłam', 'Kliknęłom', 'Kliknęliśmy'][p]
|
||||||
|
* }
|
||||||
|
* przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo
|
||||||
|
* dostępnego przycisku do niewyrażenia zgody jednym kliknięciem.`,
|
||||||
|
* none: 'Żadne z powyższych.',
|
||||||
|
* },
|
||||||
|
* })}
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
* } */
|
69
report-window/data-preview.tsx
Normal file
69
report-window/data-preview.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Classifications, StolenDataEntry } from '../stolen-data-entry';
|
||||||
|
|
||||||
|
export function DataPreview({
|
||||||
|
entries,
|
||||||
|
refresh,
|
||||||
|
}: {
|
||||||
|
entries: StolenDataEntry[];
|
||||||
|
refresh: () => void;
|
||||||
|
}) {
|
||||||
|
// currently not used, maybe scraped entirely in the future
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Adres docelowy</th>
|
||||||
|
<th>Źródło danych</th>
|
||||||
|
<th>Treść danych</th>
|
||||||
|
<th>Klasyfikacja</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{entries.map((entry) => (
|
||||||
|
<tr
|
||||||
|
key={entry.id}
|
||||||
|
style={{
|
||||||
|
backgroundColor: entry.classification == 'id' ? 'yellow' : 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td>{entry.request.shorthost}</td>
|
||||||
|
<td style={{ overflowWrap: 'anywhere' }}>
|
||||||
|
{entry.source}:{entry.name}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
width: '400px',
|
||||||
|
overflowWrap: 'anywhere',
|
||||||
|
backgroundColor: entry.isRelatedToID() ? '#ffff0054' : 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entry.getValuePreview()}
|
||||||
|
{/* always gonna have
|
||||||
|
one key, because unwrapEntry is called above */}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
value={entry.classification}
|
||||||
|
onChange={(e) => {
|
||||||
|
entry.classification = e.target
|
||||||
|
.value as keyof typeof Classifications;
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
['history', 'Historia przeglądania'],
|
||||||
|
['id', 'Identyfikator internetowy'],
|
||||||
|
['location', 'Lokalizacja'],
|
||||||
|
].map(([key, name]) => (
|
||||||
|
<option key={key} value={key}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
@ -1,143 +0,0 @@
|
|||||||
import { RequestCluster } from '../request-cluster';
|
|
||||||
import { ExplainerKey } from './explainers';
|
|
||||||
import { ParsedAnswers } from './parse-answers';
|
|
||||||
import { v } from './verbs';
|
|
||||||
|
|
||||||
abstract class Problem {
|
|
||||||
constructor(public answers: ParsedAnswers, public clusters: Record<string, RequestCluster>) {}
|
|
||||||
|
|
||||||
getMarkedClusters() {
|
|
||||||
return Object.values(this.clusters).filter((c) => c.hasMarks());
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getEmailContent(): JSX.Element;
|
|
||||||
abstract getNecessaryExplainers(): ExplainerKey[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatRange(cluster: RequestCluster) {
|
|
||||||
const parts = [] as string[];
|
|
||||||
console.log(cluster);
|
|
||||||
if (cluster.hasMarkedCookies()) {
|
|
||||||
parts.push('mojego identyfikatora internetowego pozyskanego z Cookie');
|
|
||||||
}
|
|
||||||
if (cluster.exposesOrigin()) {
|
|
||||||
parts.push('części mojej historii przeglądania');
|
|
||||||
}
|
|
||||||
return parts.join(' oraz ');
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoInformationAtAllProblem extends Problem {
|
|
||||||
getEmailContent() {
|
|
||||||
const _ = (word: string) => v(word, this.answers.zaimek);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Brak informacji na temat przetwarzania danych osobowych</h2>
|
|
||||||
<p>
|
|
||||||
{_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami
|
|
||||||
domen:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{this.getMarkedClusters().map((cluster) => (
|
|
||||||
<li key={cluster.id}>
|
|
||||||
{cluster.id} (w zakresie: {formatRange(cluster)})
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie są cele
|
|
||||||
przetwarzania takich danych oraz jakie są podstawy prawne takiego przetwarzania.
|
|
||||||
</p>
|
|
||||||
<p>Zwracam się zatem do Państwa z następującymi pytaniami:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Jaka jest tożsamość właścicieli tych domen?</li>
|
|
||||||
<li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li>
|
|
||||||
<li>
|
|
||||||
Jaka jest podstawa prawna takiego przetwarzania moich danych osobowych przez
|
|
||||||
Państwa stronę?
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
getNecessaryExplainers() {
|
|
||||||
const explainers = [] as Array<ExplainerKey>;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.getMarkedClusters().some((cluster) => {
|
|
||||||
console.log(cluster);
|
|
||||||
return cluster.hasMarkedCookies();
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
explainers.push('cookies_are_pii');
|
|
||||||
}
|
|
||||||
return explainers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UnlawfulCookieAccess extends Problem {
|
|
||||||
getNecessaryExplainers(): ExplainerKey[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
getEmailContent() {
|
|
||||||
const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2>
|
|
||||||
<p>
|
|
||||||
Państwa strona dokonała odczytu plików Cookie zapisanych na dysku twardym mojego
|
|
||||||
komputera. Dotyczy to plików cookie przypisanych do domen:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{cookie_clusters.map((cluster, index) => {
|
|
||||||
const names = cluster
|
|
||||||
.getMarkedEntries()
|
|
||||||
.filter((e) => e.source === 'cookie')
|
|
||||||
.map((e) => e.name);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li>
|
|
||||||
{cluster.id} ({names.length > 1 ? 'pliki' : 'plik'}{' '}
|
|
||||||
{names.map((name, index) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{index > 0 ? ', ' : ''}
|
|
||||||
{name}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
){index === cookie_clusters.length - 1 ? '.' : ';'}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {
|
|
||||||
// są cookiesy, nie było zgody, nie są konieczne do działania strony
|
|
||||||
const cookie_clusters = Object.values(clusters).filter((c) => c.hasMarkedCookies());
|
|
||||||
return cookie_clusters.some((cluster) => {
|
|
||||||
const hostAnswers = answers.hosts[cluster.id];
|
|
||||||
return (
|
|
||||||
(hostAnswers.present == 'not_mentioned' ||
|
|
||||||
hostAnswers.present == 'not_before_making_a_choice' ||
|
|
||||||
['none', 'closed_popup', 'deny_all'].includes(hostAnswers.popup_action)) &&
|
|
||||||
hostAnswers.was_processing_necessary != 'yes'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function deduceProblems(
|
|
||||||
answers: ParsedAnswers,
|
|
||||||
clusters: Record<string, RequestCluster>
|
|
||||||
): Problem[] {
|
|
||||||
const problems = [];
|
|
||||||
if (answers.popup_type === 'none') {
|
|
||||||
problems.push(new NoInformationAtAllProblem(answers, clusters));
|
|
||||||
}
|
|
||||||
if (UnlawfulCookieAccess.qualifies(answers, Object.values(clusters))) {
|
|
||||||
problems.push(new UnlawfulCookieAccess(answers, clusters));
|
|
||||||
}
|
|
||||||
return problems;
|
|
||||||
}
|
|
42
report-window/domain-summary.tsx
Normal file
42
report-window/domain-summary.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RequestCluster } from '../request-cluster';
|
||||||
|
import { Classifications, Sources } from '../stolen-data-entry';
|
||||||
|
|
||||||
|
const emailClassifications: Record<keyof typeof Classifications, string> = {
|
||||||
|
id: 'mój identyfikator internetowy',
|
||||||
|
history: 'część mojej historii przeglądania',
|
||||||
|
location: 'informacje na temat mojej lokalizacji geograficznej',
|
||||||
|
};
|
||||||
|
|
||||||
|
const emailSources: Record<Sources, string> = {
|
||||||
|
header: 'w nagłówku HTTP',
|
||||||
|
cookie: 'z pliku Cookie',
|
||||||
|
pathname: 'jako części adresu URL',
|
||||||
|
queryparams: 'jako część adresu URL (query-params)',
|
||||||
|
request_body: 'w body zapytania POST',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DomainSummary({
|
||||||
|
cluster,
|
||||||
|
}: {
|
||||||
|
cluster: RequestCluster;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Właścicielowi domeny <strong>{cluster.id}</strong> zostały
|
||||||
|
ujawnione:{' '}
|
||||||
|
<ul>
|
||||||
|
{cluster.representativeStolenData
|
||||||
|
.filter((entry) => entry.isMarked)
|
||||||
|
.map((entry) => (
|
||||||
|
<li key={entry.id}>
|
||||||
|
{emailClassifications[entry.classification]}{' '}
|
||||||
|
{emailSources[entry.source]} (nazwa:{' '}
|
||||||
|
<code>{entry.name}</code>, wartość:{' '}
|
||||||
|
<code>{entry.getValuePreview()}</code>)
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
import { RequestCluster } from '../request-cluster';
|
|
||||||
import { getDate } from '../util';
|
|
||||||
import deduceProblems from './deduce-problems';
|
|
||||||
import { Explainers } from './explainers';
|
|
||||||
import { ParsedAnswers } from './parse-answers';
|
|
||||||
import { v } from './verbs';
|
|
||||||
|
|
||||||
declare var PLUGIN_NAME: string;
|
|
||||||
declare var PLUGIN_URL: string;
|
|
||||||
|
|
||||||
export default function EmailContent({
|
|
||||||
answers,
|
|
||||||
visited_url,
|
|
||||||
clusters,
|
|
||||||
}: {
|
|
||||||
answers: ParsedAnswers;
|
|
||||||
visited_url: string;
|
|
||||||
clusters: Record<string, RequestCluster>;
|
|
||||||
}) {
|
|
||||||
const _ = (key: string) => v(key, answers.zaimek);
|
|
||||||
const problems = deduceProblems(answers, clusters);
|
|
||||||
const explainers = Array.from(
|
|
||||||
new Set(
|
|
||||||
problems
|
|
||||||
.map((problem) => problem.getNecessaryExplainers())
|
|
||||||
.reduce((a, b) => a.concat(b), [])
|
|
||||||
)
|
|
||||||
).map((explainer_key) => Explainers[explainer_key]);
|
|
||||||
return (
|
|
||||||
<div style={{ padding: '1rem' }}>
|
|
||||||
<pre>{JSON.stringify(answers, null, 3)}</pre>
|
|
||||||
<p>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 {_('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))}
|
|
||||||
<p>
|
|
||||||
{_('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">
|
|
||||||
treści wyroku TSUE w sprawie C-40/17
|
|
||||||
</a>{' '}
|
|
||||||
poprzez wysyłanie moich danych w wyżej opisanym zakresie stają się Państwo
|
|
||||||
współadministratorem moich danych osobowych, dlatego ciąży na Państwu obowiązek
|
|
||||||
odpowiedzi na moje pytanie na mocy Art. 12 i 13 Rozporządzenia 2016/679 Parlamentu
|
|
||||||
Europejskiego i Rady (UE) z dnia 27 kwietnia 2016 r. w sprawie ochrony osób
|
|
||||||
fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego
|
|
||||||
przepływu takich danych oraz uchylenia dyrektywy 95/46/WE (ogólne rozporządzenie o
|
|
||||||
ochronie danych, dalej: „RODO”).
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
2
report-window/email-host-settings.scss
Normal file
2
report-window/email-host-settings.scss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@import '../sidebar/fonts.scss';
|
||||||
|
@import '../sidebar/colors.scss';
|
67
report-window/email-host-settings.tsx
Normal file
67
report-window/email-host-settings.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { EmailTemplate3Config } from './email-template-3';
|
||||||
|
/* import hostSettingsDropdown from './host-setting-dropdown'; */
|
||||||
|
/* import ConsentProblems from './consent-problems'; */
|
||||||
|
import LegitimateInteresProblems from './legitimate-interest-problems';
|
||||||
|
/* import { hostNeedsQuestions } from './host-needs-questions'; */
|
||||||
|
|
||||||
|
export function setHostSetting<
|
||||||
|
P1 extends keyof EmailTemplate3Config['hosts_settings'],
|
||||||
|
P2 extends keyof EmailTemplate3Config['hosts_settings'][P1]
|
||||||
|
>(
|
||||||
|
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>,
|
||||||
|
[p1, p2]: [P1, P2],
|
||||||
|
value: EmailTemplate3Config['hosts_settings'][P1][P2]
|
||||||
|
) {
|
||||||
|
setConfig((v) => {
|
||||||
|
console.log(v, {
|
||||||
|
...v,
|
||||||
|
hosts_settings: {
|
||||||
|
...v.hosts_settings,
|
||||||
|
[p1]: {
|
||||||
|
...v.hosts_settings[p2],
|
||||||
|
[p2]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...v,
|
||||||
|
hosts_settings: {
|
||||||
|
...v.hosts_settings,
|
||||||
|
[p1]: {
|
||||||
|
...v.hosts_settings[p1],
|
||||||
|
[p2]: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function emailHostSettings(
|
||||||
|
config: EmailTemplate3Config,
|
||||||
|
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>
|
||||||
|
) {
|
||||||
|
if (config.policy_readable == 'null') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const p = config.pronoun;
|
||||||
|
return (
|
||||||
|
<aside>
|
||||||
|
{Object.entries(config.hosts_settings).map(([host_id, settings]) => (
|
||||||
|
<div key={host_id}>
|
||||||
|
{!['not_mentioned', 'null'].includes(settings.legal_basis_type) ? (
|
||||||
|
<div>
|
||||||
|
{ConsentProblems({ settings, host_id, pronoun: p, setConfig })}
|
||||||
|
{LegitimateInteresProblems({
|
||||||
|
settings,
|
||||||
|
host_id,
|
||||||
|
setConfig,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
486
report-window/email-template-2.tsx
Normal file
486
report-window/email-template-2.tsx
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RequestCluster } from '../request-cluster';
|
||||||
|
import { StolenDataEntry } from '../stolen-data-entry';
|
||||||
|
import { getDate, unique } from '../util';
|
||||||
|
import DomainSummary from './domain-summary';
|
||||||
|
import EmailTemplate2Controls from './email-template-2-controls';
|
||||||
|
|
||||||
|
export type EmailTemplate2Config = {
|
||||||
|
popup_type: 'none' | 'passive_cookie_banner' | 'consent';
|
||||||
|
popup_action: 'ignored' | 'accepted' | 'closed';
|
||||||
|
popup_closed_how: string;
|
||||||
|
popup_screenshot_base64: string | null;
|
||||||
|
popup_accept_all_text: string;
|
||||||
|
popup_mentions_passive_consent: boolean;
|
||||||
|
popup_passive_consent_text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ClusterRangeSummary({ cluster }: { cluster: RequestCluster }) {
|
||||||
|
const range = unique(
|
||||||
|
cluster.getMarkedEntries().map((entry) => entry.classification)
|
||||||
|
);
|
||||||
|
const has_cookie_ids = cluster
|
||||||
|
.getMarkedEntries()
|
||||||
|
.filter((entry) => entry.source === 'cookie')
|
||||||
|
.some((entry) => entry.classification == 'id');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[
|
||||||
|
range.includes('id')
|
||||||
|
? 'Pańskiego identyfikatora internetowego' +
|
||||||
|
(has_cookie_ids ? ' z cookie' : '')
|
||||||
|
: '',
|
||||||
|
range.includes('history')
|
||||||
|
? 'części Pańskiej historii przeglądania'
|
||||||
|
: '',
|
||||||
|
range.includes('location')
|
||||||
|
? 'informacji na temat Pana położenia'
|
||||||
|
: '',
|
||||||
|
]
|
||||||
|
.filter((e) => e !== '')
|
||||||
|
.join(', ')}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Placeholder({ children }: { children: string }) {
|
||||||
|
const invisbleCharacter = ' ';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
textDecoration: 'underline',
|
||||||
|
fontSize: '0.8em',
|
||||||
|
position: 'relative',
|
||||||
|
textUnderlineOffset: '4px',
|
||||||
|
bottom: '3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{invisbleCharacter.repeat(6)}</span>
|
||||||
|
<span style={{ color: 'gray' }}>({children})</span>
|
||||||
|
<span>{invisbleCharacter.repeat(6)}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Base64Image({ base64 }: { base64: string }) {
|
||||||
|
return <img style={{ maxWidth: '100%' }} {...{ src: base64 }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EmailTemplate2({
|
||||||
|
entries,
|
||||||
|
clusters,
|
||||||
|
}: {
|
||||||
|
entries: StolenDataEntry[];
|
||||||
|
clusters: Record<string, RequestCluster>;
|
||||||
|
version: number;
|
||||||
|
}): JSX.Element {
|
||||||
|
const [config, setConfig] = React.useState<EmailTemplate2Config>({
|
||||||
|
popup_type: 'none',
|
||||||
|
popup_action: 'ignored',
|
||||||
|
popup_screenshot_base64: null,
|
||||||
|
popup_accept_all_text: 'Zaakceptuj wszystkie',
|
||||||
|
popup_mentions_passive_consent: false,
|
||||||
|
popup_passive_consent_text: '',
|
||||||
|
popup_closed_how: 'kliknięcie przycisku „X”',
|
||||||
|
});
|
||||||
|
|
||||||
|
const visited_url = entries[0].request.originalURL;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EmailTemplate2Controls {...{ config, setConfig }} />
|
||||||
|
<article
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 20px 40px rgba(0,0,0,.2)',
|
||||||
|
padding: '4rem 3rem',
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
margin: '2rem 0',
|
||||||
|
color: 'hsl(240, 5.7%, 15.8%);',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Dzień dobry, w dniu {getDate()} odwiedziłem stronę{' '}
|
||||||
|
{visited_url}.
|
||||||
|
</p>
|
||||||
|
{config.popup_type === 'none' ? (
|
||||||
|
<p>
|
||||||
|
Nie ukazał mi się na stronie żaden mechanizm pozyskujący
|
||||||
|
zgodę na przetwarzanie moich danych osobowych lub
|
||||||
|
umożliwiający mi wyrażenie sprzeciwu wobec przetwarzania
|
||||||
|
przez stronę moich danych osobowych w zakresie
|
||||||
|
wykraczającym poza procesy konieczne do wyświetlenia
|
||||||
|
strony
|
||||||
|
</p>
|
||||||
|
) : config.popup_type == 'passive_cookie_banner' ? (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Na stronie była widoczna informacja o plikach
|
||||||
|
Cookie.{' '}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Base64Image
|
||||||
|
{...{ base64: config.popup_screenshot_base64 }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Ukazało mi się okienko z informacjami i pytaniami
|
||||||
|
dotyczącymi sposobów, w jaki strona przetwarza moje
|
||||||
|
dane osobowe.{' '}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Base64Image
|
||||||
|
{...{ base64: config.popup_screenshot_base64 }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{config.popup_action === 'ignored'
|
||||||
|
? /* HTML */ `Nie kliknąłem żadnego przycisku w
|
||||||
|
tym okienku. W szczególności nie kliknąłem
|
||||||
|
przycisku „${config.popup_accept_all_text}”.`
|
||||||
|
: config.popup_action === 'accepted'
|
||||||
|
? `Kliknąłem na widoczną w tym okienku opcję „${config.popup_accept_all_text}”.`
|
||||||
|
: ''}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
W tym samym czasie rejestrowałem ruch sieciowy generowany
|
||||||
|
przez tę stronę za pomocą narzędzi w przeglądarce Firefox.
|
||||||
|
Okazało się, że Państwa strona wysłała moje dane osobowe do
|
||||||
|
następujących podmiotów:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{Object.values(clusters)
|
||||||
|
.filter((cluster) => cluster.hasMarks())
|
||||||
|
.map((cluster) => (
|
||||||
|
<DomainSummary cluster={cluster} key={cluster.id} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{config.popup_action === 'ignored' ? (
|
||||||
|
<p>
|
||||||
|
Dane te zostały wysłane, zanim kliknąłem cokolwiek na
|
||||||
|
tej stronie.
|
||||||
|
</p>
|
||||||
|
) : config.popup_action === 'accepted' ? (
|
||||||
|
<p>
|
||||||
|
Dane te zostały wysłane po tym, jak kliknąłem przycisk „
|
||||||
|
{config.popup_accept_all_text}”
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
W załączeniu przesyłam część zrzutów ekranu dokumentujących
|
||||||
|
fakt wysłania tych danych przez Państwa stronę.{' '}
|
||||||
|
</p>
|
||||||
|
<h3>Podstawa prawna</h3>
|
||||||
|
<p>
|
||||||
|
Ustawa Prawo Telekomunikacyjne w art. 173 reguluje warunki,
|
||||||
|
które musi spełnić administrator strony, aby jego strona
|
||||||
|
mogła zapisywać i czytać treść plików cookie. Nie reguluje
|
||||||
|
jednak tego, jakim podmiotom i w jakim zakresie dane mogą
|
||||||
|
być <em>ujawniane</em> przez stronę. Tym zajmuje się
|
||||||
|
Rozporządzenie 2016/679 Parlamentu Europejskiego i Rady (UE)
|
||||||
|
z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych
|
||||||
|
w związku z 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.
|
||||||
|
Zapis/odczyt plików cookie a ujawnianie ich treści podmiotom
|
||||||
|
trzecim to dwa różne procesy. Niniejsza wiadomość i pytania
|
||||||
|
w niej zawarte dotyczą właśnie <em>ujawniania</em> moich
|
||||||
|
danych osobowych (pochodzących m.in. z Cookies) podmiotom
|
||||||
|
trzecim.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
W kontekście stron internetowych są właściwie dopuszczalne
|
||||||
|
tylko trzy z sześciu wymienionych w Art. 6 pkt 1 RODO
|
||||||
|
podstaw prawnych dla przetwarzania danych osobowych:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
„Zgoda” — osoba, której dane dotyczą wyraziła
|
||||||
|
zgodę na przetwarzanie swoich danych osobowych w jednym
|
||||||
|
lub większej liczbie określonych celów (
|
||||||
|
<em>Art. 6 pkt 1 lit. a)</em>).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
„Niezbędność” — przetwarzanie jest niezbędne do
|
||||||
|
wykonania umowy, której stroną jest osoba, której dane
|
||||||
|
dotyczą, lub do podjęcia działań na żądanie osoby,
|
||||||
|
której dane dotyczą, przed zawarciem umowy (
|
||||||
|
<em>Art. 6 pkt 1 lit. b)</em>);{' '}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
„Uzasadniony Interes” — przetwarzanie jest
|
||||||
|
niezbędne do celów wynikających z prawnie uzasadnionych
|
||||||
|
interesów realizowanych przez administratora lub przez
|
||||||
|
stronę trzecią, z wyjątkiem sytuacji, w których
|
||||||
|
nadrzędny charakter wobec tych interesów mają interesy
|
||||||
|
lub podstawowe prawa i wolności osoby, której dane
|
||||||
|
dotyczą, wymagające ochrony danych osobowych, w
|
||||||
|
szczególności gdy osoba, której dane dotyczą, jest
|
||||||
|
dzieckiem (<em>Art. 6 pkt 1 lit. f)</em>
|
||||||
|
);
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<p>
|
||||||
|
W przypadku opisywanej przeze mnie mojej wizyty na Państwa
|
||||||
|
stronie nie ma zastosowania „Zgoda”, gdyż{' '}
|
||||||
|
{config.popup_action === 'ignored' ? (
|
||||||
|
<>
|
||||||
|
nie wyrażałem żadnej zgody na takie przetwarzanie
|
||||||
|
moich danych
|
||||||
|
{config.popup_type === 'consent' ? (
|
||||||
|
<>
|
||||||
|
— w szczególności nie kliknąłem
|
||||||
|
przycisku „{config.popup_accept_all_text}”
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
) : config.popup_action === 'accepted' ? (
|
||||||
|
<>
|
||||||
|
o ile po wejściu na stronę wcisnąłem w wyskakującym
|
||||||
|
okienku przycisk „{config.popup_accept_all_text}”, o
|
||||||
|
tyle nie stanowi to według mnie ważnej w świetle
|
||||||
|
RODO zgody, gdyż brakowało w tym okienku równie
|
||||||
|
łatwo osiągalnego przycisku, którego kliknięcie
|
||||||
|
skutkowałoby zasygnalizowaniem braku mojej zgody na
|
||||||
|
takie przetwarzanie moich danych. Mówiąc wprost
|
||||||
|
— wyrażenie „zgody” było łatwiejsze niż jej
|
||||||
|
niewyrażenie. Niewyrażenie zgody wiąże się z
|
||||||
|
negatywną konsekwencją konieczności przechodzenia
|
||||||
|
przez dodatkowe kroki w wyskakującym okienku. Zatem
|
||||||
|
tak otrzymana przez Państwo moja „zgoda” nie jest
|
||||||
|
poprawną podstawą prawną do przetwarzania moich
|
||||||
|
danych osobowych, gdyż nie spełnia warunku
|
||||||
|
dobrowolności wspomnianego w motywie (42) RODO.
|
||||||
|
</>
|
||||||
|
) : config.popup_action === 'closed' ? (
|
||||||
|
<>
|
||||||
|
zamknąłem okienko pytające o zgodę poprzez{' '}
|
||||||
|
{config.popup_closed_how}. Nie może być to uznane za
|
||||||
|
zgodę, bo nie spełnia to warunku jednoznaczności
|
||||||
|
opisanego w motywie (32) Rozporządzenia 2016/679.{' '}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}{' '}
|
||||||
|
Za zgodę nie można też uznać posiadania włączonej obsługi
|
||||||
|
cookies w przeglądarce (gdyż aby zgoda była ważna, musi być
|
||||||
|
szczegółowa dla każdego celów z osobna), jakichkolwiek
|
||||||
|
innych ustawień przeglądarki, ani pasywnych działań z mojej
|
||||||
|
strony (np. „kontynuowanie korzystania ze strony”)
|
||||||
|
{config.popup_mentions_passive_consent ? (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
— nieprawdą więc jest zawarty na Państwa
|
||||||
|
stronie komunikat „
|
||||||
|
{config.popup_passive_consent_text.trim()}” (por.
|
||||||
|
paragraf 97.{' '}
|
||||||
|
<a href="https://edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_pl.pdf">
|
||||||
|
oficjalnych wytycznych EROD dotyczących zgody na
|
||||||
|
mocy rozporządzenia 2016/679
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
W mojej ocenie „Niezbędność“ nie ma zastosowania co do
|
||||||
|
opisanych powyżej sposobów przetwarzania danych. Nie widzę,
|
||||||
|
co miałoby sprawiać, aby wysyłanie moich danych osobowych do
|
||||||
|
wspomnianych powyżej podmiotów trzecich było konieczne do
|
||||||
|
wyświetlenia Państwa strony na ekranie mojego komputera
|
||||||
|
(zob.{' '}
|
||||||
|
<a href="https://edpb.europa.eu/system/files/2021-11/edpb_guidelines_082020_on_the_targeting_of_social_media_users_pl_0.pdf">
|
||||||
|
Wytyczne 8/2020 EROD dotyczące targetowania użytkowników
|
||||||
|
mediów społecznościowych
|
||||||
|
</a>
|
||||||
|
, par. 49);.{' '}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Pozostaje zatem „Uzasadniony Interes”. Aby Administrator
|
||||||
|
mógł używać uzasadnionego interesu jako podstawy prawnej
|
||||||
|
targetowania użytkowników Sieci, muszą zostać spełnione
|
||||||
|
m.in. następujące warunki:{' '}
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
Administrator danych lub podmiot trzeci, któremu dane są
|
||||||
|
ujawniane musi{' '}
|
||||||
|
<strong>
|
||||||
|
faktycznie realizować dany konkretny uzasadniony
|
||||||
|
interes
|
||||||
|
</strong>{' '}
|
||||||
|
(
|
||||||
|
<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">
|
||||||
|
Wyrok TSUE z dnia 29 lipca 2019 r. w sprawie Fashion
|
||||||
|
ID, C-40/17, ECLI:EU:C:2019:629
|
||||||
|
</a>
|
||||||
|
, pkt 95.)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Takie przetwarzanie danych jest{' '}
|
||||||
|
<strong>konieczne</strong> dla potrzeb wynikających z
|
||||||
|
danego uzasadnionego interesu (
|
||||||
|
<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">
|
||||||
|
Wyrok TSUE z dnia 29 lipca 2019 r. w sprawie Fashion
|
||||||
|
ID, C-40/17, ECLI:EU:C:2019:629
|
||||||
|
</a>
|
||||||
|
, pkt 95.)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Wybrany uzasadniony interes musi mieć pierwszeństwo nad
|
||||||
|
prawami i wolnościami osoby, której dotyczą przetwarzane
|
||||||
|
dane (
|
||||||
|
<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">
|
||||||
|
Wyrok TSUE z dnia 29 lipca 2019 r. w sprawie Fashion
|
||||||
|
ID, C-40/17, ECLI:EU:C:2019:629
|
||||||
|
</a>
|
||||||
|
, pkt 95.)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Osoby, których dane dotyczą, powinny mieć możliwość
|
||||||
|
wyrażenia sprzeciwu wobec przetwarzania ich danych do
|
||||||
|
celów związanych z targetowaniem{' '}
|
||||||
|
<strong>przed rozpoczęciem przetwarzania</strong> (zob.{' '}
|
||||||
|
<a href="https://edpb.europa.eu/system/files/2021-11/edpb_guidelines_082020_on_the_targeting_of_social_media_users_pl_0.pdf">
|
||||||
|
Wytyczne 8/2020 EROD dotyczące targetowania
|
||||||
|
użytkowników mediów społecznościowych
|
||||||
|
</a>
|
||||||
|
, par. 54);
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
{config.popup_action !== 'accepted' ? (
|
||||||
|
<p>
|
||||||
|
Moje dane zostały ujawnione podmiotom trzecim tuż po
|
||||||
|
włączeniu strony, zatem nie jest spełniony warunek 4.
|
||||||
|
Apeluję o wdrożenie zmian na stronie, które sprawią, że
|
||||||
|
dopiero po świadomym niewyrażeniu sprzeciwu przez
|
||||||
|
użytkownika aktywowane są procesy przetwarzania danych
|
||||||
|
osobowych, których podstawą prawną jest uzasadniony
|
||||||
|
interes.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
Jeżeli istnieją jednak inne niż uzasadniony interes ważne
|
||||||
|
podstawy prawne do takiego przetwarzania moich danych
|
||||||
|
osobowych przez Państwa stronę, proszę o ich wskazanie,{' '}
|
||||||
|
<em>dla każdego z wymienionych podmiotów z osobna</em>.
|
||||||
|
(Przypominam, że Art. 173 ustawy Prawo Telekomunikacyjne nie
|
||||||
|
ma tutaj zastosowania, ponieważ nie pytam o zapis/odczyt
|
||||||
|
plików na moim komputerze, tylko o ujawnianie moich danych
|
||||||
|
osobowych podmiotom trzecim). W przeciwnym wypadku, aby
|
||||||
|
ustalić, czy moje dane były przez Państwa przetwarzane na
|
||||||
|
mocy uzasadnionego interesu zgodnie z prawem, proszę o
|
||||||
|
wypełnienie następującego szablonu (lub udzielenie tych
|
||||||
|
samych informacji w innej postaci, przy zachowaniu zakresu i
|
||||||
|
szczegółowości informacji:
|
||||||
|
</p>
|
||||||
|
<div style={{ border: '1px solid black', padding: '1rem' }}>
|
||||||
|
<p>
|
||||||
|
W dniu {getDate()} strona {visited_url}:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{Object.values(clusters)
|
||||||
|
.filter((cluster) => cluster.hasMarks())
|
||||||
|
.map((cluster) => (
|
||||||
|
<li
|
||||||
|
key={cluster.id}
|
||||||
|
style={{ paddingBottom: '1rem' }}
|
||||||
|
>
|
||||||
|
ujawniła pańskie dane w zakresie{' '}
|
||||||
|
<em>
|
||||||
|
<ClusterRangeSummary {...{ cluster }} />
|
||||||
|
</em>{' '}
|
||||||
|
firmie{' '}
|
||||||
|
<Placeholder>nazwa firmy</Placeholder>,
|
||||||
|
która jest właścicielem domeny{' '}
|
||||||
|
<strong>{cluster.id}</strong> i swoją
|
||||||
|
politykę prywatności publikuje pod adresem{' '}
|
||||||
|
<Placeholder>
|
||||||
|
adres URL polityki prywatności tej firmy
|
||||||
|
</Placeholder>
|
||||||
|
. Podstawą prawną takiego przetwarzania
|
||||||
|
danych przez naszą stronę jest uzasadniony
|
||||||
|
interes:{' '}
|
||||||
|
<Placeholder>
|
||||||
|
na czym polega ten uzasadniony interes,
|
||||||
|
tzn. bieżące działania podejmowane przez
|
||||||
|
podmiot realizujący ten interes lub
|
||||||
|
korzyści dla podmiotu realizującego ten
|
||||||
|
interes oczekiwane w bardzo bliskiej
|
||||||
|
przyszłości
|
||||||
|
</Placeholder>{' '}
|
||||||
|
realizowany przez{' '}
|
||||||
|
<Placeholder>
|
||||||
|
kogo? jaki podmiot podejmuje wspomniane
|
||||||
|
działania lub jest beneficjentem
|
||||||
|
wspomnianych korzyści?
|
||||||
|
</Placeholder>
|
||||||
|
. Ujawnienie{' '}
|
||||||
|
<ClusterRangeSummary {...{ cluster }} />{' '}
|
||||||
|
temu podmiotowi przez naszą stronę było
|
||||||
|
konieczne dla potrzeb wynikających z tego
|
||||||
|
interesu, ponieważ
|
||||||
|
<Placeholder>
|
||||||
|
uzasadnienie konieczności
|
||||||
|
</Placeholder>
|
||||||
|
.<br />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Proszę w szczególności zwrócić uwagę na podanie adresów do
|
||||||
|
polityk prywatności tych firm, abym wiedział, jak
|
||||||
|
skontaktować się z nimi i wnioskować o usunięcie z ich baz
|
||||||
|
wysłanych przez Państwa stronę moich danych. )
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
W odpowiedzi proszę się nie powoływać na IAB Europe i ich
|
||||||
|
rzekomą renomę w tworzeniu rozwiązań zgodnych z RODO. IAB
|
||||||
|
chroni interes reklamodawców, a nie Użytkowników, i ich
|
||||||
|
rozwiązania (np. TCF) są{' '}
|
||||||
|
<a href="https://panoptykon.org/search/site/IAB">
|
||||||
|
notorycznie niezgodne z RODO i pozbawione szacunku dla
|
||||||
|
Użytkowników
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Apeluję także o wprowadzenie stosownych zmian na stronie
|
||||||
|
tak, aby nie pozostawiać cienia wątpliwości odnośnie tego,
|
||||||
|
na mocy jakiej przesłanki legalizującej dane są przetwarzane
|
||||||
|
przez wspomniane podmioty trzecie, lub tak, aby te dane po
|
||||||
|
prostu nie były wysyłane. Pomoże to zachować prywatność
|
||||||
|
innym użytkownikom Państwa strony. Polecam Państwa uwadze
|
||||||
|
<a href="https://edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_pl.pdf">
|
||||||
|
{' '}
|
||||||
|
oficjalne wytyczne EROD dotyczące zgody w kontekście
|
||||||
|
RODO
|
||||||
|
</a>
|
||||||
|
. Aby na przykład zapobiec automatycznemu wysyłaniu historii
|
||||||
|
przeglądania do podmiotów trzecich przez Państwa stronę,
|
||||||
|
można po prostu ustawić odpowiednio treść nagłówka{' '}
|
||||||
|
<a href="https://developer.mozilla.org/pl/docs/Web/HTTP/Headers/Referrer-Policy">
|
||||||
|
Referrer-Policy{' '}
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
407
report-window/email-template-3-controls.tsx
Normal file
407
report-window/email-template-3-controls.tsx
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as Survey from 'survey-react';
|
||||||
|
import { toBase64 } from '../util';
|
||||||
|
import emailHostSettings from './email-host-settings';
|
||||||
|
import { EmailTemplate3Config } from './email-template-3';
|
||||||
|
import verbs from './verbs';
|
||||||
|
|
||||||
|
function generateHostPage(
|
||||||
|
host: string,
|
||||||
|
index: number,
|
||||||
|
all_hosts: string[]
|
||||||
|
): { title: string; elements: any[] } {
|
||||||
|
function f(name: string, h = host) {
|
||||||
|
return `${h.replace(/\./g, '_')}|${name}`;
|
||||||
|
}
|
||||||
|
const previous_host: string | null = index > 0 ? all_hosts[index - 1] : null;
|
||||||
|
function defaultValue(name: string) {
|
||||||
|
if (!previous_host) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return { defaultValueExpression: `{${f(name, previous_host)}}` };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: host,
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: 'dropdown',
|
||||||
|
name: f('present'),
|
||||||
|
isRequired: true,
|
||||||
|
title: `Cel ujawnienia danych właścicielowi domeny ${host}`,
|
||||||
|
...defaultValue('present'),
|
||||||
|
choices: [
|
||||||
|
{ value: 'not_mentioned', text: 'nie jest podany nigdzie na stronie' },
|
||||||
|
{
|
||||||
|
value: 'mentioned_in_policy',
|
||||||
|
text: 'jest podany w polityce prywatności',
|
||||||
|
visibleIf: "{policy_readable} = 'yes' ",
|
||||||
|
},
|
||||||
|
|
||||||
|
{ value: 'mentioned_in_popup', text: 'jest podany w okienku RODO' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dropdown',
|
||||||
|
name: f('legal_basis_type'),
|
||||||
|
...defaultValue('legal_basis_type'),
|
||||||
|
isRequired: true,
|
||||||
|
title: `Podstawa prawna dla tego konkretnego celu`,
|
||||||
|
visibleIf: `{${f('present')}} notempty and {${f('present')}} != "not_mentioned"`,
|
||||||
|
choices: [
|
||||||
|
{ value: 'consent', text: 'to zgoda.' },
|
||||||
|
{
|
||||||
|
value: 'legitimate_interest',
|
||||||
|
text: 'to uzasadniony interes.',
|
||||||
|
},
|
||||||
|
{ value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radiogroup',
|
||||||
|
name: f('consent_problems'),
|
||||||
|
...defaultValue('consent_problems'),
|
||||||
|
isRequired: true,
|
||||||
|
title: `Jak ma się ta podstawa prawna do stanu faktycznego?`,
|
||||||
|
visibleIf: `{${f('legal_basis_type')}} = "consent"`,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'claims_consent_but_sends_before_consent',
|
||||||
|
text: `Strona wysłała {moje} dane do ${host} zanim {wyraziłem} na to zgodę`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'claims_consent_but_there_was_no_easy_refuse',
|
||||||
|
text: '{Kliknąłem} przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo dostępnego przycisku do niewyrażenia zgody jednym kliknięciem',
|
||||||
|
},
|
||||||
|
{ value: 'none', text: 'żadne z powyższych.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dropdown',
|
||||||
|
name: f('legitimate_interest_activity_specified'),
|
||||||
|
...defaultValue('legitimate_interest_activity_specified'),
|
||||||
|
isRequired: true,
|
||||||
|
title: /* HTML */ `Czy administrator strony opisał szczegółowo, na czym polega
|
||||||
|
uzasadniony interes w kontekście tego celu?`,
|
||||||
|
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest"`,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'precise',
|
||||||
|
text: /* HTML */ `Tak, wskazuje jasno na bieżące działania lub korzyści
|
||||||
|
wynikające z takiego przetwarzania danych.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'vague',
|
||||||
|
text: `Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'no',
|
||||||
|
text: `Nie. Nie wiadomo, na czym ten uzasadniony interes polega.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${host}?`,
|
||||||
|
name: f('legitimate_interest_description'),
|
||||||
|
visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`,
|
||||||
|
defaultValueExpression:
|
||||||
|
index == 0
|
||||||
|
? 'marketing'
|
||||||
|
: `{${f('legitimate_interest_description', previous_host)}}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dropdown',
|
||||||
|
title: `Czy domena ${host} należy do podmiotu spoza Europy (np. Google, Facebook)?`,
|
||||||
|
name: f('outside_eu'),
|
||||||
|
...defaultValue('outside_eu'),
|
||||||
|
visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f(
|
||||||
|
'consent_problems'
|
||||||
|
)}} = "none"`,
|
||||||
|
choices: [
|
||||||
|
{ value: 'yes', text: 'Tak' },
|
||||||
|
{ value: 'no', text: 'Nie' },
|
||||||
|
{ value: 'not_sure', text: 'Nie wiem' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EmailTemplate3Controls({ hosts }: { hosts: string[] }) {
|
||||||
|
const [survey, setSurvey] = React.useState<Survey.Model>(null);
|
||||||
|
React.useEffect(() => {
|
||||||
|
var json = {
|
||||||
|
showQuestionNumbers: 'off',
|
||||||
|
showProgressBar: 'top',
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
title: 'Zaimki',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: 'dropdown',
|
||||||
|
name: 'zaimek',
|
||||||
|
title: 'Forma czasownika:',
|
||||||
|
isRequired: true,
|
||||||
|
choices: [
|
||||||
|
{ value: 0, text: 'Wysłałem' },
|
||||||
|
{ value: 1, text: 'Wysłałam' },
|
||||||
|
{ value: 2, text: 'Wysłałom' },
|
||||||
|
{ value: 3, text: 'Wysłaliśmy' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Obowiązek informacyjny i machanizm pozyskiwania zgody',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: 'radiogroup',
|
||||||
|
title: 'Jaką formę informacji o przetwarzaniu danych osobowych stosuje ta strona?',
|
||||||
|
name: 'popup_type',
|
||||||
|
isRequired: true,
|
||||||
|
choices: [
|
||||||
|
{ value: 'none', text: 'Brak informacji' },
|
||||||
|
{
|
||||||
|
value: 'passive_popup',
|
||||||
|
text: /* HTML */ `Okienko o cookiesach, bez możliwości podjęcia
|
||||||
|
żadnego wyboru (np. tylko opcja „zamknij”)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'some_choice',
|
||||||
|
text: 'Okienko o cookiesach, z możliwością podjęcia wyboru',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
title: /* HTML */ `Istnieje możliwość, że okienko z informacjami i
|
||||||
|
wyborami dotyczącymi przetwarzania {Twoich} danych osobowych ukazało się
|
||||||
|
dawno temu w trakcie {twojej} wcześniejszej wizyty i wtedy je
|
||||||
|
{odkliknąłeś}. {Otwórz} tę samą stronę w Trybie Prywatnym (Incognito).
|
||||||
|
Co {widzisz}?`,
|
||||||
|
visibleIf: "{popup_type} = 'none'",
|
||||||
|
name: 'is_incognito_different',
|
||||||
|
isRequired: true,
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'incognito_is_the_same',
|
||||||
|
text: 'W Trybie prywatnym {widzę} to samo, co {widziałem} w normalnym trybie',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
visibleIf: '{is_incognito_different} != "no" and {popup_type} = "none"',
|
||||||
|
html: /* HTML */ `Jeżeli w trybie incognito widzisz więcej okienek z
|
||||||
|
informacjami o przetwarzaniu danych osobowych, wykonaj analizę w
|
||||||
|
normalnym trybie ponownie - ale najpierw usuń pliki cookies tej
|
||||||
|
strony.
|
||||||
|
<a
|
||||||
|
href="https://support.mozilla.org/pl/kb/usuwanie-ciasteczek-i-danych-stron-firefox?redirectslug=usuwanie-ciasteczek&redirectlocale=pl"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Zobacz, jak to zrobić
|
||||||
|
</a>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radiogroup',
|
||||||
|
name: 'mentions_passive_consent',
|
||||||
|
isRequired: true,
|
||||||
|
visibleIf: '{popup_type} = "passive_popup"',
|
||||||
|
title: 'Czy treść okienka wskazuje na zgodę wyrażoną pasywnie, np. „Korzystając z naszej strony wyrażasz zgodę” lub „Brak zmiany ustawień przeglądarki oznacza zgodę”?',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'yes',
|
||||||
|
text: 'Tak',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'no',
|
||||||
|
text: 'Nie',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Polityka prywatności',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: 'dropdown',
|
||||||
|
title: 'Czy polityka prywatności jest dostępna i czytelna?',
|
||||||
|
name: 'policy_readable',
|
||||||
|
isRequired: true,
|
||||||
|
choices: [
|
||||||
|
{ value: 'yes', text: 'dostępna i czytelna' },
|
||||||
|
{
|
||||||
|
value: 'entirely_obscured_by_popup',
|
||||||
|
text: 'dostępna, ale nieczytelna. Zasłania ją popup o RODO',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'cant_find',
|
||||||
|
text: `Niedostępna. {Szukałem}, ale nie {znalazłem} jej na stronie`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...hosts.map(generateHostPage),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
|
||||||
|
const survey = new Survey.Model(json);
|
||||||
|
survey.onProcessTextValue.add(function (
|
||||||
|
sender: Survey.SurveyModel,
|
||||||
|
options: { name: string; value?: string }
|
||||||
|
) {
|
||||||
|
if (verbs[options.name.toLowerCase()]) {
|
||||||
|
options.value = verbs[options.name.toLowerCase()][sender.valuesHash.zaimek];
|
||||||
|
if (options.name[0] == options.name[0].toUpperCase()) {
|
||||||
|
options.value = [
|
||||||
|
options.value[0].toUpperCase(),
|
||||||
|
...options.value.slice(1),
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSurvey(survey);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!survey) {
|
||||||
|
return <div>Wczytywanie...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Survey.Survey model={survey} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _EmailTemplate3Controls({
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
}: {
|
||||||
|
config: EmailTemplate3Config;
|
||||||
|
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
|
||||||
|
}): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{emailHostSettings(config, setConfig)}
|
||||||
|
<div>
|
||||||
|
<label htmlFor="poup_type">Typ okienka o RODO: </label>
|
||||||
|
<select
|
||||||
|
id="poup_type"
|
||||||
|
value={config.popup_type}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig((v) => ({
|
||||||
|
...v,
|
||||||
|
popup_type: e.target.value as EmailTemplate3Config['popup_type'],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="none">Brak jakiejkolwiek informacji</option>
|
||||||
|
<option value="passive_cookie_banner">
|
||||||
|
Pasywne powiadomienie o cookiesach
|
||||||
|
</option>
|
||||||
|
<option value="consent">Okienko z pytaniem o zgodę</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{config.popup_type !== 'none' ? (
|
||||||
|
<div>
|
||||||
|
<label htmlFor="popup_screenshot">Zrzut ekranu okienka o RODO:</label>
|
||||||
|
<input
|
||||||
|
{...{
|
||||||
|
type: 'file',
|
||||||
|
id: 'popup_screenshot',
|
||||||
|
onChange: async (e) => {
|
||||||
|
const popup_screenshot_base64 = await toBase64(e.target.files[0]);
|
||||||
|
setConfig((v) => ({
|
||||||
|
...v,
|
||||||
|
popup_screenshot_base64,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<label htmlFor="popup_action">Czy coś klikn*ł*m w informacjach o RODO?</label>
|
||||||
|
<select
|
||||||
|
id="popup_action"
|
||||||
|
value={config.popup_type}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig((v) => ({
|
||||||
|
...v,
|
||||||
|
popup_action: e.target.value as EmailTemplate3Config['popup_action'],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="ignored">Nic nie klin*ł*m</option>
|
||||||
|
<option value="accepted">Kliknięte „{config.popup_accept_all_text}”</option>
|
||||||
|
<option value="closed">Zamkn*ł*m okienko (np. przyciskiem "X")</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{config.popup_action === 'closed' ? (
|
||||||
|
<div>
|
||||||
|
<label htmlFor="popup_closed_how">Jak okienko zostało zamknięte? Poprzez</label>
|
||||||
|
<input
|
||||||
|
id="popup_closed_how"
|
||||||
|
type="text"
|
||||||
|
placeholder="kliknięcie przycisku „X”"
|
||||||
|
value={config.popup_closed_how}
|
||||||
|
style={{ width: '300px' }}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig((v) => ({
|
||||||
|
...v,
|
||||||
|
popup_closed_how: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{config.popup_type !== 'none' ? (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="popup_mentions_passive_consent"
|
||||||
|
checked={config.popup_mentions_passive_consent}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig((v) => ({
|
||||||
|
...v,
|
||||||
|
popup_mentions_passive_consent: e.target.checked,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label htmlFor="popup_mentions_passive_consent">
|
||||||
|
okienko wspomina o pasywnej zgodzie (np. „korzystając ze strony wyrażasz
|
||||||
|
zgodę”)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{config.popup_mentions_passive_consent ? (
|
||||||
|
<div>
|
||||||
|
<label htmlFor="popup_passive_consent_text">
|
||||||
|
Jak okienko próbuje wmówić Ci, że wyrażasz zgodę? Przeklej z treści okienka:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="popup_passive_consent_text"
|
||||||
|
placeholder="Korzystając ze strony wyrażasz zgodę"
|
||||||
|
value={config.popup_passive_consent_text}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig((v) => ({
|
||||||
|
...v,
|
||||||
|
popup_passive_consent_text: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
27
report-window/email-template-3.scss
Normal file
27
report-window/email-template-3.scss
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
@import '../sidebar/fonts.scss';
|
||||||
|
@import '../sidebar/colors.scss';
|
||||||
|
|
||||||
|
.mail-wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.75fr minmax(20rem, 60rem);
|
||||||
|
height: calc(100vh - 5rem);
|
||||||
|
background-color: $ultra-light-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-content-wrapper {
|
||||||
|
background-color: $ultra-light-grey;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-controls {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-content {
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 2rem 1.75rem;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
144
report-window/email-template-3.tsx
Normal file
144
report-window/email-template-3.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RequestCluster } from '../request-cluster';
|
||||||
|
import { StolenDataEntry } from '../stolen-data-entry';
|
||||||
|
import { getDate } from '../util';
|
||||||
|
import EmailTemplate3Controls from './email-template-3-controls';
|
||||||
|
import './email-template-3.scss';
|
||||||
|
|
||||||
|
declare var PLUGIN_NAME: string;
|
||||||
|
declare var PLUGIN_URL: string;
|
||||||
|
|
||||||
|
export type EmailTemplate3Config = {
|
||||||
|
pronoun: 0 | 1 | 2 | 3; // masc, fem, neutral, plural
|
||||||
|
policy_readable: 'null' | 'yes' | 'entirely_obscured_by_popup' | 'cant_find';
|
||||||
|
hosts_settings: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
presence: 'null' | 'not_mentioned' | 'mentioned_in_popup' | 'mentioned_in_policy';
|
||||||
|
legal_basis_type: 'null' | 'consent' | 'legitimate_interest' | 'not_mentioned';
|
||||||
|
consent_problems?:
|
||||||
|
| 'claims_consent_but_sends_before_consent'
|
||||||
|
| 'claims_consent_but_there_was_no_easy_refuse'
|
||||||
|
| 'none'
|
||||||
|
| 'null';
|
||||||
|
|
||||||
|
legitimate_interest_activity_specified: 'null' | 'no' | 'precise' | 'vague';
|
||||||
|
legitimate_interest_activity_description: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
popup_type: 'none' | 'passive_cookie_banner' | 'consent';
|
||||||
|
popup_action: 'ignored' | 'accepted' | 'closed';
|
||||||
|
popup_closed_how: string;
|
||||||
|
popup_screenshot_base64: string | null;
|
||||||
|
popup_accept_all_text: string;
|
||||||
|
popup_mentions_passive_consent: boolean;
|
||||||
|
popup_passive_consent_text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EmailTemplate3({
|
||||||
|
entries,
|
||||||
|
clusters,
|
||||||
|
}: {
|
||||||
|
entries: StolenDataEntry[];
|
||||||
|
clusters: Record<string, RequestCluster>;
|
||||||
|
version: number;
|
||||||
|
}): JSX.Element {
|
||||||
|
const all_host_ids = Array.from(new Set(entries.map((entry) => entry.request.shorthost)));
|
||||||
|
|
||||||
|
const [config, setConfig] = React.useState<EmailTemplate3Config>({
|
||||||
|
pronoun: Math.round(Math.random()) as 0 | 1,
|
||||||
|
policy_readable: 'null',
|
||||||
|
hosts_settings: Object.fromEntries(
|
||||||
|
all_host_ids.map((cluster_id) => [
|
||||||
|
cluster_id,
|
||||||
|
{
|
||||||
|
presence: 'null',
|
||||||
|
legal_basis_presence: 'null',
|
||||||
|
legal_basis_type: 'null',
|
||||||
|
consent_problems: 'null',
|
||||||
|
legitimate_interest_activity_specified: 'null',
|
||||||
|
legitimate_interest_activity_description: '',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
),
|
||||||
|
popup_type: 'none',
|
||||||
|
popup_action: 'ignored',
|
||||||
|
popup_screenshot_base64: null,
|
||||||
|
popup_accept_all_text: 'Zaakceptuj wszystkie',
|
||||||
|
popup_mentions_passive_consent: false,
|
||||||
|
popup_passive_consent_text: '',
|
||||||
|
popup_closed_how: 'kliknięcie przycisku „X”',
|
||||||
|
});
|
||||||
|
|
||||||
|
const visited_url = entries[0].request.originalURL;
|
||||||
|
|
||||||
|
const p = config.pronoun;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mail-wrapper">
|
||||||
|
<div className="mail-controls">
|
||||||
|
<EmailTemplate3Controls {...{ config, setConfig, hosts: all_host_ids }} />
|
||||||
|
</div>
|
||||||
|
<div className="mail-content-wrapper">
|
||||||
|
<article className="mail-content">
|
||||||
|
<p>
|
||||||
|
Dzień dobry, w dniu {getDate()}{' '}
|
||||||
|
{['odwiedziłem', 'odwiedziłam', 'odwiedziłom', 'odwiedziliśmy'][p]} stronę{' '}
|
||||||
|
{visited_url} i {['zbadałem', 'zbadałam', 'zbadałom', 'zbadaliśmy'][p]} ją
|
||||||
|
za pomocą wtyczki <a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w celu zbadania,
|
||||||
|
jakie informacje o {['mnie', 'mnie', 'mnie', 'nas'][p]} wysyła ta strona do
|
||||||
|
podmiotów trzecich.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{['Moją', 'Moją', 'Moją', 'Naszą'][p]} szczególną uwagę przykuło:
|
||||||
|
WYFILTROWANE WZGLĘDEM TEGO, CZY DANEGO PODMIOTU NIE MA W POLITYCE
|
||||||
|
PRYWATNOŚCI LUB POWIADOMIENIU O COOKIESACH{' '}
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
- wysyłanie mojego identyfikatora internetowego [z Cookie] (value)
|
||||||
|
oraz części mojej historii przeglądania do właściciela domeny
|
||||||
|
(domain);
|
||||||
|
</li>
|
||||||
|
<li> - (...).</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Dane te zostały wysłane zanim{' '}
|
||||||
|
{['miałem', 'miałam', 'miałom', 'mieliśmy'][p]} szansę przeczytać Państwa
|
||||||
|
politykę prywatności i w jakikolwiek czynny i jednoznaczny sposób wyrazić
|
||||||
|
zgodę na takie przetwarzanie moich danych osobowych.
|
||||||
|
</p>
|
||||||
|
{!['yes', 'null'].includes(config.policy_readable) ? (
|
||||||
|
<p>
|
||||||
|
{['Chciałem', 'Chciałam', 'Chciałom', 'Chcieliśmy'][p]} przeczytać
|
||||||
|
Państwa politykę prywatności przed akceptacją, ale{' '}
|
||||||
|
{config.policy_readable == 'cant_find' ? (
|
||||||
|
<>nie mogę znaleźć jej nigdzie na Państwa stronie.</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}{' '}
|
||||||
|
{config.policy_readable == 'entirely_obscured_by_popup' ? (
|
||||||
|
<>jest ona przesłonięta przez okienko o RODO.</>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
Dane zostały udostępnione podmiotom, o których nie{' '}
|
||||||
|
{['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'][p]} informacji ani w
|
||||||
|
Państwa polityce prywatności, ani w żadnym wyskakującym okienku na Państwa
|
||||||
|
stronie. Z tego powodu zwracam{p == 3 ? 'y' : ''} się do Państwa z pytaniem:
|
||||||
|
jakie były podstawy prawne takiego ujawnienia{' '}
|
||||||
|
{['moich', 'moich', 'moich', 'naszych'][p]} danych osobowych wyżej
|
||||||
|
wymienionym podmiotom? Uprzejmie{' '}
|
||||||
|
{['proszę', 'proszę', 'proszę', 'prosimy'][p]} o wskazanie podstawy prawnej
|
||||||
|
dla każdego z tych podmiotów z osobna.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
19
report-window/email-template.tsx
Normal file
19
report-window/email-template.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { RequestCluster } from '../request-cluster';
|
||||||
|
import { StolenDataEntry } from '../stolen-data-entry';
|
||||||
|
import EmailTemplate3 from './email-template-3';
|
||||||
|
|
||||||
|
export default function EmailTemplate({
|
||||||
|
entries,
|
||||||
|
clusters,
|
||||||
|
version,
|
||||||
|
}: {
|
||||||
|
entries: StolenDataEntry[];
|
||||||
|
clusters: Record<string, RequestCluster>;
|
||||||
|
version: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EmailTemplate3 {...{ entries, clusters, version }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
export type ExplainerKey = 'cookies_are_pii';
|
|
||||||
|
|
||||||
export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => JSX.Element> = {
|
|
||||||
cookies_are_pii: () => (
|
|
||||||
<>
|
|
||||||
<h3>Ciasteczka stanowią dane osobowe</h3>
|
|
||||||
<p>
|
|
||||||
Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
|
|
||||||
osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając „identyfikator
|
|
||||||
internetowy” i „numer identyfikacyjny” jako przykłady danych osobowych.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
@ -1,366 +0,0 @@
|
|||||||
function generateHostPage(
|
|
||||||
host: string,
|
|
||||||
index: number,
|
|
||||||
all_hosts: string[]
|
|
||||||
): { title: string; elements: any[]; visibleIf: string } {
|
|
||||||
function f(name: string, h = host) {
|
|
||||||
return `${h.replace(/\./g, '_')}|${name}`;
|
|
||||||
}
|
|
||||||
const previous_host: string | null = index > 0 ? all_hosts[index - 1] : null;
|
|
||||||
function defaultValue(name: string) {
|
|
||||||
if (!previous_host) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return { defaultValueExpression: `{${f(name, previous_host)}}` };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title: host,
|
|
||||||
visibleIf: "{popup_type} != 'none'",
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: f('present'),
|
|
||||||
isRequired: true,
|
|
||||||
title: `Cel ujawnienia danych właścicielowi domeny ${host}`,
|
|
||||||
...defaultValue('present'),
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'not_mentioned',
|
|
||||||
text: 'nie jest podany nigdzie na stronie',
|
|
||||||
visibleIf: "{policy_readable} = 'yes' ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_before_making_a_choice',
|
|
||||||
text: 'nie jest podany w żadnym miejscu na stronie, do którego można się dostać bez podejmowania wyboru dotyczącego przetwarzania danych osobowych',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'mentioned_in_policy',
|
|
||||||
text: 'jest podany w polityce prywatności',
|
|
||||||
visibleIf: "{policy_readable} = 'yes' ",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
value: 'mentioned_in_popup',
|
|
||||||
text: 'jest podany w okienku RODO',
|
|
||||||
visibleIf: "{popup_type} != 'none' ",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: f('legal_basis_type'),
|
|
||||||
...defaultValue('legal_basis_type'),
|
|
||||||
isRequired: true,
|
|
||||||
title: `Podstawa prawna dla tego konkretnego celu`,
|
|
||||||
visibleIf: `{${f('present')}} notempty and {${f(
|
|
||||||
'present'
|
|
||||||
)}} != "not_mentioned" and {${f('present')}} != "not_before_making_a_choice"`,
|
|
||||||
choices: [
|
|
||||||
{ value: 'consent', text: 'to zgoda.' },
|
|
||||||
{
|
|
||||||
value: 'legitimate_interest',
|
|
||||||
text: 'to uzasadniony interes.',
|
|
||||||
},
|
|
||||||
{ value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: f('consent_problems'),
|
|
||||||
...defaultValue('consent_problems'),
|
|
||||||
isRequired: true,
|
|
||||||
title: `Jak ma się ta podstawa prawna do stanu faktycznego?`,
|
|
||||||
visibleIf: `{${f('legal_basis_type')}} = "consent"`,
|
|
||||||
defaultValueExpression:
|
|
||||||
'iif({popup_action} = "none" or {popup_action} = "closed_popup", "claims_consent_but_sends_before_consent", iif({popup_action} = "accept_all" and {rejection_is_hard} = "yes", "claims_consent_but_there_was_no_easy_refuse", ""))',
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'claims_consent_but_sends_before_consent',
|
|
||||||
text: `Strona wysłała {moje} dane do ${host} zanim {wyraziłem} na to zgodę`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'claims_consent_but_there_was_no_easy_refuse',
|
|
||||||
text: '{Kliknąłem} przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo dostępnego przycisku do niewyrażenia zgody jednym kliknięciem',
|
|
||||||
},
|
|
||||||
{ value: 'none', text: 'żadne z powyższych.' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: f('legitimate_interest_activity_specified'),
|
|
||||||
...defaultValue('legitimate_interest_activity_specified'),
|
|
||||||
isRequired: true,
|
|
||||||
title: /* HTML */ `Czy administrator strony opisał szczegółowo, na czym polega
|
|
||||||
uzasadniony interes w kontekście tego celu?`,
|
|
||||||
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest"`,
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'precise',
|
|
||||||
text: /* HTML */ `Tak, wskazuje jasno na bieżące działania lub korzyści
|
|
||||||
wynikające z takiego przetwarzania danych.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'vague',
|
|
||||||
text: `Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'no',
|
|
||||||
text: `Nie. Nie wiadomo, na czym ten uzasadniony interes polega.`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${host}?`,
|
|
||||||
name: f('legitimate_interest_description'),
|
|
||||||
visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`,
|
|
||||||
placeholder: 'marketing',
|
|
||||||
defaultValueExpression:
|
|
||||||
index == 0
|
|
||||||
? 'marketing'
|
|
||||||
: `{${f('legitimate_interest_description', previous_host)}}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
title: `Czy domena ${host} należy do podmiotu spoza Europy (np. Google, Facebook)?`,
|
|
||||||
name: f('outside_eu'),
|
|
||||||
...defaultValue('outside_eu'),
|
|
||||||
visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f(
|
|
||||||
'consent_problems'
|
|
||||||
)}} = "none"`,
|
|
||||||
choices: [
|
|
||||||
{ value: 'yes', text: 'Tak' },
|
|
||||||
{ value: 'no', text: 'Nie' },
|
|
||||||
{ value: 'not_sure', text: 'Nie wiem' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
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'),
|
|
||||||
...defaultValue('was_processing_necessary'),
|
|
||||||
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f(
|
|
||||||
'present'
|
|
||||||
)}} = "not_mentioned"`,
|
|
||||||
choices: [
|
|
||||||
{ value: 'yes', text: 'Tak, było konieczne' },
|
|
||||||
{ value: 'no', text: 'Nie, nie było konieczne' },
|
|
||||||
{ value: 'not_sure', text: 'Nie mam zdania' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function generateSurveyQuestions(hosts: string[]) {
|
|
||||||
return {
|
|
||||||
showQuestionNumbers: 'off',
|
|
||||||
showProgressBar: 'top',
|
|
||||||
clearInvisibleValues: 'onHidden',
|
|
||||||
pages: [
|
|
||||||
{
|
|
||||||
title: 'Zaimki',
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'zaimek',
|
|
||||||
title: 'Forma czasownika:',
|
|
||||||
isRequired: true,
|
|
||||||
choices: [
|
|
||||||
{ value: 0, text: 'Wysłałem' },
|
|
||||||
{ value: 1, text: 'Wysłałam' },
|
|
||||||
{ value: 2, text: 'Wysłałom' },
|
|
||||||
{ value: 3, text: 'Wysłaliśmy' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Obowiązek informacyjny i machanizm pozyskiwania zgody',
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
title: 'Jaką formę informacji o przetwarzaniu danych osobowych stosuje ta strona?',
|
|
||||||
name: 'popup_type',
|
|
||||||
isRequired: true,
|
|
||||||
choices: [
|
|
||||||
{ value: 'none', text: 'Brak informacji' },
|
|
||||||
{
|
|
||||||
value: 'page',
|
|
||||||
text: 'Tylko w postaci tekstu na podstronie np. "prywatność" lub "polityka cookies"',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'passive_popup',
|
|
||||||
text: /* HTML */ `Okienko o cookiesach, bez możliwości podjęcia
|
|
||||||
żadnego wyboru (np. tylko opcja „zamknij”)`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'some_choice',
|
|
||||||
text: 'Okienko o cookiesach, z możliwością podjęcia wyboru',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'checkbox',
|
|
||||||
title: /* HTML */ `Istnieje możliwość, że okienko z informacjami i wyborami
|
|
||||||
dotyczącymi przetwarzania {Twoich} danych osobowych ukazało się dawno temu w
|
|
||||||
trakcie {twojej} wcześniejszej wizyty i wtedy je {odkliknąłeś}. {Otwórz} tę
|
|
||||||
samą stronę w Trybie Prywatnym (Incognito). Co {widzisz}?`,
|
|
||||||
visibleIf: "{popup_type} = 'none' or {popup_type} = 'page'",
|
|
||||||
name: 'is_incognito_different',
|
|
||||||
isRequired: true,
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'incognito_is_the_same',
|
|
||||||
text: 'W Trybie prywatnym {widzę} to samo, co {widziałem} w normalnym trybie',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'html',
|
|
||||||
visibleIf:
|
|
||||||
'{is_incognito_different} != "no" and ({popup_type} = "none" or {popup_type} = "page") ',
|
|
||||||
html: /* HTML */ `Jeżeli w trybie incognito widzisz więcej okienek z
|
|
||||||
informacjami o przetwarzaniu danych osobowych, wykonaj analizę w
|
|
||||||
normalnym trybie ponownie - ale najpierw usuń pliki cookies tej strony.
|
|
||||||
<a
|
|
||||||
href="https://support.mozilla.org/pl/kb/usuwanie-ciasteczek-i-danych-stron-firefox?redirectslug=usuwanie-ciasteczek&redirectlocale=pl"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Zobacz, jak to zrobić
|
|
||||||
</a>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'mentions_passive_consent',
|
|
||||||
isRequired: true,
|
|
||||||
visibleIf: '{popup_type} = "passive_popup"',
|
|
||||||
title: 'Czy treść okienka wskazuje na zgodę wyrażoną pasywnie, np. „Korzystając z naszej strony wyrażasz zgodę”, „Brak zmiany ustawień przeglądarki oznacza zgodę”, „Klikając przycisk "X" (zamknij) wyrażasz zgodę”?',
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'yes',
|
|
||||||
text: 'Tak',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'no',
|
|
||||||
text: 'Nie',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
name: 'passive_consent_description',
|
|
||||||
isRequired: true,
|
|
||||||
visibleIf: '{mentions_passive_consent} = "yes"',
|
|
||||||
title: 'Jakimi słowami administrator opisuje to pasywne wyrażenie zgody? Zacytuj wprost',
|
|
||||||
defaultValue: 'Korzystając ze strony wyrażasz zgodę',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'cookie_wall',
|
|
||||||
isRequired: true,
|
|
||||||
visibleIf: '{popup_type} = "passive_popup"',
|
|
||||||
title: 'Czy treść strony jest wygodnie czytelna bez odkliknięcia tego okienka o RODO?',
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'no', // wiem, że tu jest "no", a odpowiedź brzmi "tak" - ale nazwa pytania dotyczy obecności cookie walla
|
|
||||||
text: 'Tak, jest czytelna',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'yes',
|
|
||||||
text: 'Nie. Jest zupełnie niewidoczna albo jest przesłonięta w stopniu uniemożliwiającym lub znacznie utrudniającym czytanie treści strony.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'rejection_is_hard',
|
|
||||||
isRequired: true,
|
|
||||||
visibleIf: '{popup_type} = "some_choice"',
|
|
||||||
title: 'Czy wyrażenie zgody na wszystkie cele jest dokładnie tak samo łatwe, jak odmowa zgody na wszystkie cele?',
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'no', // wiem, że tu jest "no", a odpowiedź brzmi "tak" - ale nazwa pytania dotyczy braku równowagi
|
|
||||||
text: 'Tak. Opcja odmowy zgody na wszystkie cele jest równie widoczna i łatwo dostępna, co opcja wyrażenia zgody.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'yes',
|
|
||||||
text: 'Nie. Muszę wykonać więcej czynności aby odmówić wszystkich zgód, albo opcja niewyrażenia zgody jest mało widoczna.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'popup_action',
|
|
||||||
isRequired: true,
|
|
||||||
visibleIf: '{popup_type} = "some_choice" or {popup_type} = "passive_popup"',
|
|
||||||
title: 'Jaką akcję {podjąłeś} w ramach wyskakującego okienka?',
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'none',
|
|
||||||
text: 'Nic nie {kliknąłem}',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'closed_popup',
|
|
||||||
text: '{Zamknąłem} okienko za pomocą przycisku „X” lub „Zamknij”, lub podobnego',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'accept_all',
|
|
||||||
text: '{Kliknąłem} przycisk od akceptacji wszystkich zgód',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'deny_all',
|
|
||||||
text: '{Kliknąłem} przycisk do odmówienia zgody na wszystkie cele',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'other',
|
|
||||||
text: 'Coś innego',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
name: 'administrator_identity_available_before_choice',
|
|
||||||
isRequired: true,
|
|
||||||
visibleIf: '{popup_type} = "some_choice"',
|
|
||||||
title: 'Czy przed podjęciem wyboru dot. {Twoich} danych masz możliwość poznać tożsamość administratora strony?',
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
value: 'yes',
|
|
||||||
text: 'Tak.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'no',
|
|
||||||
text: 'Nie.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Obowiązek informacyjny, polityka prywatności',
|
|
||||||
visibleIf: "{popup_type} != 'none'",
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
type: 'radiogroup',
|
|
||||||
title: 'Czy polityka prywatności jest dostępna i czytelna?',
|
|
||||||
name: 'policy_readable',
|
|
||||||
isRequired: true,
|
|
||||||
choices: [
|
|
||||||
{ value: 'yes', text: 'dostępna i czytelna' },
|
|
||||||
{
|
|
||||||
value: 'entirely_obscured_by_popup',
|
|
||||||
text: 'dostępna, ale nieczytelna. Zasłania ją całkowicie lub prawie całkowicie popup o RODO lub nie można się do niej doklikać bez podjęcia wyboru w okienku',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'cant_find',
|
|
||||||
text: `Niedostępna. {Szukałem}, ale nie {znalazłem} jej na stronie`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
...hosts.map(generateHostPage),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
65
report-window/legitimate-interest-problems.tsx
Normal file
65
report-window/legitimate-interest-problems.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { setHostSetting } from './email-host-settings';
|
||||||
|
import { EmailTemplate3Config } from './email-template-3';
|
||||||
|
|
||||||
|
export default function LegitimateInteresProblems({
|
||||||
|
settings,
|
||||||
|
host_id,
|
||||||
|
pronoun,
|
||||||
|
setConfig,
|
||||||
|
}: {
|
||||||
|
host_id: string;
|
||||||
|
settings: EmailTemplate3Config['hosts_settings'][string];
|
||||||
|
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
|
||||||
|
}) {
|
||||||
|
if (settings.legal_basis_type !== 'legitimate_interest') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const [description, setDescription] = React.useState('marketing');
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Czy administrator strony opisał szczegółowo, na czym polega uzasadniony interes w
|
||||||
|
kontekście tego celu?
|
||||||
|
{/* {hostSettingsDropdown({
|
||||||
|
settings,
|
||||||
|
host_id,
|
||||||
|
setConfig,
|
||||||
|
field: 'legitimate_interest_activity_specified' as const,
|
||||||
|
value: settings.legitimate_interest_activity_specified,
|
||||||
|
options: {
|
||||||
|
precise: [
|
||||||
|
'Tak, wskazuje jasno na bieżące działania lub korzyści wynikające z takiego przetwarzania danych.',
|
||||||
|
],
|
||||||
|
vague: ['Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.'],
|
||||||
|
no: ['Nie. Nie wiadomo, na czym ten uzasadniony interes polega.'],
|
||||||
|
},
|
||||||
|
})} */}
|
||||||
|
</div>
|
||||||
|
{settings.legitimate_interest_activity_specified === 'vague' ? (
|
||||||
|
<div>
|
||||||
|
Jak administrator opisał to, na czym polega uzasadniony interes w kontekście{' '}
|
||||||
|
{host_id}?{' '}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={(e) =>
|
||||||
|
setHostSetting(
|
||||||
|
setConfig,
|
||||||
|
[host_id, 'legitimate_interest_activity_description'],
|
||||||
|
description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
zatwierdź
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
import RawAnswers, { BasicRawAnswers, HostRawAnswers } from './raw-answers';
|
|
||||||
|
|
||||||
export type RecordValue<T> = T extends Record<any, infer R> ? R : any;
|
|
||||||
|
|
||||||
export type ParsedHostAnswers = ({
|
|
||||||
present:
|
|
||||||
| 'not_mentioned'
|
|
||||||
| 'not_before_making_a_choice'
|
|
||||||
| 'mentioned_in_policy'
|
|
||||||
| 'mentioned_in_popup';
|
|
||||||
legal_basis_type: 'consent' | 'legitimate_interes' | 'not_mentioned';
|
|
||||||
popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other';
|
|
||||||
was_processing_necessary: 'yes' | 'no' | 'not_sure';
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
consent_problems:
|
|
||||||
| 'claims_consent_but_sends_before_consent'
|
|
||||||
| 'claims_consent_but_there_was_no_easy_refuse';
|
|
||||||
}
|
|
||||||
| { consent_problems: 'none'; outside_eu: 'yes' | 'no' | 'not_sure' }
|
|
||||||
)) & {
|
|
||||||
legitimate_interest_activity_specified: 'no' | 'precise' | 'vague';
|
|
||||||
outside_eu: 'yes' | 'no' | 'not_sure';
|
|
||||||
legitimate_interest_description?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ParsedAnswers = BasicRawAnswers & { hosts: Record<string, ParsedHostAnswers> };
|
|
||||||
|
|
||||||
function parseHostAnswers(
|
|
||||||
raw_answers: Record<keyof HostRawAnswers, string>
|
|
||||||
): Record<string, ParsedHostAnswers> {
|
|
||||||
const result: Record<string, Record<string, string>> = {};
|
|
||||||
for (const [key, value] of Object.entries(raw_answers)) {
|
|
||||||
const [masked_host, attr] = key.split('|');
|
|
||||||
const host = masked_host.replace(/_/g, '.');
|
|
||||||
if (!result[host]) {
|
|
||||||
result[host] = {} as ParsedHostAnswers;
|
|
||||||
}
|
|
||||||
result[host][attr] = value;
|
|
||||||
}
|
|
||||||
return result as Record<string, ParsedHostAnswers>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseAnswers({
|
|
||||||
zaimek,
|
|
||||||
is_incognito_different,
|
|
||||||
policy_readable,
|
|
||||||
popup_type,
|
|
||||||
cookie_wall,
|
|
||||||
passive_consent_description,
|
|
||||||
mentions_passive_consent,
|
|
||||||
rejection_is_hard,
|
|
||||||
administrator_identity_available_before_choice,
|
|
||||||
...rest
|
|
||||||
}: RawAnswers): ParsedAnswers {
|
|
||||||
return {
|
|
||||||
zaimek,
|
|
||||||
is_incognito_different,
|
|
||||||
policy_readable,
|
|
||||||
popup_type,
|
|
||||||
cookie_wall,
|
|
||||||
passive_consent_description,
|
|
||||||
mentions_passive_consent,
|
|
||||||
rejection_is_hard,
|
|
||||||
administrator_identity_available_before_choice,
|
|
||||||
hosts: parseHostAnswers(rest),
|
|
||||||
} as ParsedAnswers;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import * as Survey from 'survey-react';
|
|
||||||
import RawAnswers from './raw-answers';
|
|
||||||
import useSurvey from './use-survey';
|
|
||||||
|
|
||||||
export default function Questions({
|
|
||||||
hosts,
|
|
||||||
onComplete,
|
|
||||||
}: {
|
|
||||||
hosts: string[];
|
|
||||||
onComplete: (data: RawAnswers) => void;
|
|
||||||
}) {
|
|
||||||
const survey = useSurvey(hosts, {
|
|
||||||
onComplete: (sender) => onComplete(sender.data),
|
|
||||||
});
|
|
||||||
if (!survey) {
|
|
||||||
return <div>Wczytywanie....</div>;
|
|
||||||
}
|
|
||||||
return <Survey.Survey model={survey} />;
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
export type HostRawAnswers = {
|
|
||||||
[key: `${string}|present`]:
|
|
||||||
| 'not_mentioned'
|
|
||||||
| 'not_before_making_a_choice'
|
|
||||||
| 'mentioned_in_policy'
|
|
||||||
| 'mentioned_in_popup';
|
|
||||||
[key: `${string}|legal_basis_type`]: 'consent' | 'legitimate_interest' | 'not_mentioned';
|
|
||||||
[key: `${string}|consent`]:
|
|
||||||
| 'claims_consent_but_sends_before_consent'
|
|
||||||
| 'claims_consent_but_there_was_no_easy_refuse'
|
|
||||||
| 'none';
|
|
||||||
[key: `${string}|legitimate_interest_activity_specified`]: 'precise' | 'vague' | 'no';
|
|
||||||
[key: `${string}|legitimate_interest_description`]: string;
|
|
||||||
[key: `${string}|outside_eu`]: 'yes' | 'no' | 'not_sure';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BasicRawAnswers = {
|
|
||||||
zaimek: 0 | 1 | 2 | 3;
|
|
||||||
is_incognito_different: [] | ['incognito_is_the_same'];
|
|
||||||
policy_readable: 'yes' | 'vague' | 'cant_find';
|
|
||||||
popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other';
|
|
||||||
} & (
|
|
||||||
| ({
|
|
||||||
popup_type: 'passive_popup';
|
|
||||||
cookie_wall: 'yes' | 'no';
|
|
||||||
rejection_is_hard: undefined;
|
|
||||||
administrator_identity_available_before_choice: undefined;
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
mentions_passive_consent?: 'yes';
|
|
||||||
passive_consent_description: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mentions_passive_consent?: 'no';
|
|
||||||
passive_consent_description: undefined;
|
|
||||||
}
|
|
||||||
))
|
|
||||||
| {
|
|
||||||
popup_type: 'some_choice';
|
|
||||||
rejection_is_hard: 'yes' | 'no';
|
|
||||||
administrator_identity_available_before_choice: 'yes' | 'no';
|
|
||||||
cookie_wall: undefined;
|
|
||||||
passive_consent_description: undefined;
|
|
||||||
mentions_passive_consent: undefined;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
popup_type: 'none' | 'page';
|
|
||||||
cookie_wall: undefined;
|
|
||||||
passive_consent_description: undefined;
|
|
||||||
mentions_passive_consent: undefined;
|
|
||||||
rejection_is_hard: undefined;
|
|
||||||
administrator_identity_available_before_choice: undefined;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type RawAnswers = BasicRawAnswers & HostRawAnswers;
|
|
||||||
|
|
||||||
export default RawAnswers;
|
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-rendering: optimizelegibility;
|
text-rendering: optimizelegibility;
|
||||||
font-smooth: auto;
|
font-smooth: auto;
|
||||||
@ -20,6 +21,13 @@ body {
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: $black-color;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 2.1875rem;
|
||||||
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -1,59 +1,40 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { getMemory } from '../memory';
|
import { getMemory } from '../memory';
|
||||||
import { useEmitter } from '../util';
|
import { StolenDataEntry } from '../stolen-data-entry';
|
||||||
|
import { reduceConcat, useEmitter } from '../util';
|
||||||
|
import EmailTemplate from './email-template';
|
||||||
|
|
||||||
import './report-window.scss';
|
import './report-window.scss';
|
||||||
import Questions from './questions';
|
|
||||||
import EmailContent from './email-content';
|
|
||||||
import { parseAnswers, ParsedAnswers } from './parse-answers';
|
|
||||||
|
|
||||||
function Report() {
|
function Report() {
|
||||||
try {
|
try {
|
||||||
const origin = new URL(document.location.toString()).searchParams.get('origin');
|
const origin = new URL(document.location.toString()).searchParams.get('origin');
|
||||||
const [counter] = useEmitter(getMemory());
|
const [counter, setCounter] = useEmitter(getMemory());
|
||||||
const [answers, setAnswers] = React.useState<ParsedAnswers>(null);
|
|
||||||
const [mode, setMode] = React.useState('survey');
|
|
||||||
const clusters = getMemory().getClustersForOrigin(origin);
|
const clusters = getMemory().getClustersForOrigin(origin);
|
||||||
/* const [entries, setEntries] = React.useState<StolenDataEntry[]>([]); */
|
const [entries, setEntries] = React.useState<StolenDataEntry[]>([]);
|
||||||
/* React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
* setEntries(
|
setEntries(
|
||||||
* Object.values(clusters)
|
Object.values(clusters)
|
||||||
* .map((cluster) => {
|
.map((cluster) => {
|
||||||
* cluster.calculateRepresentativeStolenData();
|
cluster.calculateRepresentativeStolenData();
|
||||||
* return cluster.representativeStolenData;
|
return cluster.representativeStolenData;
|
||||||
* })
|
})
|
||||||
* .reduce(reduceConcat, [])
|
.reduce(reduceConcat, [])
|
||||||
* .filter((entry) => entry.isMarked)
|
.filter((entry) => entry.isMarked)
|
||||||
* );
|
);
|
||||||
* }, []); */
|
}, []);
|
||||||
/* if (entries.length == 0) {
|
if (entries.length == 0) {
|
||||||
* return <>Wczytywanie...</>;
|
return <>Wczytywanie...</>;
|
||||||
* } */
|
}
|
||||||
const visited_url = Object.values(clusters)
|
|
||||||
.find((cluster) => cluster.getMarkedRequests().length > 0)
|
|
||||||
?.getMarkedRequests()[0].originalURL;
|
|
||||||
|
|
||||||
const result = (
|
const result = (
|
||||||
<div {...{ 'data-version': counter }}>
|
<div {...{ 'data-version': counter }}>
|
||||||
<nav>
|
<nav>
|
||||||
<img src="../assets/icon-addon.svg" width={48} height={48}></img>{' '}
|
<img src="../assets/icon-addon.svg" width={48} height={48}></img>{' '}
|
||||||
<h1>Rentgen - Generuj treść maila dla {origin}</h1>
|
<h1>Rentgen - Generuj treść maila dla {origin}</h1>
|
||||||
</nav>
|
</nav>
|
||||||
{mode === 'survey' ? (
|
|
||||||
<Questions
|
<EmailTemplate {...{ entries, clusters, version: counter }} />
|
||||||
hosts={Object.values(clusters)
|
|
||||||
.filter((cluster) => cluster.getMarkedRequests().length > 0)
|
|
||||||
.map((cluster) => cluster.id)}
|
|
||||||
onComplete={(answers) => {
|
|
||||||
setAnswers(parseAnswers(answers));
|
|
||||||
setMode('preview');
|
|
||||||
}}
|
|
||||||
></Questions>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{mode === 'preview' ? <EmailContent {...{ answers, visited_url, clusters }} /> : ''}
|
|
||||||
{/* <HARConverter {...{ entries }} /> */}
|
{/* <HARConverter {...{ entries }} /> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import * as Survey from 'survey-react';
|
|
||||||
import generateSurveyQuestions from './generate-survey-questions';
|
|
||||||
import RawAnswers from './raw-answers';
|
|
||||||
import verbs, { v } from './verbs';
|
|
||||||
|
|
||||||
export default function useSurvey(
|
|
||||||
hosts: string[],
|
|
||||||
{ onComplete }: { onComplete: (sender: { data: RawAnswers }) => void }
|
|
||||||
): Survey.ReactSurveyModel {
|
|
||||||
const [survey, setSurvey] = React.useState<Survey.Model>(null);
|
|
||||||
React.useEffect(() => {
|
|
||||||
const model = generateSurveyQuestions(hosts);
|
|
||||||
console.log(model);
|
|
||||||
const survey = new Survey.Model(model);
|
|
||||||
survey.onProcessTextValue.add(function (
|
|
||||||
sender: Survey.SurveyModel,
|
|
||||||
options: { name: string; value?: string }
|
|
||||||
) {
|
|
||||||
if (verbs[options.name.toLowerCase()]) {
|
|
||||||
options.value = v(options.name, sender.valuesHash.zaimek);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
survey.onComplete.add(onComplete);
|
|
||||||
setSurvey(survey);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return survey;
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
const words = {
|
export default {
|
||||||
zrobiłem: ['zrobiłem', 'zrobiłam', 'zrobiłom', 'zrobiliśmy'],
|
zrobiłem: ['zrobiłem', 'zrobiłam', 'zrobiłom', 'zrobiliśmy'],
|
||||||
szukałem: ['szukałem', 'szukałam', 'szukałom', 'szukaliśmy'],
|
szukałem: ['szukałem', 'szukałam', 'szukałom', 'szukaliśmy'],
|
||||||
znalazłem: ['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'],
|
znalazłem: ['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'],
|
||||||
@ -16,21 +16,4 @@ const words = {
|
|||||||
widzisz: ['widzisz', 'widzisz', 'widzisz', 'widzicie'],
|
widzisz: ['widzisz', 'widzisz', 'widzisz', 'widzicie'],
|
||||||
widzę: ['widzę', 'widzę', 'widzę', 'widzimy'],
|
widzę: ['widzę', 'widzę', 'widzę', 'widzimy'],
|
||||||
widziałem: ['widziałem', 'widziałam', 'widziałom', 'widzieliśmy'],
|
widziałem: ['widziałem', 'widziałam', 'widziałom', 'widzieliśmy'],
|
||||||
odwiedziłem: ['odwiedziłem', 'odwiedziłam', 'odwiedziłom', 'odwiedziliśmy'],
|
|
||||||
mam: ['mam', 'mam', 'mam', 'mamy'],
|
|
||||||
podjąłeś: ['podjąłeś', 'podjęłaś', 'podjęłoś', 'podjęliście'],
|
|
||||||
zamknąłem: ['zamknąłem', 'zamknęłaś', 'zamknęłoś', 'zamknęliście'],
|
|
||||||
zwracam: ['zwracam', 'zwracam', 'zwracam', 'zwracamy'],
|
|
||||||
moich: ['moich', 'moich', 'moich', 'naszych'],
|
|
||||||
ciebie: ['ciebie', 'ciebie', 'ciebie', 'was'],
|
|
||||||
} as { [key: string]: string[] };
|
} as { [key: string]: string[] };
|
||||||
|
|
||||||
export default words;
|
|
||||||
|
|
||||||
export function v(key: string, index: number) {
|
|
||||||
let result = words[key.toLowerCase()]?.[index] || key;
|
|
||||||
if (key[0] == key[0].toUpperCase()) {
|
|
||||||
result = [result[0].toUpperCase(), ...result.slice(1)].join('');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,12 @@ import { Sources, StolenDataEntry } from './stolen-data-entry';
|
|||||||
|
|
||||||
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
|
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
|
||||||
|
|
||||||
const source_priority: Array<Sources> = ['cookie', 'pathname', 'queryparams', 'header'];
|
const source_priority: Array<Sources> = [
|
||||||
|
'cookie',
|
||||||
|
'pathname',
|
||||||
|
'queryparams',
|
||||||
|
'header',
|
||||||
|
];
|
||||||
|
|
||||||
export class RequestCluster extends EventEmitter {
|
export class RequestCluster extends EventEmitter {
|
||||||
public requests: ExtendedRequest[] = [];
|
public requests: ExtendedRequest[] = [];
|
||||||
@ -32,10 +37,6 @@ export class RequestCluster extends EventEmitter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMarkedCookies() {
|
|
||||||
return this.getMarkedEntries().some((entry) => entry.source === 'cookie');
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateRepresentativeStolenData(
|
calculateRepresentativeStolenData(
|
||||||
filter: {
|
filter: {
|
||||||
minValueLength: number;
|
minValueLength: number;
|
||||||
@ -92,7 +93,8 @@ export class RequestCluster extends EventEmitter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
array[index].getValuePreview() === array[index - 1].getValuePreview() ||
|
array[index].getValuePreview() ===
|
||||||
|
array[index - 1].getValuePreview() ||
|
||||||
isSameURL(array[index].value, array[index - 1].value)
|
isSameURL(array[index].value, array[index - 1].value)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@ -124,7 +126,9 @@ export class RequestCluster extends EventEmitter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.sort((entry1, entry2) => (entry1.getPriority() > entry2.getPriority() ? -1 : 1));
|
.sort((entry1, entry2) =>
|
||||||
|
entry1.getPriority() > entry2.getPriority() ? -1 : 1
|
||||||
|
);
|
||||||
return this.representativeStolenData;
|
return this.representativeStolenData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +165,9 @@ export class RequestCluster extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMarkedEntries(): StolenDataEntry[] {
|
getMarkedEntries(): StolenDataEntry[] {
|
||||||
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
|
return this.requests
|
||||||
|
.map((request) => request.getMarkedEntries())
|
||||||
|
.reduce(reduceConcat, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
exposesOrigin() {
|
exposesOrigin() {
|
||||||
|
@ -157,7 +157,7 @@ export default function StolenDataCluster({
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="table-header" colSpan={4}>
|
<th className="table-header" colSpan={4}>
|
||||||
Wysłane dane:
|
Znalezione ustawienia:
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user