From 5cf640e6865e1e6960a5319a5fa3f05701dce31d Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Tue, 8 Feb 2022 22:27:12 +0100 Subject: [PATCH] =?UTF-8?q?Wsparcie=20dla=20dw=C3=B3ch=20podstawowych=20pr?= =?UTF-8?q?oblem=C3=B3w,=20more=20to=20come?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- report-window/deduce-problems.tsx | 143 +++++++++++++++++++++ report-window/email-content.tsx | 53 +++++++- report-window/explainers.tsx | 14 ++ report-window/generate-survey-questions.ts | 64 ++++++++- report-window/parse-answers.ts | 55 +++----- report-window/raw-answers.ts | 5 +- report-window/report-window.scss | 8 -- report-window/report-window.tsx | 10 +- report-window/use-survey.ts | 1 + report-window/verbs.ts | 9 +- request-cluster.ts | 22 ++-- 11 files changed, 313 insertions(+), 71 deletions(-) create mode 100644 report-window/deduce-problems.tsx create mode 100644 report-window/explainers.tsx diff --git a/report-window/deduce-problems.tsx b/report-window/deduce-problems.tsx new file mode 100644 index 0000000..3df5711 --- /dev/null +++ b/report-window/deduce-problems.tsx @@ -0,0 +1,143 @@ +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) {} + + 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 ( + <> +

Brak informacji na temat przetwarzania danych osobowych

+

+ {_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami + domen: +

+ +

+ Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie są cele + przetwarzania takich danych oraz jakie są podstawy prawne takiego przetwarzania. +

+

Zwracam się zatem do Państwa z następującymi pytaniami:

+ + + ); + } + getNecessaryExplainers() { + const explainers = [] as Array; + + 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 ( + <> +

Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne

+

+ Państwa strona dokonała odczytu plików Cookie zapisanych na dysku twardym mojego + komputera. Dotyczy to plików cookie przypisanych do domen: +

+
    + {cookie_clusters.map((cluster, index) => { + const names = cluster + .getMarkedEntries() + .filter((e) => e.source === 'cookie') + .map((e) => e.name); + + return ( +
  • + {cluster.id} ({names.length > 1 ? 'pliki' : 'plik'}{' '} + {names.map((name, index) => { + return ( + <> + {index > 0 ? ', ' : ''} + {name} + + ); + })} + ){index === cookie_clusters.length - 1 ? '.' : ';'} +
  • + ); + })} +
+ + ); + } + 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 +): 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; +} diff --git a/report-window/email-content.tsx b/report-window/email-content.tsx index 1c62798..e2fffa4 100644 --- a/report-window/email-content.tsx +++ b/report-window/email-content.tsx @@ -1,10 +1,57 @@ +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'; -export default function EmailContent({ answers }: { answers: ParsedAnswers }) { +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; +}) { + 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 ( -
-

Email template

+
{JSON.stringify(answers, null, 3)}
+

Dzień dobry,

+

+ w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po podejrzeniu ruchu + sieciowego generowanego przez tę stronę za pomocą wtyczki{' '} + {PLUGIN_NAME} w przeglądarce Firefox {_('mam')} pytania + dotyczące przetwarzania {_('moich')} danych osobowych, na które nie {_('znalazłem')}{' '} + odpowiedzi nigdzie na Państwa stronie. +

+ {problems.map((problem) => problem.getEmailContent())} + {explainers.map((explainer) => explainer(answers.zaimek))} +

