Create complex types for the survey response to prepare for template writing
This commit is contained in:
parent
7330ebf9f2
commit
8e72759a7e
@ -1,40 +0,0 @@
|
||||
/* 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>
|
||||
* );
|
||||
* } */
|
@ -1,69 +0,0 @@
|
||||
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,42 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
10
report-window/email-content.tsx
Normal file
10
report-window/email-content.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { ParsedAnswers } from './parse-answers';
|
||||
|
||||
export default function EmailContent({ answers }: { answers: ParsedAnswers }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Email template</h1>
|
||||
<pre>{JSON.stringify(answers, null, 3)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
@import '../sidebar/fonts.scss';
|
||||
@import '../sidebar/colors.scss';
|
@ -1,67 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,486 +0,0 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,407 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
@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;
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
314
report-window/generate-survey-questions.ts
Normal file
314
report-window/generate-survey-questions.ts
Normal file
@ -0,0 +1,314 @@
|
||||
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: '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"`,
|
||||
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'`,
|
||||
placeholder: 'marketing',
|
||||
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 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: '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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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: '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',
|
||||
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),
|
||||
],
|
||||
};
|
||||
}
|
0
report-window/generate-survey.ts
Normal file
0
report-window/generate-survey.ts
Normal file
@ -1,65 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
83
report-window/parse-answers.ts
Normal file
83
report-window/parse-answers.ts
Normal file
@ -0,0 +1,83 @@
|
||||
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';
|
||||
}
|
||||
| ({
|
||||
present: 'mentioned_in_policy' | 'mentioned_in_popup';
|
||||
} & (
|
||||
| ({
|
||||
legal_basis_type: 'consent';
|
||||
} & (
|
||||
| {
|
||||
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' }
|
||||
))
|
||||
| ({
|
||||
legal_basis_type: 'legitimate_interest';
|
||||
} & (
|
||||
| {
|
||||
legitimate_interest_activity_specified: 'no';
|
||||
}
|
||||
| {
|
||||
legitimate_interest_activity_specified: 'precise';
|
||||
outside_eu: 'yes' | 'no' | 'not_sure';
|
||||
}
|
||||
| {
|
||||
legitimate_interest_activity_specified: 'vague';
|
||||
legitimate_interest_description: string;
|
||||
}
|
||||
))
|
||||
| {
|
||||
legal_basis_type: 'not_mentioned';
|
||||
}
|
||||
));
|
||||
|
||||
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 RawAnswers;
|
||||
}
|
19
report-window/questions.tsx
Normal file
19
report-window/questions.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
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} />;
|
||||
}
|
57
report-window/raw-answers.ts
Normal file
57
report-window/raw-answers.ts
Normal file
@ -0,0 +1,57 @@
|
||||
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_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';
|
||||
cookie_wall: undefined;
|
||||
passive_consent_description: undefined;
|
||||
mentions_passive_consent: undefined;
|
||||
administrator_identity_available_before_choice: 'yes' | 'no';
|
||||
}
|
||||
| {
|
||||
popup_type: 'none';
|
||||
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;
|
@ -1,40 +1,53 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { getMemory } from '../memory';
|
||||
import { StolenDataEntry } from '../stolen-data-entry';
|
||||
import { reduceConcat, useEmitter } from '../util';
|
||||
import EmailTemplate from './email-template';
|
||||
import { useEmitter } from '../util';
|
||||
|
||||
import './report-window.scss';
|
||||
import Questions from './questions';
|
||||
import EmailContent from './email-content';
|
||||
import { parseAnswers, ParsedAnswers } from './parse-answers';
|
||||
|
||||
function Report() {
|
||||
try {
|
||||
const origin = new URL(document.location.toString()).searchParams.get('origin');
|
||||
const [counter, setCounter] = useEmitter(getMemory());
|
||||
const [counter] = useEmitter(getMemory());
|
||||
const [answers, setAnswers] = React.useState<ParsedAnswers>(null);
|
||||
const [mode, setMode] = React.useState('survey');
|
||||
const clusters = getMemory().getClustersForOrigin(origin);
|
||||
const [entries, setEntries] = React.useState<StolenDataEntry[]>([]);
|
||||
React.useEffect(() => {
|
||||
setEntries(
|
||||
Object.values(clusters)
|
||||
.map((cluster) => {
|
||||
cluster.calculateRepresentativeStolenData();
|
||||
return cluster.representativeStolenData;
|
||||
})
|
||||
.reduce(reduceConcat, [])
|
||||
.filter((entry) => entry.isMarked)
|
||||
);
|
||||
}, []);
|
||||
if (entries.length == 0) {
|
||||
return <>Wczytywanie...</>;
|
||||
}
|
||||
/* const [entries, setEntries] = React.useState<StolenDataEntry[]>([]); */
|
||||
/* React.useEffect(() => {
|
||||
* setEntries(
|
||||
* Object.values(clusters)
|
||||
* .map((cluster) => {
|
||||
* cluster.calculateRepresentativeStolenData();
|
||||
* return cluster.representativeStolenData;
|
||||
* })
|
||||
* .reduce(reduceConcat, [])
|
||||
* .filter((entry) => entry.isMarked)
|
||||
* );
|
||||
* }, []); */
|
||||
/* if (entries.length == 0) {
|
||||
* return <>Wczytywanie...</>;
|
||||
* } */
|
||||
const result = (
|
||||
<div {...{ 'data-version': counter }}>
|
||||
<nav>
|
||||
<img src="../assets/icon-addon.svg" width={48} height={48}></img>{' '}
|
||||
<h1>Rentgen - Generuj treść maila dla {origin}</h1>
|
||||
</nav>
|
||||
|
||||
<EmailTemplate {...{ entries, clusters, version: counter }} />
|
||||
{mode === 'survey' ? (
|
||||
<Questions
|
||||
hosts={Object.keys(clusters)}
|
||||
onComplete={(answers) => {
|
||||
setAnswers(parseAnswers(answers));
|
||||
setMode('preview');
|
||||
}}
|
||||
></Questions>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{mode === 'preview' ? <EmailContent {...{ answers }} /> : ''}
|
||||
{/* <HARConverter {...{ entries }} /> */}
|
||||
</div>
|
||||
);
|
||||
|
34
report-window/use-survey.ts
Normal file
34
report-window/use-survey.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import * as Survey from 'survey-react';
|
||||
import generateSurveyQuestions from './generate-survey-questions';
|
||||
import RawAnswers from './raw-answers';
|
||||
import verbs 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);
|
||||
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 = 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('');
|
||||
}
|
||||
}
|
||||
});
|
||||
survey.onComplete.add(onComplete);
|
||||
setSurvey(survey);
|
||||
}, []);
|
||||
|
||||
return survey;
|
||||
}
|
@ -157,7 +157,7 @@ export default function StolenDataCluster({
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="table-header" colSpan={4}>
|
||||
Znalezione ustawienia:
|
||||
Wysłane dane:
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
Loading…
Reference in New Issue
Block a user