+ {_('Zwracam')} Państwa uwagę na fakt, że w myśl{' '} + + treści wyroku TSUE w sprawie C-40/17 + {' '} + 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”). +

); } diff --git a/report-window/explainers.tsx b/report-window/explainers.tsx new file mode 100644 index 0000000..556f3f4 --- /dev/null +++ b/report-window/explainers.tsx @@ -0,0 +1,14 @@ +export type ExplainerKey = 'cookies_are_pii'; + +export const Explainers: Record JSX.Element> = { + cookies_are_pii: () => ( + <> +

Ciasteczka stanowią dane osobowe

+

+ 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. +

+ + ), +}; diff --git a/report-window/generate-survey-questions.ts b/report-window/generate-survey-questions.ts index 7c40618..3c18f37 100644 --- a/report-window/generate-survey-questions.ts +++ b/report-window/generate-survey-questions.ts @@ -2,7 +2,7 @@ function generateHostPage( host: string, index: number, all_hosts: string[] -): { title: string; elements: any[] } { +): { title: string; elements: any[]; visibleIf: string } { function f(name: string, h = host) { return `${h.replace(/\./g, '_')}|${name}`; } @@ -15,6 +15,7 @@ function generateHostPage( } return { title: host, + visibleIf: "{popup_type} != 'none'", elements: [ { type: 'radiogroup', @@ -70,6 +71,8 @@ function generateHostPage( 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', @@ -83,7 +86,7 @@ function generateHostPage( ], }, { - type: 'dropdown', + type: 'radiogroup', name: f('legitimate_interest_activity_specified'), ...defaultValue('legitimate_interest_activity_specified'), isRequired: true, @@ -118,7 +121,7 @@ function generateHostPage( : `{${f('legitimate_interest_description', previous_host)}}`, }, { - type: 'dropdown', + type: 'radiogroup', title: `Czy domena ${host} należy do podmiotu spoza Europy (np. Google, Facebook)?`, name: f('outside_eu'), ...defaultValue('outside_eu'), @@ -131,6 +134,20 @@ function generateHostPage( { 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' }, + ], + }, ], }; } @@ -168,6 +185,10 @@ export default function generateSurveyQuestions(hosts: string[]) { 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 @@ -185,7 +206,7 @@ export default function generateSurveyQuestions(hosts: string[]) { 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'", + visibleIf: "{popup_type} = 'none' or {popup_type} = 'page'", name: 'is_incognito_different', isRequired: true, choices: [ @@ -197,7 +218,8 @@ export default function generateSurveyQuestions(hosts: string[]) { }, { type: 'html', - visibleIf: '{is_incognito_different} != "no" and {popup_type} = "none"', + 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. @@ -213,7 +235,7 @@ export default function generateSurveyQuestions(hosts: string[]) { 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ę”?', + 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', @@ -267,6 +289,35 @@ export default function generateSurveyQuestions(hosts: string[]) { }, ], }, + { + 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', @@ -288,6 +339,7 @@ export default function generateSurveyQuestions(hosts: string[]) { }, { title: 'Obowiązek informacyjny, polityka prywatności', + visibleIf: "{popup_type} != 'none'", elements: [ { type: 'radiogroup', diff --git a/report-window/parse-answers.ts b/report-window/parse-answers.ts index c0a114c..4f7b0d2 100644 --- a/report-window/parse-answers.ts +++ b/report-window/parse-answers.ts @@ -2,42 +2,27 @@ import RawAnswers, { BasicRawAnswers, HostRawAnswers } from './raw-answers'; export type RecordValue = T extends Record ? R : any; -export type ParsedHostAnswers = +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'; +} & ( | { - present: 'not_mentioned' | 'not_before_making_a_choice'; + consent_problems: + | 'claims_consent_but_sends_before_consent' + | 'claims_consent_but_there_was_no_easy_refuse'; } - | ({ - 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'; - } - )); + | { 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 }; @@ -79,5 +64,5 @@ export function parseAnswers({ rejection_is_hard, administrator_identity_available_before_choice, hosts: parseHostAnswers(rest), - } as RawAnswers; + } as ParsedAnswers; } diff --git a/report-window/raw-answers.ts b/report-window/raw-answers.ts index 26246bc..8f81aff 100644 --- a/report-window/raw-answers.ts +++ b/report-window/raw-answers.ts @@ -18,6 +18,7 @@ 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'; @@ -37,13 +38,13 @@ export type BasicRawAnswers = { | { 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; - administrator_identity_available_before_choice: 'yes' | 'no'; } | { - popup_type: 'none'; + popup_type: 'none' | 'page'; cookie_wall: undefined; passive_consent_description: undefined; mentions_passive_consent: undefined; diff --git a/report-window/report-window.scss b/report-window/report-window.scss index c711913..65e1245 100644 --- a/report-window/report-window.scss +++ b/report-window/report-window.scss @@ -3,7 +3,6 @@ * { margin: 0px; - padding: 0px; box-sizing: border-box; text-rendering: optimizelegibility; font-smooth: auto; @@ -21,13 +20,6 @@ body { background-color: #fff; } -p { - margin-bottom: 1rem; - color: $black-color; - font-size: 1.25rem; - line-height: 2.1875rem; -} - nav { position: sticky; top: 0; diff --git a/report-window/report-window.tsx b/report-window/report-window.tsx index 692112d..467316e 100644 --- a/report-window/report-window.tsx +++ b/report-window/report-window.tsx @@ -30,6 +30,10 @@ function Report() { /* if (entries.length == 0) { * return <>Wczytywanie...; * } */ + const visited_url = Object.values(clusters) + .find((cluster) => cluster.getMarkedRequests().length > 0) + ?.getMarkedRequests()[0].originalURL; + const result = (
{mode === 'survey' ? ( cluster.getMarkedRequests().length > 0) + .map((cluster) => cluster.id)} onComplete={(answers) => { setAnswers(parseAnswers(answers)); setMode('preview'); @@ -47,7 +53,7 @@ function Report() { ) : ( '' )} - {mode === 'preview' ? : ''} + {mode === 'preview' ? : ''} {/* */}
); diff --git a/report-window/use-survey.ts b/report-window/use-survey.ts index ae7bf70..77ddec8 100644 --- a/report-window/use-survey.ts +++ b/report-window/use-survey.ts @@ -11,6 +11,7 @@ export default function useSurvey( const [survey, setSurvey] = React.useState(null); React.useEffect(() => { const model = generateSurveyQuestions(hosts); + console.log(model); const survey = new Survey.Model(model); survey.onProcessTextValue.add(function ( sender: Survey.SurveyModel, diff --git a/report-window/verbs.ts b/report-window/verbs.ts index 1519192..bc0aa05 100644 --- a/report-window/verbs.ts +++ b/report-window/verbs.ts @@ -16,12 +16,19 @@ const words = { widzisz: ['widzisz', 'widzisz', 'widzisz', 'widzicie'], widzę: ['widzę', 'widzę', 'widzę', 'widzimy'], 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[] }; export default words; export function v(key: string, index: number) { - let result = words[key.toLowerCase()][index] || key; + let result = words[key.toLowerCase()]?.[index] || key; if (key[0] == key[0].toUpperCase()) { result = [result[0].toUpperCase(), ...result.slice(1)].join(''); } diff --git a/request-cluster.ts b/request-cluster.ts index bd4521c..182e0e0 100644 --- a/request-cluster.ts +++ b/request-cluster.ts @@ -4,12 +4,7 @@ import { Sources, StolenDataEntry } from './stolen-data-entry'; import { allSubhosts, isSameURL, reduceConcat, unique } from './util'; -const source_priority: Array = [ - 'cookie', - 'pathname', - 'queryparams', - 'header', -]; +const source_priority: Array = ['cookie', 'pathname', 'queryparams', 'header']; export class RequestCluster extends EventEmitter { public requests: ExtendedRequest[] = []; @@ -37,6 +32,10 @@ export class RequestCluster extends EventEmitter { return false; } + hasMarkedCookies() { + return this.getMarkedEntries().some((entry) => entry.source === 'cookie'); + } + calculateRepresentativeStolenData( filter: { minValueLength: number; @@ -93,8 +92,7 @@ export class RequestCluster extends EventEmitter { return true; } if ( - array[index].getValuePreview() === - array[index - 1].getValuePreview() || + array[index].getValuePreview() === array[index - 1].getValuePreview() || isSameURL(array[index].value, array[index - 1].value) ) { return false; @@ -126,9 +124,7 @@ export class RequestCluster extends EventEmitter { return true; } }) - .sort((entry1, entry2) => - entry1.getPriority() > entry2.getPriority() ? -1 : 1 - ); + .sort((entry1, entry2) => (entry1.getPriority() > entry2.getPriority() ? -1 : 1)); return this.representativeStolenData; } @@ -165,9 +161,7 @@ export class RequestCluster extends EventEmitter { } getMarkedEntries(): StolenDataEntry[] { - return this.requests - .map((request) => request.getMarkedEntries()) - .reduce(reduceConcat, []); + return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []); } exposesOrigin() {