├── .dockerignore ├── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── LICENSE.txt ├── Readme.md ├── _docker ├── Backend ├── cron.sh ├── entrypoint.sh └── privacymail-crontab ├── docker-compose.test.yml ├── docker-compose.yml.example ├── environment.yaml └── privacymail ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── exporter.py ├── identity ├── __init__.py ├── admin.py ├── apps.py ├── checks.py ├── filters.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20190314_1512.py │ ├── 0003_service_resultsdirty.py │ ├── 0004_service_hasapprovedidentity.py │ ├── 0005_servicethirdpartyembeds_sets_cookie.py │ ├── 0006_auto_20190315_1349.py │ ├── 0007_auto_20190315_1929.py │ ├── 0008_auto_20190401_1401.py │ ├── 0009_servicethirdpartyembeds_receives_identifier.py │ ├── 0010_service_changeurltype.py │ ├── 0011_service_add_advertising_option.py │ ├── 0012_service_permitted_senders.py │ ├── 0013_identity_is_dead.py │ └── __init__.py ├── models.py ├── models │ ├── Identity.py │ ├── Service.py │ ├── ServiceThirdPartyEmbeds.py │ └── __init__.py ├── rating │ ├── ABTesting.py │ ├── __init__.py │ ├── calculate.py │ ├── emailLeaks.py │ ├── loadedResources.py │ ├── personalizedLinks.py │ ├── rating.py │ ├── trackingServices.py │ └── unpersonalizedLinks.py ├── tables.py ├── templatetags │ ├── __init__.py │ └── tags.py ├── tests.py ├── util.py └── views.py ├── mailfetcher ├── __init__.py ├── admin.py ├── analyser_cron.py ├── apps.py ├── cron.py ├── crons │ ├── __init__.py │ └── mailCrawler │ │ ├── __init__.py │ │ ├── analysis │ │ ├── __init__.py │ │ ├── clickLinks.py │ │ ├── importClickResults.py │ │ ├── importViewResults.py │ │ ├── leakage.py │ │ └── viewMail.py │ │ ├── confirmMail.py │ │ ├── fetchMails.py │ │ ├── getUnfinishedMailCount.py │ │ ├── init.py │ │ ├── openWPM.py │ │ └── singleMail.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_thirdparty_resultsdirty.py │ ├── 0003_auto_20190326_1054.py │ ├── 0004_auto_20190326_1106.py │ ├── 0005_eresource_personalised.py │ ├── 0006_mail_possible_ab_testing.py │ ├── 0007_auto_20190402_0727.py │ ├── 0008_mail_contains_javascript.py │ ├── 0009_thirdparty_metadata.py │ ├── 0010_thirdparty_service.py │ ├── 0011_thirdparty_add_connections_to_service.py │ ├── 0012_auto_20201125_1250.py │ ├── 0013_auto_20201126_1225.py │ ├── 0014_scanword.py │ ├── 0015_auto_20210120_1042.py │ └── __init__.py ├── models │ ├── Eresource.py │ ├── Mail.py │ ├── Scanword.py │ ├── Thirdparty.py │ └── __init__.py ├── templates │ ├── identity_approval_mail.txt │ ├── mailfetcher │ │ ├── confirm.html │ │ ├── mail.html │ │ └── scanword.html │ └── third_party_spam.txt ├── test.py ├── testdata │ ├── mail_approvable.eml │ └── mail_unapprovable.eml ├── tests.py └── views.py ├── manage.py ├── privacymail ├── __init__.py ├── env.example ├── settings.py ├── urls.py └── wsgi.py ├── util ├── __init__.py ├── admin.py ├── apps.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── analysedirty.py │ │ ├── benchmark.py │ │ ├── clearcache.py │ │ ├── inspectcache.py │ │ ├── reanalyse_eresources.py │ │ ├── recrawl.py │ │ ├── redocache.py │ │ ├── reset_queue.py │ │ └── thesis_analyser.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py └── website ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── index.html └── static │ ├── favicon.ico │ ├── manifest.json │ └── robots.txt ├── src ├── .eslintrc.js ├── App.tsx ├── assets │ ├── fonts │ │ ├── Roboto-Black.ttf │ │ ├── Roboto-BlackItalic.ttf │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-LightItalic.ttf │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-MediumItalic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.ttf │ │ └── Roboto-ThinItalic.ttf │ ├── images │ │ ├── favicon.ico │ │ ├── female.jpg │ │ ├── iconFont │ │ │ ├── Read Me.txt │ │ │ ├── demo-files │ │ │ │ ├── demo.css │ │ │ │ └── demo.js │ │ │ ├── demo.html │ │ │ ├── fonts │ │ │ │ ├── PrivacyMail.eot │ │ │ │ ├── PrivacyMail.svg │ │ │ │ ├── PrivacyMail.ttf │ │ │ │ └── PrivacyMail.woff │ │ │ ├── selection.json │ │ │ └── style.css │ │ ├── icons │ │ │ ├── alternate_email-24px.svg │ │ │ ├── attach_money-24px.svg │ │ │ ├── cancel-24px.svg │ │ │ ├── check_circle-24px.svg │ │ │ ├── check_circle_fill-24px.svg │ │ │ ├── check_circle_outline-24px.svg │ │ │ ├── danger-24px.svg │ │ │ ├── de.svg │ │ │ ├── done_all-24px.svg │ │ │ ├── email-24px.svg │ │ │ ├── en.svg │ │ │ ├── expand-24px.svg │ │ │ ├── file_copy-24px.svg │ │ │ ├── github.svg │ │ │ ├── home-24px.svg │ │ │ ├── how_to_reg-24px.svg │ │ │ ├── insert_link-24px.svg │ │ │ ├── mood-24px.svg │ │ │ ├── policy-24px.svg │ │ │ ├── public-24px.svg │ │ │ ├── question_answer-24px.svg │ │ │ ├── record_voice_over-24px.svg │ │ │ ├── remove_circle-24px.svg │ │ │ ├── repeat-24px.svg │ │ │ ├── school-24px.svg │ │ │ ├── search-24px.svg │ │ │ ├── share-24px.svg │ │ │ └── warning-24px.svg │ │ ├── letter.jpg │ │ ├── logo.png │ │ ├── male.jpg │ │ └── onDemandInstructions │ │ │ ├── gmailCopyEmail.png │ │ │ ├── gmailViewSource.png │ │ │ ├── thunderbirdCopyEmail.png │ │ │ └── thunderbirdViewSource.png │ └── styles │ │ ├── collapsible.scss │ │ ├── colors.scss │ │ ├── fonts.scss │ │ ├── grid.scss │ │ ├── home │ │ ├── details.scss │ │ ├── home.scss │ │ ├── index.scss │ │ └── welcome.scss │ │ ├── iconList.scss │ │ ├── identity │ │ ├── identity.scss │ │ ├── index.scss │ │ └── person.scss │ │ ├── index.scss │ │ ├── layout │ │ ├── footer.scss │ │ ├── header.scss │ │ └── index.scss │ │ ├── newsletter │ │ ├── analysis.scss │ │ ├── faqHint.scss │ │ ├── generalInfo.scss │ │ ├── identityAlert.scss │ │ ├── index.scss │ │ ├── newSearch.scss │ │ ├── newsletter.scss │ │ └── privacyRating.scss │ │ ├── onDemand.scss │ │ ├── spinner.scss │ │ ├── staticPages │ │ ├── faq.scss │ │ ├── imprint.scss │ │ ├── index.scss │ │ └── notFound.scss │ │ └── stepper.scss ├── components │ ├── embed │ │ ├── AnalysisEmbed.tsx │ │ ├── Embed.tsx │ │ ├── EmbedHeadline.tsx │ │ ├── EmbedOnClickThirdparties.tsx │ │ ├── EmbedOnOpenThirdparties.tsx │ │ └── EmbedPersonalisedLinks.tsx │ ├── faq │ │ └── FAQ.tsx │ ├── footer │ │ ├── Footer.tsx │ │ └── LanguageSelection.tsx │ ├── header │ │ └── Header.tsx │ ├── home │ │ ├── Detection.tsx │ │ ├── Home.tsx │ │ ├── Statistics.tsx │ │ ├── Tracking.tsx │ │ ├── Welcome.tsx │ │ ├── WhatWeDo.tsx │ │ └── WhyPrivacymail.tsx │ ├── identity │ │ ├── Identity.tsx │ │ └── Person.tsx │ ├── imprint │ │ └── Imprint.tsx │ ├── newsletter │ │ ├── FaqHint.tsx │ │ ├── GeneralInfo.tsx │ │ ├── HistoryRating.tsx │ │ ├── IdentityAlert.tsx │ │ ├── NewSearch.tsx │ │ ├── Newsletter.tsx │ │ ├── NoEmailAlert.tsx │ │ ├── PrivacyRating.tsx │ │ ├── ShareButton.tsx │ │ └── analysis │ │ │ ├── ABTesting.tsx │ │ │ ├── Analysis.tsx │ │ │ ├── ColoredNumbers.tsx │ │ │ ├── Methode.tsx │ │ │ ├── OnClickThirdparties.tsx │ │ │ ├── OnOpenThirdparties.tsx │ │ │ ├── PassOrNotIcon.tsx │ │ │ ├── PersonalisedLinks.tsx │ │ │ ├── Spam.tsx │ │ │ ├── ThirdpartyConnections.tsx │ │ │ └── ThridpartysByCategory.tsx │ ├── notfound │ │ ├── DefaultNotFound.tsx │ │ ├── EmbedNotFound.tsx │ │ └── ServiceNotFound.tsx │ ├── onDemand │ │ ├── OnDemand.tsx │ │ ├── OnDemandAnalysis.tsx │ │ ├── OnDemandInput.tsx │ │ └── instructions │ │ │ ├── Gmail.tsx │ │ │ ├── Instructions.tsx │ │ │ └── Thunderbird.tsx │ └── privacy │ │ └── Privacy.tsx ├── i18n │ ├── addTranslation.js │ ├── countires.json │ ├── de.json │ ├── en.json │ ├── faq.json │ ├── i18n.js │ ├── mergeOptions.js │ └── sectors.json ├── index.js ├── react-app-env.d.ts ├── repository │ ├── emailAnalysis.tsx │ ├── embed.ts │ ├── execute.ts │ ├── identity.ts │ ├── index.ts │ ├── newsletter.ts │ └── statistics.ts ├── serviceWorker.js ├── testing │ ├── penalty.csv │ ├── scoreTesting.js │ └── scoreTestingLocalData.js └── utils │ ├── CollapsibleItem.tsx │ ├── Icon.tsx │ ├── IconList.tsx │ ├── InvalidDomain.tsx │ ├── Spinner.tsx │ ├── SplitDomainName.tsx │ ├── Stepper.tsx │ ├── Tooltip.tsx │ └── functions │ ├── convertRatingToMark.ts │ ├── getRatingColor.ts │ ├── isDomainValid.ts │ ├── isThirdpartyEvil.ts │ └── onEnterKey.ts ├── tsconfig.json └── webpack.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # avoid apple shit 7 | **/.DS_Store 8 | .DS_Store 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | .python-virtualenv 79 | .envrc 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # dotenv 88 | .env 89 | 90 | # virtualenv 91 | .venv 92 | venv/ 93 | ENV/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | # pycharm 109 | .idea/ 110 | 111 | # vscode 112 | .vscode/ 113 | 114 | # django db 115 | privacymail/db.sqlite3 116 | privacymail/static 117 | 118 | # Temporary editor files 119 | *.swp 120 | 121 | # Personal scripts 122 | dev.sh 123 | 124 | # docker volume 125 | dbdata/ 126 | docker-compose.yml 127 | docker-compose.yml.prod 128 | 129 | privacymail/privacymail/tmp/ 130 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | GIT_SUBMODULE_STRATEGY: recursive 3 | 4 | stages: 5 | - package 6 | 7 | package: 8 | stage: package 9 | image: docker:latest 10 | variables: 11 | LANG: C.UTF-8 12 | before_script: 13 | - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY 14 | script: 15 | - docker build --pull -f _docker/Backend -t "$CI_REGISTRY_IMAGE" . 16 | - docker push "$CI_REGISTRY_IMAGE" 17 | only: 18 | - develop 19 | 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "privacymail/OpenWPM"] 2 | path = privacymail/OpenWPM 3 | url = https://github.com/mozilla/OpenWPM.git 4 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # PrivacyMail 2 | 3 | PrivacyMail is an eMail privacy analysis system. For more information about the platform, visit [privacymail.info](https://privacymail.info). 4 | 5 | ## Installation 6 | PrivacyMail is a Django-based website. The `ansible` folder contains a deployment script that uses [Ansible](https://www.ansible.com/). See the README in that folder for additional details on what you need to set up to allow the system to deploy correctly. There are also some additional manual steps involved in setting up the necessary cronjobs to automate the retrieval and analysis of eMails. These steps are also described in the README file. 7 | 8 | ## Development 9 | If you want to do some local development, you will need to set up your own `privacymail/privacymail/settings.py`. See `ansible/templates/settings.py` for the template. Make sure to replace all statements that look like `{{ lookup( [...] ) }}`, as these are directives that are interpreted by Ansible during the deployment process. 10 | 11 | As a minimum, you will need to set up a virtualenv, install the dependencies from the `requirements.txt` file, set up the settings file as mentioned above, and provide the system with a Postgres database (docker works fine here). Afterwards, run the database migrations, and you should be good to go. However, in this setup, you will be unable to analyze eMails. For this, you will also need to set up [OpenWPM](https://github.com/mozilla/openwpm) (included as a submodule in this git repository) and configure eMail servers in settings.py. For details on the OpenWPM setup, check the ansible deployment playbook. 12 | 13 | ## License 14 | PrivacyMail is licensed under the GPLv3 license. 15 | 16 | ## Citation 17 | If you use PrivacyMail in a scientific project, please cite our paper at the Annual Privacy Forum 2019: 18 | 19 | ``` 20 | @article{PrivacyMail, 21 | title = {{Towards Transparency in Email Tracking}}, 22 | author = {Maass, Max and Schwär, Stephan and Hollick, Matthias}, 23 | journal = {Annual Privacy Forum}, 24 | year = {2019} 25 | } 26 | ``` 27 | 28 | ## Acknowledgement 29 | The creation of PrivacyMail was funded in part by the DFG as part of project C.1 within the [RTG 2050 "Privacy and Trust for Mobile Users"](https://www.informatik.tu-darmstadt.de/privacy-trust/privacy_and_trust/index.en.jsp). -------------------------------------------------------------------------------- /_docker/Backend: -------------------------------------------------------------------------------- 1 | FROM continuumio/miniconda3 2 | 3 | # Copy Project 4 | WORKDIR /opt/privacymail 5 | 6 | RUN apt-get clean -qq \ 7 | && rm -r /var/lib/apt/lists/* -vf \ 8 | && apt-get clean -qq \ 9 | && apt-get update -qq \ 10 | && apt-get upgrade -qq \ 11 | # deps to run firefox inc. with xvfb 12 | && apt-get install cron curl make libgtk-3-0 libx11-xcb1 libdbus-glib-1-2 libxt6 xvfb -qq 13 | 14 | # create conda environment 15 | COPY environment.yaml . 16 | RUN conda env create --file=environment.yaml 17 | RUN /opt/conda/bin/activate privacymail 18 | 19 | # install new node version 20 | RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - 21 | RUN apt-get install nodejs -qq 22 | 23 | COPY privacymail privacymail 24 | COPY .git .git 25 | 26 | # create openwpm log and data directories 27 | RUN mkdir -p /opt/privacymail/privacymail/privacymail/tmp/data 28 | RUN mkdir -p /opt/privacymail/privacymail/privacymail/tmp/log 29 | 30 | # install openwpm 31 | WORKDIR /opt/privacymail/privacymail/OpenWPM 32 | RUN ./scripts/install-firefox.sh 33 | 34 | WORKDIR /opt/privacymail/privacymail/OpenWPM/openwpm/Extension/firefox 35 | # Building firefox extension 36 | RUN npm install && npm run build 37 | 38 | # Building webext-instrumentation extension 39 | WORKDIR /opt/privacymail/privacymail/OpenWPM/openwpm/Extension/webext-instrumentation 40 | RUN npm install --legacy-peer-deps && npm run build:main && npm run build:module 41 | 42 | # build frontend 43 | WORKDIR /opt/privacymail/privacymail/website 44 | RUN npm install && npm run build 45 | 46 | WORKDIR /opt/privacymail 47 | COPY _docker/entrypoint.sh . 48 | 49 | # setup crontab 50 | COPY _docker/cron.sh . 51 | RUN chmod 755 cron.sh 52 | COPY _docker/privacymail-crontab /etc/cron.d/privacymail-crontab 53 | RUN chmod 0644 /etc/cron.d/privacymail-crontab \ 54 | && crontab /etc/cron.d/privacymail-crontab 55 | 56 | ENTRYPOINT ["bash", "/opt/privacymail/entrypoint.sh"] 57 | -------------------------------------------------------------------------------- /_docker/cron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source /etc/environment 3 | source ~/.bashrc 4 | source /opt/conda/etc/profile.d/conda.sh 5 | conda activate privacymail 6 | 7 | cd /opt/privacymail/privacymail 8 | 9 | python manage.py runcrons 10 | -------------------------------------------------------------------------------- /_docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Start cron because it won't on its own for some reason 3 | service cron start 4 | # Export env 5 | printenv > /etc/environment 6 | 7 | echo "Making migrations and migrating the database. " 8 | source /opt/conda/etc/profile.d/conda.sh 9 | conda activate privacymail 10 | cd privacymail 11 | python manage.py migrate --noinput 12 | python manage.py collectstatic --noinput 13 | 14 | gunicorn privacymail.wsgi:application --bind 0.0.0.0:8000 --workers=4 15 | -------------------------------------------------------------------------------- /_docker/privacymail-crontab: -------------------------------------------------------------------------------- 1 | */10 * * * * bash /opt/privacymail/cron.sh >> /tmp/privacymail.log 2>> /tmp/privacymail.err 2 | -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | backend: 6 | build: 7 | context: . 8 | dockerfile: _docker/Backend 9 | networks: 10 | - default 11 | ports: 12 | - "8000:8000" 13 | depends_on: 14 | - "db" 15 | environment: 16 | - APPLICATION_DEBUG=True 17 | - DATABASE_NAME=privacymail 18 | - DATABASE_USER=privacymail 19 | - DATABASE_PASSWORD=privacymail 20 | - DATABASE_HOST=db 21 | - DATABASE_PORT=5432 22 | - RAVEN_DSN=https://test@my-sentry.local 23 | - MAIL_NEWSLETTER_USERNAME=test 24 | - MAIL_NEWSLETTER_PASSWORD=test 25 | - MAIL_PRIVAYCLETTER_USERNAME=test 26 | - MAIL_PRIVAYCLETTER_PASSWORD=test 27 | - MAIL_PRIVACYMAIL_USERNAME=test 28 | - MAIL_PRIVACYMAIL_PASSWORD=test 29 | - ALLOWED_HOST=localhost 30 | 31 | db: 32 | image: postgres 33 | networks: 34 | - default 35 | volumes: 36 | - ./dbdata/:/var/lib/postgresql/data 37 | ports: 38 | - "5432:5432" 39 | environment: 40 | - POSTGRES_DB=privacymail 41 | - POSTGRES_USER=privacymail 42 | - POSTGRES_PASSWORD=privacymail 43 | 44 | testdb: 45 | image: postgres 46 | networks: 47 | - default 48 | volumes: 49 | - ./testdbdata/:/var/lib/postgresql/data 50 | ports: 51 | - "5431:5432" 52 | environment: 53 | - POSTGRES_DB=TEST 54 | - POSTGRES_USER=privacymail 55 | - POSTGRES_PASSWORD=privacymail 56 | 57 | networks: 58 | default: 59 | -------------------------------------------------------------------------------- /docker-compose.yml.example: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | backend: 6 | build: 7 | context: . 8 | dockerfile: _docker/Backend 9 | networks: 10 | - default 11 | ports: 12 | - "8000:8000" 13 | depends_on: 14 | - "db" 15 | environment: 16 | - SECRET_KEY="" 17 | - APPLICATION_DEBUG=False 18 | - DATABASE_NAME=privacymail 19 | - DATABASE_USER=privacymail 20 | - DATABASE_PASSWORD=privacymail 21 | - DATABASE_HOST=db 22 | - DATABASE_PORT=5432 23 | - RAVEN_DSN= 24 | - MAIL_NEWSLETTER_USERNAME=test 25 | - MAIL_NEWSLETTER_PASSWORD=test 26 | - MAIL_PRIVAYCLETTER_USERNAME=test 27 | - MAIL_PRIVAYCLETTER_PASSWORD=test 28 | - MAIL_PRIVACYMAIL_USERNAME=test 29 | - MAIL_PRIVACYMAIL_PASSWORD=test 30 | - ALLOWED_HOST= 31 | - MAXIMUM_ALLOWED_EMAIL_ANALYSIS_ONDEMAND=5 32 | 33 | db: 34 | image: postgres 35 | networks: 36 | - default 37 | volumes: 38 | - ./dbdata/:/var/lib/postgresql/data 39 | environment: 40 | - POSTGRES_DB=privacymail 41 | - POSTGRES_USER=privacymail 42 | - POSTGRES_PASSWORD=privacymail 43 | 44 | networks: 45 | default: 46 | -------------------------------------------------------------------------------- /privacymail/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/api/__init__.py -------------------------------------------------------------------------------- /privacymail/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /privacymail/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /privacymail/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/api/migrations/__init__.py -------------------------------------------------------------------------------- /privacymail/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /privacymail/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /privacymail/api/urls.py: -------------------------------------------------------------------------------- 1 | """privacymail URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.urls import path 17 | from . import views 18 | from identity.views import StatisticView, IdentityView, EmbedView, ServiceView, ServiceListView 19 | 20 | urlpatterns = [ 21 | path("service/", ServiceView.as_view(), name="Service"), 22 | path("service/", ServiceView.as_view(), name="ServiceLookup"), 23 | path("services/", ServiceListView.as_view(), name="ServiceList"), 24 | path("embed//", EmbedView.as_view(), name="Embed"), 25 | path("embed/", EmbedView.as_view(), name="EmbedLookup"), 26 | path("identity/", IdentityView.as_view(), name="IdentityCreation"), 27 | path("statistics", StatisticView.as_view(), name="Statistic"), 28 | path( 29 | "bookmarklet/identity/", 30 | views.BookmarkletApiView.as_view(), 31 | name="BookmarkletApiEndpoint", 32 | ), 33 | path("analysis", views.AnalysisView.as_view(), name="Analysis"), 34 | ] 35 | -------------------------------------------------------------------------------- /privacymail/identity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/identity/__init__.py -------------------------------------------------------------------------------- /privacymail/identity/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from identity.models import * 3 | 4 | admin.site.register(Identity) 5 | admin.site.register(Service) -------------------------------------------------------------------------------- /privacymail/identity/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | class IdentityConfig(AppConfig): 4 | name = 'identity' 5 | -------------------------------------------------------------------------------- /privacymail/identity/filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from identity.models import Service 3 | 4 | 5 | class ServiceFilter(django_filters.FilterSet): 6 | sector = django_filters.ChoiceFilter(choices=Service.SECTOR_CHOICES) 7 | 8 | class Meta: 9 | model = Service 10 | fields = ['sector', 'country_of_origin'] 11 | -------------------------------------------------------------------------------- /privacymail/identity/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django_countries.widgets import CountrySelectWidget 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty 5 | 6 | 7 | class ServiceMetadataForm(forms.ModelForm): 8 | class Meta: 9 | model = Service 10 | fields = ('country_of_origin', 'sector') 11 | # widgets = {'country_of_origin': CountrySelectWidget(layout='
{widget}')} 12 | widgets = {'country_of_origin': CountrySelectWidget(layout='{widget}')} 13 | 14 | 15 | class EmbedMetadataForm(forms.ModelForm): 16 | class Meta: 17 | model = Thirdparty 18 | fields = ('country_of_origin', 'sector') 19 | # widgets = {'country_of_origin': CountrySelectWidget(layout='
{widget}')} 20 | widgets = {'country_of_origin': CountrySelectWidget(layout='{widget}')} 21 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-14 15:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Identity', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('first_name', models.CharField(max_length=50)), 19 | ('surname', models.CharField(max_length=50)), 20 | ('mail', models.EmailField(max_length=254, unique=True)), 21 | ('gender', models.BooleanField()), 22 | ('approved', models.BooleanField(default=False)), 23 | ('lastapprovalremindersend', models.TimeField(default=None, null=True)), 24 | ('receives_third_party_spam', models.BooleanField(default=False)), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='Service', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('url', models.URLField()), 32 | ('name', models.CharField(max_length=50)), 33 | ], 34 | ), 35 | migrations.CreateModel( 36 | name='ServiceThirdPartyEmbeds', 37 | fields=[ 38 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 39 | ('leaks_address', models.BooleanField(default=False)), 40 | ('embed_type', models.CharField( 41 | choices=[('LINK', 'link'), ('IMAGE', 'image'), ('CSS', 'css'), ('UNDETERMINED', 'undetermined')], 42 | default='UNDETERMINED', max_length=20)), 43 | ], 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0002_auto_20190314_1512.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-14 15:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | initial = True 9 | 10 | dependencies = [ 11 | ('identity', '0001_initial'), 12 | ('mailfetcher', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='servicethirdpartyembeds', 18 | name='mail', 19 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='mailfetcher.Mail'), 20 | ), 21 | migrations.AddField( 22 | model_name='servicethirdpartyembeds', 23 | name='service', 24 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='embeds', 25 | to='identity.Service'), 26 | ), 27 | migrations.AddField( 28 | model_name='servicethirdpartyembeds', 29 | name='thirdparty', 30 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, 31 | related_name='embeds', to='mailfetcher.Thirdparty'), 32 | ), 33 | migrations.AddField( 34 | model_name='service', 35 | name='thirdparties', 36 | field=models.ManyToManyField(related_name='services', through='identity.ServiceThirdPartyEmbeds', 37 | to='mailfetcher.Thirdparty'), 38 | ), 39 | migrations.AddField( 40 | model_name='identity', 41 | name='service', 42 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='identity.Service'), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0003_service_resultsdirty.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-15 08:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('identity', '0002_auto_20190314_1512'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='service', 14 | name='resultsdirty', 15 | field=models.BooleanField(default=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0004_service_hasapprovedidentity.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-15 09:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('identity', '0003_service_resultsdirty'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='service', 14 | name='hasApprovedIdentity', 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0005_servicethirdpartyembeds_sets_cookie.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-15 11:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('identity', '0004_service_hasapprovedidentity'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='servicethirdpartyembeds', 14 | name='sets_cookie', 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0006_auto_20190315_1349.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-15 13:49 2 | 3 | from django.db import migrations, models 4 | import django_countries.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('identity', '0005_servicethirdpartyembeds_sets_cookie'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='service', 16 | name='country_of_origin', 17 | field=django_countries.fields.CountryField(blank=True, max_length=2), 18 | ), 19 | migrations.AddField( 20 | model_name='service', 21 | name='sector', 22 | field=models.CharField(choices=[('adult', 'Adult'), ('art', 'Art'), ('games', 'Games'), ('entertainment', 'Entertainment'), ('health', 'Health'), ('finance', 'Financial'), ('news', 'News'), ('shopping', 'Shopping'), ('b2b', 'Business-to-Business'), ('reference', 'Reference'), ('science', 'Science'), ('politics', 'Political Party / Politician'), ('activist', 'Activist'), ('sports', 'Sports'), ('unknown', 'Unknown')], default='unknown', max_length=30), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0007_auto_20190315_1929.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-15 19:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('identity', '0006_auto_20190315_1349'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name='servicethirdpartyembeds', 14 | name='embed_type', 15 | field=models.CharField(choices=[('STATIC', 'static'), ('ONVIEW', 'onView'), ('ONCLICK', 'onClick'), 16 | ('UNDETERMINED', 'undetermined')], default='UNDETERMINED', max_length=20), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0008_auto_20190401_1401.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-01 14:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('identity', '0007_auto_20190315_1929'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='service', 15 | name='sector', 16 | field=models.CharField(choices=[('activist', 'Activist'), ('adult', 'Adult'), ('art', 'Art'), ('b2b', 'Business-to-Business'), ('entertainment', 'Entertainment'), ('finance', 'Financial'), ('games', 'Games'), ('health', 'Health'), ('news', 'News'), ('politics', 'Political Party / Politician'), ('reference', 'Reference'), ('science', 'Science'), ('shopping', 'Shopping'), ('sports', 'Sports'), ('travel', 'Travel'), ('unknown', 'Unknown')], default='unknown', max_length=30), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0009_servicethirdpartyembeds_receives_identifier.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-02 12:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('identity', '0008_auto_20190401_1401'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='servicethirdpartyembeds', 15 | name='receives_identifier', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0010_service_changeurltype.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-05 08:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('identity', '0009_servicethirdpartyembeds_receives_identifier'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='service', 15 | name='url', 16 | field=models.CharField(max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0011_service_add_advertising_option.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-08 09:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('identity', '0010_service_changeurltype'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='service', 15 | name='sector', 16 | field=models.CharField(choices=[('activist', 'Activist'), ('adult', 'Adult'), ('ads', 'Advertising'), ('art', 'Art'), ('b2b', 'Business-to-Business'), ('entertainment', 'Entertainment'), ('finance', 'Financial'), ('games', 'Games'), ('health', 'Health'), ('news', 'News'), ('politics', 'Political Party / Politician'), ('reference', 'Reference'), ('science', 'Science'), ('shopping', 'Shopping'), ('sports', 'Sports'), ('travel', 'Travel'), ('unknown', 'Unknown')], default='unknown', max_length=30), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0012_service_permitted_senders.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-12 13:38 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | def set_my_defaults(apps, schema_editor): 8 | Service = apps.get_model('identity', 'Service') 9 | for service in Service.objects.all().iterator(): 10 | service.permitted_senders = [service.url] 11 | service.save() 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | dependencies = [ 17 | ('identity', '0011_service_add_advertising_option'), 18 | ] 19 | 20 | operations = [ 21 | migrations.AddField( 22 | model_name='service', 23 | name='permitted_senders', 24 | field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), null=True, size=None), 25 | preserve_default=False, 26 | ), 27 | migrations.RunPython(set_my_defaults, migrations.RunPython.noop), 28 | migrations.AlterField( 29 | model_name='service', 30 | name='permitted_senders', 31 | field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), null=False, size=None) 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/0013_identity_is_dead.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2021-02-24 15:44 2 | 3 | from django.db import migrations, models 4 | 5 | def set_my_defaults(apps, schema_editor): 6 | Identity = apps.get_model('identity', 'Identity') 7 | for identity in Identity.objects.all().iterator(): 8 | if identity.approved: 9 | identity.is_dead = False 10 | else: 11 | identity.is_dead = True 12 | 13 | identity.save() 14 | 15 | class Migration(migrations.Migration): 16 | 17 | dependencies = [ 18 | ('identity', '0012_service_permitted_senders'), 19 | ] 20 | 21 | operations = [ 22 | migrations.AddField( 23 | model_name='identity', 24 | name='is_dead', 25 | field=models.BooleanField(default=True), 26 | ), 27 | migrations.RunPython(set_my_defaults, migrations.RunPython.noop), 28 | ] 29 | -------------------------------------------------------------------------------- /privacymail/identity/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/identity/migrations/__init__.py -------------------------------------------------------------------------------- /privacymail/identity/models.py: -------------------------------------------------------------------------------- 1 | # Create your models here. 2 | -------------------------------------------------------------------------------- /privacymail/identity/models/ServiceThirdPartyEmbeds.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import names 4 | from django.apps import apps 5 | from django.contrib.postgres.fields import ArrayField 6 | from django.core import exceptions 7 | from django.db import models 8 | from django_countries.fields import CountryField 9 | from identity.util import convertForJsonResponse 10 | from identity.models import Service 11 | from model_utils import Choices 12 | 13 | 14 | class ServiceThirdPartyEmbeds(models.Model): 15 | STATIC = "STATIC" 16 | ONVIEW = "ONVIEW" 17 | ONCLICK = "ONCLICK" 18 | UNDETERMINED = "UNDETERMINED" 19 | 20 | EMBED_TYPES = Choices( 21 | (STATIC, "static"), 22 | (ONVIEW, "onView"), 23 | (ONCLICK, "onClick"), 24 | (UNDETERMINED, "undetermined"), 25 | ) 26 | 27 | service = models.ForeignKey( 28 | Service, related_name="embeds", on_delete=models.SET_NULL, null=True 29 | ) 30 | thirdparty = models.ForeignKey( 31 | "mailfetcher.Thirdparty", 32 | related_name="embeds", 33 | on_delete=models.SET_NULL, 34 | null=True, 35 | blank=True, 36 | ) 37 | leaks_address = models.BooleanField(default=False) 38 | embed_type = models.CharField( 39 | choices=EMBED_TYPES, default=EMBED_TYPES.UNDETERMINED, max_length=20 40 | ) 41 | mail = models.ForeignKey("mailfetcher.Mail", on_delete=models.CASCADE, null=True) 42 | sets_cookie = models.BooleanField(default=False) 43 | receives_identifier = models.BooleanField(default=False) 44 | 45 | def toJSON(self): 46 | return { 47 | "service": convertForJsonResponse(self.service), 48 | "thirdparty": convertForJsonResponse(self.thirdparty), 49 | "leaks_address": convertForJsonResponse(self.leaks_address), 50 | "embed_type": convertForJsonResponse(self.embed_type), 51 | "mail": convertForJsonResponse(self.mail), 52 | "sets_cookie": convertForJsonResponse(self.sets_cookie), 53 | "receives_identifier": convertForJsonResponse(self.receives_identifier), 54 | } 55 | -------------------------------------------------------------------------------- /privacymail/identity/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .Service import Service 2 | from .Identity import Identity 3 | from .ServiceThirdPartyEmbeds import ServiceThirdPartyEmbeds -------------------------------------------------------------------------------- /privacymail/identity/rating/ABTesting.py: -------------------------------------------------------------------------------- 1 | from identity.rating.calculate import scaleToRating 2 | 3 | from identity.models import Identity 4 | from mailfetcher.models import Mail 5 | 6 | def calculateABTesting(service, weight,rMin, rMax): 7 | idents = Identity.objects.filter(service=service) 8 | 9 | if Mail.objects.filter( 10 | identity__in=idents, 11 | identity__approved=True, 12 | possible_AB_testing=True 13 | ).exists(): 14 | rating = 1 15 | else: 16 | rating = 0 17 | 18 | return { 19 | "weight": weight, 20 | "rating": scaleToRating(rating, rMax), 21 | } 22 | -------------------------------------------------------------------------------- /privacymail/identity/rating/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/identity/rating/__init__.py -------------------------------------------------------------------------------- /privacymail/identity/rating/emailLeaks.py: -------------------------------------------------------------------------------- 1 | from identity.rating.calculate import scaleToRating, calculateRating 2 | from identity.models import Identity 3 | 4 | def calculateSpam(service): 5 | if Identity.objects.filter(service=service, receives_third_party_spam=True).count() > 0: 6 | return 1 7 | else: 8 | return 0 9 | 10 | 11 | def calculateEmailLeaksThirdparties( 12 | embeds, 13 | ): # TODO Sicherstellen, dass das so auch richtig ist 14 | if embeds.filter(leaks_address=True).exists(): 15 | return 1 16 | else: 17 | return 0 18 | 19 | 20 | def calculateEmailLeaks(service, embeds, weights, rMin, rMax): 21 | categories = { 22 | "spam": { 23 | "rating": scaleToRating(calculateSpam(service), rMax["spam"]), 24 | "weight": weights["spam"], 25 | }, 26 | "emailLeaks": { 27 | "rating": scaleToRating( 28 | calculateEmailLeaksThirdparties(embeds), rMax["thirdparties"] 29 | ), 30 | "weight": weights["thirdparties"], 31 | }, 32 | } 33 | 34 | return calculateRating(categories) 35 | -------------------------------------------------------------------------------- /privacymail/identity/rating/loadedResources.py: -------------------------------------------------------------------------------- 1 | from identity.rating.calculate import scaleToRating, countToRating 2 | 3 | from identity.util import filterDict 4 | 5 | from identity.models import ServiceThirdPartyEmbeds 6 | from django.db.models import Q 7 | 8 | def CDNs(embeds, service, rMin, rMax): 9 | return countToRating( 10 | embeds.filter( 11 | Q(embed_type=ServiceThirdPartyEmbeds.ONVIEW) & 12 | ((~Q(thirdparty__sector="tracker") & ~Q(thirdparty__sector="unkown")) | Q(thirdparty__name = service.name)) 13 | ).count(), 14 | rMin, 15 | rMax, 16 | ) 17 | 18 | 19 | def calculateCDNs(embeds, service, weight, rMin, rMax): 20 | return { 21 | "weight": weight, 22 | "rating": scaleToRating(CDNs(embeds,service, rMin, rMax), rMax), 23 | } 24 | -------------------------------------------------------------------------------- /privacymail/identity/rating/personalizedLinks.py: -------------------------------------------------------------------------------- 1 | from identity.rating.calculate import ( 2 | scaleToRating, 3 | calculateRating, 4 | countToRating, 5 | ) 6 | from django.db.models import Q 7 | from identity.util import filterDict 8 | from identity.models import ServiceThirdPartyEmbeds 9 | 10 | 11 | def calculatePersonalizedLinksToOwnWebsite(embeds,service, rMin, rMax): 12 | if embeds.filter( 13 | (Q(embed_type=ServiceThirdPartyEmbeds.ONCLICK) | Q(embed_type=ServiceThirdPartyEmbeds.STATIC)) & 14 | Q(thirdparty__name=service.name) & 15 | Q(receives_identifier=True) 16 | ).count() >= 1: 17 | return 1 18 | else: 19 | return 0 20 | 21 | 22 | def calculatePersonalizedLinksThirdParties( 23 | embeds,service, rMin, rMax 24 | ): # TODO should I filter the links to the newsletters own site? 25 | return countToRating( 26 | embeds.filter( 27 | (Q(embed_type=ServiceThirdPartyEmbeds.ONCLICK) | Q(embed_type=ServiceThirdPartyEmbeds.STATIC)) & 28 | ~Q(thirdparty__name=service.name) & 29 | Q(receives_identifier=True) 30 | ).count(), 31 | rMin, 32 | rMax, 33 | ) 34 | 35 | 36 | def calculatePersonalizedLinks(embeds,service, weights, rMin, rMax): 37 | categories = { 38 | "toOwnWebsite": { 39 | "rating": scaleToRating( 40 | calculatePersonalizedLinksToOwnWebsite( 41 | embeds,service, rMin["toOwnWebsite"], rMax["toOwnWebsite"], 42 | ), 43 | rMax["toOwnWebsite"], 44 | ), 45 | "weight": weights["toOwnWebsite"], 46 | }, 47 | "toThirdParties": { 48 | "rating": scaleToRating( 49 | calculatePersonalizedLinksThirdParties( 50 | embeds,service, rMin["toThirdParties"], rMax["toThirdParties"] 51 | ), 52 | rMax["toThirdParties"], 53 | ), 54 | "weight": weights["toThirdParties"], 55 | }, 56 | } 57 | 58 | return calculateRating(categories) 59 | -------------------------------------------------------------------------------- /privacymail/identity/rating/trackingServices.py: -------------------------------------------------------------------------------- 1 | from identity.rating.calculate import ( 2 | scaleToRating, 3 | calculateRating, 4 | countToRating, 5 | ) 6 | from identity.util import filterDict 7 | from django.core.cache import cache 8 | from django.db.models import Q 9 | from mailfetcher.models import Thirdparty 10 | 11 | from identity.models import ServiceThirdPartyEmbeds, Service 12 | 13 | def highNumber(embeds, service, rMin, rMax): 14 | return countToRating( 15 | embeds.filter( 16 | ((Q(embed_type=ServiceThirdPartyEmbeds.ONVIEW) & 17 | (Q(thirdparty__sector="tracker") | Q(thirdparty__sector="unkown"))) | 18 | (Q(embed_type=ServiceThirdPartyEmbeds.ONCLICK) & 19 | (Q(thirdparty__sector="tracker") | Q(thirdparty__sector="unkown"))) & 20 | Q(thirdparty__name = service.name)) 21 | ).count(), 22 | rMin, 23 | rMax, 24 | ) 25 | 26 | def trackers(embeds,service, rMin, rMax): 27 | tracker_embeds = embeds.filter( 28 | ((Q(embed_type=ServiceThirdPartyEmbeds.ONVIEW) & 29 | (Q(thirdparty__sector="tracker") | Q(thirdparty__sector="unkown"))) | 30 | (Q(embed_type=ServiceThirdPartyEmbeds.ONCLICK) & 31 | (Q(thirdparty__sector="tracker") | Q(thirdparty__sector="unkown"))) & 32 | Q(thirdparty__name = service.name)) 33 | ).distinct(), 34 | big = 0 35 | small = 0 36 | 37 | for tracker_embed in tracker_embeds[0]: 38 | if Service.objects.filter(thirdparties=tracker_embed.thirdparty).count() > 10 : 39 | big=big+1 40 | else : 41 | small=small+1 42 | 43 | return countToRating( 44 | big * 2 + small, 45 | rMin, 46 | rMax, 47 | ) 48 | 49 | 50 | def calculateTrackingServices(embeds, service, weights, rMin, rMax): 51 | return { 52 | "weight": weights["bigTrackers"], 53 | "rating": scaleToRating( 54 | trackers(embeds, service, rMin["smallTrackers"], rMax["bigTrackers"]), 55 | rMax["bigTrackers"], 56 | ), 57 | } 58 | -------------------------------------------------------------------------------- /privacymail/identity/tables.py: -------------------------------------------------------------------------------- 1 | import django_tables2 as tables 2 | from django_tables2.columns import LinkColumn, Column 3 | from django_tables2.utils import A 4 | from identity.models import Service 5 | 6 | 7 | class ServiceTable(tables.Table): 8 | name = LinkColumn('Service', args=[A('pk')]) 9 | country_of_origin = Column() 10 | 11 | class Meta: 12 | model = Service 13 | fields = ('name', 'country_of_origin', 'sector') 14 | -------------------------------------------------------------------------------- /privacymail/identity/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/identity/templatetags/__init__.py -------------------------------------------------------------------------------- /privacymail/identity/templatetags/tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.inclusion_tag('check.html') 7 | def show_check(check): 8 | assert check.is_sane() 9 | return { 10 | "id": check.get_id(), 11 | "title": check.get_title(), 12 | "description": check.get_description(), 13 | "reliability": check.get_reliability(), 14 | "status": check.get_status(), 15 | "interpretation": check.get_interpretation(), 16 | "condition": check.get_condition(), 17 | "error": check.get_error(), 18 | "add_data": check.get_additional_data(), 19 | "display": check.should_display() 20 | } 21 | 22 | 23 | class DetailItem(): 24 | text = "" 25 | icons = [] # Each icon is specified as {"icon": "fa-icon-ident", "tooltip": "tooltip text"} 26 | link = "" 27 | properties = [] # Additional properties of the item 28 | 29 | def __init__(self, text, link, icons=[], properties=[]): 30 | self.text = text 31 | self.link = link 32 | self.icons = icons 33 | self.properties = properties 34 | 35 | 36 | @register.inclusion_tag('detail.html') 37 | def show_list_details(item): 38 | return { 39 | "text": item.text, 40 | "link": item.link, 41 | "icons": item.icons, 42 | "properties": item.properties 43 | } 44 | -------------------------------------------------------------------------------- /privacymail/identity/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/mailfetcher/__init__.py -------------------------------------------------------------------------------- /privacymail/mailfetcher/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from mailfetcher.models import Mail, Eresource, Thirdparty 3 | 4 | # Register your models here. 5 | admin.site.register(Mail) 6 | admin.site.register(Eresource) 7 | admin.site.register(Thirdparty) 8 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MailfetcherConfig(AppConfig): 5 | name = 'mailfetcher' 6 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/crons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/mailfetcher/crons/__init__.py -------------------------------------------------------------------------------- /privacymail/mailfetcher/crons/mailCrawler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/mailfetcher/crons/mailCrawler/__init__.py -------------------------------------------------------------------------------- /privacymail/mailfetcher/crons/mailCrawler/analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/mailfetcher/crons/mailCrawler/analysis/__init__.py -------------------------------------------------------------------------------- /privacymail/mailfetcher/crons/mailCrawler/analysis/leakage.py: -------------------------------------------------------------------------------- 1 | from mailfetcher.models import Mail, Eresource 2 | 3 | 4 | def analyze_mail_connections_for_leakage(mail): 5 | hashdict = None 6 | all_eresources = Eresource.objects.filter(mail=mail).exclude( 7 | possible_unsub_link=True 8 | ) 9 | if mail.h_x_original_to is None: 10 | print("Did not find mailaddress. Mail: {}".format(mail)) 11 | return 12 | hashdict = Mail.generate_match_dict(mail.h_x_original_to) 13 | for eresource in all_eresources: 14 | Mail.analyze_eresource(eresource, hashdict) 15 | 16 | 17 | def analyze_single_mail_for_leakage(h_x_original_to, eresources): 18 | hashdict = Mail.generate_match_dict(h_x_original_to) 19 | for eresource in eresources: 20 | analyze_eresource(eresource, hashdict) 21 | return eresources 22 | 23 | 24 | def analyze_eresource(eresource, hashdict): 25 | for key, val in hashdict.items(): 26 | if ( 27 | str(val) in eresource["url"] 28 | or str(val).casefold() in eresource["url"].replace("-", "").casefold() 29 | ): 30 | if "mail_leakage" not in eresource or eresource["mail_leakage"] is None: 31 | eresource["mail_leakage"] = key 32 | else: 33 | if key in eresource["mail_leakage"]: 34 | continue 35 | eresource["mail_leakage"] = eresource["mail_leakage"] + ", " + key -------------------------------------------------------------------------------- /privacymail/mailfetcher/crons/mailCrawler/getUnfinishedMailCount.py: -------------------------------------------------------------------------------- 1 | from mailfetcher.models import Mail 2 | 3 | from django.conf import settings 4 | 5 | # Check whether there are too many mail in the database waiting to be processed. 6 | def getUnfinishedMailCount(): 7 | viewed_mails = ( 8 | Mail.objects.filter(processing_state=Mail.PROCESSING_STATES.VIEWED) 9 | .exclude(processing_fails__gte=settings.OPENWPM_RETRIES) 10 | .count() 11 | ) 12 | clicked_mails = ( 13 | Mail.objects.filter(processing_state=Mail.PROCESSING_STATES.LINK_CLICKED) 14 | .exclude(processing_fails__gte=settings.OPENWPM_RETRIES) 15 | .count() 16 | ) 17 | unprocessed_mails = ( 18 | Mail.objects.filter(processing_state=Mail.PROCESSING_STATES.UNPROCESSED) 19 | .exclude(processing_fails__gte=settings.OPENWPM_RETRIES) 20 | .count() 21 | ) 22 | failed_mails = Mail.objects.filter( 23 | processing_fails__gte=settings.OPENWPM_RETRIES 24 | ).count() 25 | 26 | unfinished_mail_count = viewed_mails + clicked_mails + unprocessed_mails 27 | 28 | print( 29 | "{} unprocessed mails in database. Additional {} mails are in failed state".format( 30 | unfinished_mail_count, failed_mails 31 | ) 32 | ) 33 | print( 34 | "{} unprocessed, {} viewed and {} link_clicked.".format( 35 | unprocessed_mails, viewed_mails, clicked_mails 36 | ) 37 | ) 38 | 39 | return unfinished_mail_count -------------------------------------------------------------------------------- /privacymail/mailfetcher/crons/mailCrawler/init.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import http.server 3 | import socket 4 | from contextlib import closing 5 | from django.core.cache import cache 6 | 7 | 8 | def check_socket(host, port): 9 | with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: 10 | if sock.connect_ex((host, port)) == 0: 11 | return True 12 | else: 13 | return False 14 | 15 | # This initizes the cronjob server 16 | def init(): 17 | cache.delete("ImapFetcher") 18 | 19 | PORT = 5000 20 | DIRECTORY = "/tmp/" 21 | class Handler(http.server.SimpleHTTPRequestHandler): 22 | def __init__(self, *args, **kwargs): 23 | super().__init__(*args, directory=DIRECTORY, **kwargs) 24 | 25 | if not check_socket("127.0.0.1", PORT): 26 | server = http.server.HTTPServer(("127.0.0.1", PORT), Handler) 27 | return server, startThread(server) 28 | else: 29 | return None, None 30 | 31 | def startThread(server): 32 | # This function serves email messages as small websites 33 | # create a dummy favicon.ico 34 | open("/tmp/favicon.ico", "a").close() 35 | thread = threading.Thread(target=server.serve_forever) 36 | thread.deamon = True 37 | thread.start() 38 | print("--- WEB Server started on port 5000 ---") 39 | 40 | return thread -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0002_thirdparty_resultsdirty.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2019-03-15 08:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ('mailfetcher', '0001_initial'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='thirdparty', 14 | name='resultsdirty', 15 | field=models.BooleanField(default=True), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0003_auto_20190326_1054.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-26 10:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0002_thirdparty_resultsdirty'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='eresource', 15 | name='mail_leakage', 16 | field=models.CharField(blank=True, max_length=200, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0004_auto_20190326_1106.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-26 11:06 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0003_auto_20190326_1054'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='eresource', 15 | name='mail_leakage', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0005_eresource_personalised.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-01 15:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0004_auto_20190326_1106'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='eresource', 15 | name='personalised', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0006_mail_possible_ab_testing.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-01 22:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0005_eresource_personalised'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='mail', 15 | name='possible_AB_testing', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0007_auto_20190402_0727.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-02 07:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0006_mail_possible_ab_testing'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='eresource', 15 | name='type', 16 | field=models.CharField(choices=[('a', 'Link'), ('img', 'Image'), ('con', 'Connection'), ('con_click', 'Connection_clicked'), ('link', 'css'), ('script', 'JavaScript')], max_length=50), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0008_mail_contains_javascript.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-02 16:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0007_auto_20190402_0727'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='mail', 15 | name='contains_javascript', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0009_thirdparty_metadata.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-05 13:54 2 | 3 | from django.db import migrations, models 4 | import django_countries.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('mailfetcher', '0008_mail_contains_javascript'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='thirdparty', 16 | name='country_of_origin', 17 | field=django_countries.fields.CountryField(blank=True, max_length=2), 18 | ), 19 | migrations.AddField( 20 | model_name='thirdparty', 21 | name='sector', 22 | field=models.CharField(choices=[('tracker', 'Tracking / Advertising'), ('cdn', 'Hosting / Content Distribution'), ('altdomain', 'Alternative Domain of first party'), ('unknown', 'Unknown')], default='unknown', max_length=30), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0010_thirdparty_service.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-08 09:29 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('identity', '0011_service_add_advertising_option'), 11 | ('mailfetcher', '0009_thirdparty_metadata'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='thirdparty', 17 | name='service', 18 | field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='identity.Service'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0011_thirdparty_add_connections_to_service.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-04-08 13:21 2 | 3 | from django.db import migrations 4 | from django.core.exceptions import ObjectDoesNotExist 5 | 6 | 7 | def forward_func(apps, schema_editor): 8 | # Function that will be run when the migration is run forward. 9 | Service = apps.get_model("identity", "Service") 10 | Thirdparty = apps.get_model("mailfetcher", "Thirdparty") 11 | 12 | for tp in Thirdparty.objects.all(): 13 | try: 14 | ser = Service.objects.get(url=tp.host) 15 | tp.service = ser 16 | tp.save() 17 | except ObjectDoesNotExist: 18 | continue 19 | 20 | 21 | def reverse_func(apps, schema_editor): 22 | # Function that will be run when the migration is reversed for some reason. 23 | Thirdparty = apps.get_model("mailfetcher", "Thirdparty") 24 | for tp in Thirdparty.objects.all(): 25 | tp.service = None 26 | tp.save() 27 | 28 | 29 | class Migration(migrations.Migration): 30 | 31 | dependencies = [ 32 | ('mailfetcher', '0010_thirdparty_service'), 33 | ] 34 | 35 | operations = [ 36 | migrations.RunPython(forward_func, reverse_func), 37 | ] 38 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0012_auto_20201125_1250.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-25 12:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0011_thirdparty_add_connections_to_service'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='mail', 15 | name='h_subject', 16 | field=models.CharField(blank=True, max_length=5000, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0013_auto_20201126_1225.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-11-26 12:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0012_auto_20201125_1250'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='mail', 15 | name='h_bcc', 16 | field=models.CharField(blank=True, max_length=500, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='mail', 20 | name='h_cc', 21 | field=models.CharField(blank=True, max_length=500, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='mail', 25 | name='h_date', 26 | field=models.CharField(blank=True, max_length=500, null=True), 27 | ), 28 | migrations.AlterField( 29 | model_name='mail', 30 | name='h_from', 31 | field=models.CharField(blank=True, max_length=500, null=True), 32 | ), 33 | migrations.AlterField( 34 | model_name='mail', 35 | name='h_to', 36 | field=models.CharField(blank=True, max_length=500, null=True), 37 | ), 38 | migrations.AlterField( 39 | model_name='mail', 40 | name='h_user_agent', 41 | field=models.CharField(blank=True, max_length=500, null=True), 42 | ), 43 | migrations.AlterField( 44 | model_name='mail', 45 | name='h_x_original_to', 46 | field=models.CharField(blank=True, max_length=500, null=True), 47 | ), 48 | migrations.AlterField( 49 | model_name='thirdparty', 50 | name='host', 51 | field=models.CharField(max_length=500, unique=True), 52 | ), 53 | migrations.AlterField( 54 | model_name='thirdparty', 55 | name='name', 56 | field=models.CharField(max_length=500), 57 | ), 58 | ] 59 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0014_scanword.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2021-02-24 15:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0013_auto_20201126_1225'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Scanword', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('type', models.CharField(max_length=50)), 18 | ('word', models.TextField(max_length=2000)), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/0015_auto_20210120_1042.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2021-01-20 10:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('mailfetcher', '0014_scanword'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='mail', 15 | name='cached', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='mail', 20 | name='similarity_processed', 21 | field=models.BooleanField(default=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/mailfetcher/migrations/__init__.py -------------------------------------------------------------------------------- /privacymail/mailfetcher/models/Scanword.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from django.db import models 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class Scanword(models.Model): 8 | type = models.CharField(max_length=50) 9 | word = models.TextField(max_length=2000) 10 | 11 | def __str__(self): 12 | return f"({self.type}): {self.word}" 13 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .Mail import Mail 2 | from .Thirdparty import Thirdparty 3 | from .Eresource import Eresource 4 | from .Scanword import Scanword 5 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/templates/identity_approval_mail.txt: -------------------------------------------------------------------------------- 1 | The identity {{ ident }} from service {{ ident.service }} is not approved yet, but received mail. 2 | Please check for approval on here: {{URL}}/mail/{{ mail.id }} 3 | Thanks. -------------------------------------------------------------------------------- /privacymail/mailfetcher/templates/mailfetcher/confirm.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | {% load bootstrap4 %} 3 | 4 | 5 | 6 | Confirm Identities - Privacymail 7 | 8 | {% bootstrap_css %} 9 | 10 | 11 |

Unregistered Identities:

12 | {% for identity in identities %} 13 | {% if identity.message.count > 0 %} 14 |
15 |
{% csrf_token %} 16 | 17 | 18 | 19 |
20 |

{{ identity.service.name }}

21 | 22 |
23 |
24 |
25 | From: {{ identity.message.first.h_from }}
26 | To: {{ identity.message.first.h_to }}
27 | X-Original-To: {{ identity.message.first.h_x_original_to }}
28 | CC: {{ identity.message.first.h_cc }}
29 | BCC: {{ identity.message.first.h_bcc }}
30 | Subject: {{ identity.message.first.h_subject }}
31 | Date: {{ identity.message.first.h_date }}
32 | Message: 33 | 42 |
43 | {{ identity.message.first.get_cleartext|linebreaks }} 44 |
45 |
46 |
47 | {% endif %} 48 | {% endfor %} 49 | {% bootstrap_javascript jquery='full' %} 50 | 51 | 52 | -------------------------------------------------------------------------------- /privacymail/mailfetcher/templates/third_party_spam.txt: -------------------------------------------------------------------------------- 1 | The identity from {{ ident.service }} may have received third-party spam. 2 | Please check for approval on here: {{URL}}/mail/{{ mail.id }} 3 | Thanks. -------------------------------------------------------------------------------- /privacymail/mailfetcher/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from mailfetcher.models import Mail, Scanword 3 | from identity.models import Identity, Service 4 | import email 5 | 6 | 7 | class AutoConfirm(TestCase): 8 | def setUp(self): 9 | """ 10 | create a service and a identity 11 | """ 12 | service = Service.create('nbcnews.com', 'NBCNews') 13 | identity = Identity.create(service, 'newsletterme.de') 14 | identity.surname = "cartier" 15 | identity.first_name = "cathy" 16 | identity.mail = 'cathy.cartier@newsletterme.de' 17 | identity.save() 18 | 19 | Scanword.objects.create(type="word", word="Subscribe") 20 | 21 | def test_auto_approval(self): 22 | mail = b'' 23 | with open('./mailfetcher/testdata/mail_approvable.eml', 'rb') as f: 24 | mail = f.read() 25 | 26 | mail = Mail.create(email.message_from_bytes(mail)) 27 | 28 | self.assertIsNotNone(mail) 29 | self.assertTrue(mail.identity.first().approved) 30 | 31 | def test_no_approval(self): 32 | mail = b'' 33 | with open('./mailfetcher/testdata/mail_unapprovable.eml', 'rb') as f: 34 | mail = f.read() 35 | 36 | mail = Mail.create(email.message_from_bytes(mail)) 37 | 38 | self.assertIsNotNone(mail) 39 | self.assertFalse(mail.identity.first().approved) 40 | 41 | def test_second_mail_no_approval(self): 42 | mail1 = b'' 43 | with open('./mailfetcher/testdata/mail_unapprovable.eml', 'rb') as f: 44 | mail1 = f.read() 45 | 46 | mail1 = Mail.create(email.message_from_bytes(mail1)) 47 | 48 | mail2 = b'' 49 | with open('./mailfetcher/testdata/mail_approvable.eml', 'rb') as f: 50 | mail2 = f.read() 51 | 52 | mail2 = Mail.create(email.message_from_bytes(mail2)) 53 | 54 | self.assertIsNotNone(mail1) 55 | self.assertIsNotNone(mail2) 56 | self.assertFalse(mail2.identity.first().approved) 57 | -------------------------------------------------------------------------------- /privacymail/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "privacymail.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /privacymail/privacymail/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/privacymail/__init__.py -------------------------------------------------------------------------------- /privacymail/privacymail/env.example: -------------------------------------------------------------------------------- 1 | # copy this file to .env in the same folder and input you config 2 | SECRET_KEY="super-secret-key" 3 | 4 | APPLICATION_DEBUG=False 5 | 6 | DATABASE_NAME=privacymail 7 | DATABASE_USER=privacymail 8 | DATABASE_PASSWORD=privacymail 9 | 10 | DATABASE_HOST=localhost 11 | DATABASE_PORT=5432 12 | 13 | RAVEN_DSN=https://my-raven-dsn.com 14 | 15 | MAIL_NEWSLETTER_USERNAME=test 16 | MAIL_NEWSLETTER_PASSWORD=test 17 | MAIL_PRIVAYCLETTER_USERNAME=test 18 | MAIL_PRIVAYCLETTER_PASSWORD=test 19 | MAIL_PRIVACYMAIL_USERNAME=test 20 | MAIL_PRIVACYMAIL_PASSWORD=test 21 | 22 | ALLOWED_HOST=localhost 23 | MAXIMUM_ALLOWED_EMAIL_ANALYSIS_ONDEMAND = 10 -------------------------------------------------------------------------------- /privacymail/privacymail/urls.py: -------------------------------------------------------------------------------- 1 | """privacymail URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.views.generic import TemplateView 18 | from django.urls import path, re_path, include 19 | from mailfetcher.views import confirmview, wordlistmanager, mailview 20 | 21 | urlpatterns = [ 22 | path('admin/confirm', confirmview), 23 | path('admin/mail/', mailview), 24 | path('admin/wordlist', wordlistmanager), 25 | path('admin/', admin.site.urls), 26 | path('api/', include('api.urls')), 27 | re_path(r'^', TemplateView.as_view(template_name='index.html'), name="ReactApp"), 28 | ] 29 | -------------------------------------------------------------------------------- /privacymail/privacymail/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for privacymail project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "privacymail.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /privacymail/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/util/__init__.py -------------------------------------------------------------------------------- /privacymail/util/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /privacymail/util/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ManagementConfig(AppConfig): 5 | name = 'management' 6 | -------------------------------------------------------------------------------- /privacymail/util/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/util/management/__init__.py -------------------------------------------------------------------------------- /privacymail/util/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/util/management/commands/__init__.py -------------------------------------------------------------------------------- /privacymail/util/management/commands/analysedirty.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty 5 | import multiprocessing 6 | from itertools import repeat 7 | from mailfetcher.analyser_cron import ( 8 | create_service_cache, 9 | create_summary_cache, 10 | create_third_party_cache, 11 | analyse_dirty_services, 12 | multiprocessing_create_service_cache, 13 | ) 14 | from django.db import connections 15 | 16 | 17 | class Command(BaseCommand): 18 | def handle(self, *args, **kwargs): 19 | 20 | cache.clear() 21 | self.stdout.write("Analysing dirty Services\n") 22 | 23 | # create_summary_cache(force=True) 24 | 25 | analyse_dirty_services() 26 | 27 | print("Done") 28 | -------------------------------------------------------------------------------- /privacymail/util/management/commands/benchmark.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty 5 | from mailfetcher.analyser_cron import create_service_cache, create_summary_cache, create_third_party_cache 6 | import time 7 | import statistics 8 | class Command(BaseCommand): 9 | def handle(self, *args, **kwargs): 10 | print("Services") 11 | t = time.time() 12 | service1 = Service.objects.filter(id=1)[0] 13 | #print(service1.mails_not_cached().count() 14 | create_service_cache(service1, force=True) 15 | t2 = time.time() 16 | print(t2-t) 17 | print('Done') 18 | -------------------------------------------------------------------------------- /privacymail/util/management/commands/clearcache.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | 4 | 5 | class Command(BaseCommand): 6 | def handle(self, *args, **kwargs): 7 | cache.clear() 8 | self.stdout.write('Cleared cache\n') 9 | -------------------------------------------------------------------------------- /privacymail/util/management/commands/inspectcache.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | from django.core.cache import cache 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty 5 | from pprint import pprint 6 | 7 | 8 | class Command(BaseCommand): 9 | def add_arguments(self, parser): 10 | parser.add_argument('id', nargs='+', type=int) 11 | parser.add_argument('--service', action="store_true", dest="service", help="Show cache for specified service") 12 | parser.add_argument('--embed', action="store_true", dest="embed", help="Show cache for specified embedded domain") 13 | 14 | def handle(self, *args, **options): 15 | if options['service']: 16 | for sid in options['id']: 17 | try: 18 | service = Service.objects.get(pk=sid) 19 | c = cache.get(service.derive_service_cache_path()) 20 | pprint(c) 21 | except Service.DoesNotExist: 22 | raise CommandError('Service %s does not exist' % sid) 23 | elif options['embed']: 24 | for sid in options['id']: 25 | try: 26 | embed = Thirdparty.objects.get(pk=sid) 27 | c = cache.get(embed.derive_thirdparty_cache_path()) 28 | pprint(c) 29 | except Service.DoesNotExist: 30 | raise CommandError('Embed %s does not exist' % sid) 31 | else: 32 | raise CommandError("Must specify type of cache entry to look up (e.g. --service, --embed, ...)") 33 | -------------------------------------------------------------------------------- /privacymail/util/management/commands/reanalyse_eresources.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty, Mail, Eresource 5 | from mailfetcher.analyser_cron import thesis_link_personalisation_of_services 6 | from identity.views import ServiceView 7 | import traceback 8 | from mailfetcher.crons.mailCrawler.analysis.leakage import ( 9 | analyze_mail_connections_for_leakage, 10 | ) 11 | 12 | 13 | class Command(BaseCommand): 14 | def handle(self, *args, **kwargs): 15 | try: 16 | print("Reanalyzing Database.") 17 | mail_queue = Mail.objects.filter( 18 | processing_state=Mail.PROCESSING_STATES.DONE 19 | ) 20 | print( 21 | '(Re-)analyzing {} mails with state "done".'.format(mail_queue.count()) 22 | ) 23 | count = 0 24 | for mail in mail_queue: 25 | count += 1 26 | for eresource in mail.eresource_set.all(): 27 | if eresource.mail_leakage is None: 28 | continue 29 | eresource.mail_leakage = None 30 | eresource.save() 31 | analyze_mail_connections_for_leakage(mail) 32 | mail.processing_state = Mail.PROCESSING_STATES.DONE 33 | mail.save() 34 | mail.create_service_third_party_connections() 35 | if count % 20 == 0: 36 | print(count) 37 | print("All done. Exiting.") 38 | except Exception: 39 | traceback.print_exc() 40 | 41 | print("Done") 42 | -------------------------------------------------------------------------------- /privacymail/util/management/commands/redocache.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty 5 | import multiprocessing 6 | from itertools import repeat 7 | from mailfetcher.analyser_cron import ( 8 | create_service_cache, 9 | create_summary_cache, 10 | create_third_party_cache, 11 | multiprocessing_create_service_cache, 12 | multiprocessing_create_thirdparty_cache 13 | ) 14 | from django.db import connections 15 | import time 16 | class Command(BaseCommand): 17 | def handle(self, *args, **kwargs): 18 | t1 = time.time() 19 | cache.clear() 20 | self.stdout.write("Cleared cache\n") 21 | 22 | # create_summary_cache(force=True) 23 | if multiprocessing.cpu_count() > 3: 24 | cpus = multiprocessing.cpu_count() - 3 25 | else: 26 | cpus = 1 27 | 28 | with multiprocessing.Pool(cpus) as p: 29 | p.map(multiprocessing_create_service_cache, Service.objects.all()) 30 | p.map(multiprocessing_create_thirdparty_cache, Thirdparty.objects.all()) 31 | t2 = time.time() 32 | print(t2 - t1) 33 | print("Done") 34 | -------------------------------------------------------------------------------- /privacymail/util/management/commands/reset_queue.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | from identity.models import Service, Identity 4 | from mailfetcher.analyser_cron import ( 5 | create_service_cache, 6 | analyse_dirty_services, 7 | create_third_party_cache, 8 | analyze_differences_between_similar_mails, 9 | ) 10 | 11 | from mailfetcher.models import Thirdparty, Mail, Eresource 12 | from identity.views import ServiceView 13 | import traceback 14 | import time 15 | 16 | 17 | class Command(BaseCommand): 18 | def handle(self, *args, **kwargs): 19 | cache.delete('onDemand_analysis_queue') 20 | print("Done") -------------------------------------------------------------------------------- /privacymail/util/management/commands/thesis_analyser.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.cache import cache 3 | from identity.models import Service 4 | from mailfetcher.models import Thirdparty, Mail, Eresource 5 | from mailfetcher import analyser_cron 6 | from identity.views import ServiceView 7 | import traceback 8 | 9 | # This command was mainly used to call functions of the analyser cron for the thesis evaluation. 10 | class Command(BaseCommand): 11 | def handle(self, *args, **kwargs): 12 | 13 | try: 14 | 15 | # analyser_cron.thesis_link_personalisation_of_services() 16 | # analyser_cron.any_connection_overview() 17 | # analyser_cron.services_setting_cookies() 18 | # analyser_cron.any_connection_overview() 19 | # analyser_cron.third_party_analization_general() 20 | # analyser_cron.analyse_contacted_domains_from_cache() 21 | analyser_cron.address_leakage_statistics() 22 | # analyser_cron.long_chains_calculation() 23 | # analyser_cron.analyse_ab_testing() 24 | # analyser_cron.general_statistics() 25 | # analyser_cron.embedded_type_statistics() 26 | 27 | except Exception: 28 | traceback.print_exc() 29 | 30 | print('Done') 31 | -------------------------------------------------------------------------------- /privacymail/util/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/util/migrations/__init__.py -------------------------------------------------------------------------------- /privacymail/util/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /privacymail/util/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /privacymail/util/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /privacymail/website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /privacymail/website/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | PrivacyMail 27 | 28 | 29 | 30 |
31 | 41 | 42 | -------------------------------------------------------------------------------- /privacymail/website/public/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/public/static/favicon.ico -------------------------------------------------------------------------------- /privacymail/website/public/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "PrivacyMail", 3 | "name": "PrivacyMail", 4 | "icons": [ 5 | { 6 | "src": "../src/assets/images/favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /privacymail/website/public/static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /privacymail/website/src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["airbnb", "plugin:@typescript-eslint/recommended"], 3 | parser: "@typescript-eslint/parser", 4 | plugins: ["@typescript-eslint", "prettier"], 5 | settings: { 6 | "import/parsers": { 7 | "@typescript-eslint/parser": [".ts", ".tsx"] 8 | }, 9 | "import/resolver": { 10 | typescript: {} 11 | } 12 | }, 13 | rules: { 14 | "react/jsx-filename-extension": [2, { extensions: [".js", ".jsx", ".ts", ".tsx"] }], 15 | "import/no-extraneous-dependencies": [2, { devDependencies: ["**/test.tsx", "**/test.ts"] }], 16 | "@typescript-eslint/indent": [2, 2] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/female.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/female.jpg -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/iconFont/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts 4 | 5 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 6 | 7 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 8 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/iconFont/demo-files/demo.js: -------------------------------------------------------------------------------- 1 | if (!("boxShadow" in document.body.style)) { 2 | document.body.setAttribute("class", "noBoxShadow"); 3 | } 4 | 5 | document.body.addEventListener("click", function(e) { 6 | var target = e.target; 7 | if (target.tagName === "INPUT" && target.getAttribute("class").indexOf("liga") === -1) { 8 | target.select(); 9 | } 10 | }); 11 | 12 | (function() { 13 | var fontSize = document.getElementById("fontSize"), 14 | testDrive = document.getElementById("testDrive"), 15 | testText = document.getElementById("testText"); 16 | function updateTest() { 17 | testDrive.innerHTML = testText.value || String.fromCharCode(160); 18 | if (window.icomoonLiga) { 19 | window.icomoonLiga(testDrive); 20 | } 21 | } 22 | function updateSize() { 23 | testDrive.style.fontSize = fontSize.value + "px"; 24 | } 25 | fontSize.addEventListener("change", updateSize, false); 26 | testText.addEventListener("input", updateTest, false); 27 | testText.addEventListener("change", updateTest, false); 28 | updateSize(); 29 | })(); 30 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/iconFont/fonts/PrivacyMail.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/iconFont/fonts/PrivacyMail.eot -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/iconFont/fonts/PrivacyMail.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/iconFont/fonts/PrivacyMail.ttf -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/iconFont/fonts/PrivacyMail.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/iconFont/fonts/PrivacyMail.woff -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/alternate_email-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/attach_money-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/cancel-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/check_circle-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/check_circle_fill-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/check_circle_outline-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/danger-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/de.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/done_all-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/email-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/expand-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/file_copy-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/home-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/how_to_reg-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/insert_link-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/mood-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/policy-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/public-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/question_answer-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/record_voice_over-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/remove_circle-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/repeat-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/school-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/search-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/share-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/icons/warning-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/letter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/letter.jpg -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/logo.png -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/male.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/male.jpg -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/onDemandInstructions/gmailCopyEmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/onDemandInstructions/gmailCopyEmail.png -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/onDemandInstructions/gmailViewSource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/onDemandInstructions/gmailViewSource.png -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/onDemandInstructions/thunderbirdCopyEmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/onDemandInstructions/thunderbirdCopyEmail.png -------------------------------------------------------------------------------- /privacymail/website/src/assets/images/onDemandInstructions/thunderbirdViewSource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacymail/PrivacyMail/23002c6037995f81a9964fd9337b80b36a4b0df7/privacymail/website/src/assets/images/onDemandInstructions/thunderbirdViewSource.png -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/collapsible.scss: -------------------------------------------------------------------------------- 1 | .collapsibleItem { 2 | border-top: $divider-border; 3 | .expandable { 4 | width: 24px; 5 | margin-left: auto; 6 | margin-right: 1rem; 7 | 8 | > div { 9 | transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1) 50ms; 10 | span { 11 | font-size: 1.5rem; 12 | } 13 | } 14 | .expanded { 15 | transform: rotate(180deg); 16 | } 17 | cursor: pointer; 18 | 19 | object { 20 | padding: auto; 21 | } 22 | } 23 | .collapsibleSmall { 24 | width: 100%; 25 | display: flex; 26 | align-items: center; 27 | margin: 1rem 0; 28 | } 29 | .collapsibleBig { 30 | margin: 0; 31 | border-top: $divider-border; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/colors.scss: -------------------------------------------------------------------------------- 1 | $header-color: #3471d6; 2 | $header-secondary-color: #dddddd; 3 | $button-color: #0047a4; 4 | $button-color-secondary: #717171; 5 | $font-color-primary: #444444; 6 | $font-color-secondary: #ffffff; 7 | $font-color-headline: #3c4858; 8 | $font-color-disabled: #717171; 9 | 10 | $methode-noProblem: #4caf50; 11 | $methode-problem: #f57c00; 12 | 13 | $tooltip-error: #f51d00; 14 | 15 | $divider-border: 1px solid #d6d6d6; 16 | $divider-border-big: 1px solid #717171; 17 | 18 | $analysisBackgroud: #f2f2f2; 19 | 20 | $warning: #ffd700; 21 | $info: #d8ecff; 22 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/grid.scss: -------------------------------------------------------------------------------- 1 | .grid { 2 | display: grid; 3 | grid-template-columns: repeat(12, 1fr); 4 | } 5 | .grid-item-1 { 6 | grid-column: span 1; 7 | } 8 | .grid-item-2 { 9 | grid-column: span 2; 10 | } 11 | .grid-item-3 { 12 | grid-column: span 3; 13 | } 14 | .grid-item-4 { 15 | grid-column: span 4; 16 | } 17 | .grid-item-5 { 18 | grid-column: span 5; 19 | } 20 | .grid-item-6 { 21 | grid-column: span 6; 22 | } 23 | .grid-item-7 { 24 | grid-column: span 7; 25 | } 26 | .grid-item-8 { 27 | grid-column: span 8; 28 | } 29 | .grid-item-9 { 30 | grid-column: span 9; 31 | } 32 | .grid-item-10 { 33 | grid-column: span 10; 34 | } 35 | .grid-item-11 { 36 | grid-column: span 11; 37 | } 38 | .grid-item-12, 39 | .grid-item { 40 | grid-column: span 12; 41 | } 42 | .grid-divider { 43 | @extend .grid-item; 44 | height: 2rem; 45 | } 46 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/home/details.scss: -------------------------------------------------------------------------------- 1 | .tracking { 2 | grid-area: tracking; 3 | } 4 | .whatWeDo { 5 | grid-area: whatWeDo; 6 | } 7 | .whyPrivacymail { 8 | grid-area: whyPrivacymail; 9 | } 10 | .detection { 11 | grid-area: detection; 12 | } 13 | .details { 14 | margin: 0 auto 0 auto; 15 | display: grid; 16 | grid-row-gap: 4rem; 17 | grid-column-gap: 4rem; 18 | padding: 1rem; 19 | 20 | @media only screen and (min-width: calc(500px + 2rem)) { 21 | width: calc((100% - (500px + 2rem)) * 0.4 + 500px); 22 | } 23 | @media only screen and (min-width: calc((#{$screen-break} + 50px - (500px )) *2.5 + (500px))) { 24 | width: $screen-break + 50px; 25 | } 26 | 27 | @media only screen and (min-width: 800px) { 28 | //medium sized screens 29 | grid-template-areas: 30 | "whatWeDo whatWeDo" 31 | "whyPrivacymail whyPrivacymail" 32 | "detection tracking"; 33 | } 34 | grid-template-areas: 35 | "whatWeDo" 36 | "whyPrivacymail" 37 | "detection" 38 | "tracking"; 39 | } 40 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/home/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | h1 { 3 | padding: 2rem 0 0 0; 4 | font-size: 2.5rem; 5 | margin: 0px; 6 | color: $font-color-secondary; 7 | } 8 | h2 { 9 | padding: 1rem 0 1.5rem 0; 10 | font-size: 1.75rem; 11 | margin: 0px; 12 | } 13 | h3 { 14 | @extend .medium; 15 | font-size: 1.2rem; 16 | padding-bottom: 0.5rem; 17 | margin: 0; 18 | } 19 | h4 { 20 | @extend .light; 21 | padding: 2rem 0 0 0; 22 | margin: 0px; 23 | color: $font-color-secondary; 24 | } 25 | h5 { 26 | @extend .medium; 27 | color: $font-color-primary; 28 | padding-bottom: 0.2rem; 29 | margin: 0; 30 | font-size: 2rem; 31 | } 32 | button { 33 | margin-top: 0.5rem; 34 | width: 100%; 35 | } 36 | p { 37 | margin: 0px; 38 | width: 100%; 39 | line-height: 1.3rem; 40 | } 41 | 42 | .statistic { 43 | margin-top: 3rem; 44 | text-align: center; 45 | p { 46 | margin-bottom: 0.7rem; 47 | } 48 | } 49 | .highlighted { 50 | font-size: 1.125rem; 51 | } 52 | 53 | .grid { 54 | grid-column-gap: 1rem; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/home/index.scss: -------------------------------------------------------------------------------- 1 | @import "./home.scss"; 2 | @import "./welcome.scss"; 3 | @import "./details.scss"; 4 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/home/welcome.scss: -------------------------------------------------------------------------------- 1 | .welcome { 2 | position: relative; 3 | max-height: 900px; 4 | overflow: hidden; 5 | 6 | z-index: 1; 7 | width: 100%; 8 | color: $font-color-secondary; 9 | 10 | > * { 11 | padding: 0 1rem; 12 | height: 100%; 13 | 14 | display: flex; 15 | flex-flow: column; 16 | 17 | margin: 0px auto 0px auto; 18 | 19 | @media only screen and (min-width: calc(500px + 2rem)) { 20 | width: calc((100% - (500px + 2rem)) * 0.4 + 500px); 21 | } 22 | @media only screen and (min-width: calc((#{$screen-break} + 50px - (500px )) *2.5 + (500px))) { 23 | width: $screen-break + 50px; 24 | } 25 | h4 { 26 | padding-top: 1rem; 27 | } 28 | > * { 29 | margin: 2rem 0 0 0; 30 | } 31 | .dynamics { 32 | min-height: 600px; 33 | margin-top: 0; 34 | display: flex; 35 | flex-grow: 1; 36 | flex-flow: column; 37 | justify-content: space-around; 38 | > * { 39 | } 40 | } 41 | h1 { 42 | padding: 0; 43 | } 44 | .input { 45 | margin: 1.5rem 0 1.5rem 0; 46 | input { 47 | margin-top: 2rem; 48 | } 49 | > * { 50 | width: 100%; 51 | } 52 | } 53 | } 54 | 55 | .onDemandLink { 56 | margin: 1.5rem 0 3rem 0; 57 | h2 { 58 | color: $font-color-secondary; 59 | //margin: 0.5rem 0; 60 | //padding: 0; 61 | padding: 0; 62 | } 63 | #onDemandButton { 64 | margin-top: 2rem; 65 | background: #007dc8; 66 | } 67 | } 68 | } 69 | .welcome::before { 70 | display: block; 71 | position: absolute; 72 | top: 0; 73 | bottom: 0; 74 | left: 0; 75 | right: 0; 76 | content: ""; 77 | z-index: -1; 78 | width: 100%; 79 | height: 100%; 80 | background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url("../../images/letter.jpg"); 81 | //background-attachment: fixed; 82 | background-position: center; 83 | background-repeat: no-repeat; 84 | background-size: cover; 85 | filter: blur(4px); 86 | 87 | transform: scale(1.05); 88 | } 89 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/iconList.scss: -------------------------------------------------------------------------------- 1 | .image-list { 2 | .icon { 3 | span { 4 | font-size: 3rem; 5 | vertical-align: middle; 6 | } 7 | } 8 | ul { 9 | margin: 0px; 10 | vertical-align: middle; 11 | list-style-type: none; 12 | padding: 0px; 13 | } 14 | li { 15 | @extend .normal; 16 | @extend .light; 17 | margin: 1rem 0; 18 | 19 | display: flex; 20 | align-items: center; 21 | 22 | h3 { 23 | font-size: 1.2rem; 24 | padding-bottom: 0.5rem; 25 | margin: 0; 26 | } 27 | .iconListContent { 28 | flex: 1 1 auto; 29 | padding: 0px 0px 0px 1rem; 30 | } 31 | p { 32 | margin: 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/identity/identity.scss: -------------------------------------------------------------------------------- 1 | .identity { 2 | a { 3 | text-decoration: underline !important; 4 | } 5 | h2 { 6 | margin-left: 1rem; 7 | } 8 | .stepperContent { 9 | position: relative; 10 | .start > div { 11 | display: flex; 12 | flex-flow: column; 13 | 14 | max-width: 400px; 15 | min-height: 400px; 16 | 17 | margin: 0 auto; 18 | padding: 0 1rem; 19 | 20 | div { 21 | flex: 1 0 auto; 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | } 27 | 28 | .instructions { 29 | display: flex; 30 | justify-content: center; 31 | 32 | @media only screen and (max-width: 550px) { 33 | //everythin greater than 34 | flex-wrap: wrap; 35 | } 36 | 37 | .steps { 38 | margin: 1rem; 39 | display: flex; 40 | flex-direction: column; 41 | justify-content: space-between; 42 | 43 | min-width: 16rem; 44 | max-width: 20rem; 45 | width: 100%; 46 | } 47 | } 48 | .done { 49 | > div { 50 | max-width: 400px; 51 | margin: 0 auto; 52 | } 53 | } 54 | .identityButtons { 55 | padding: 1rem 1rem 0 1rem; 56 | display: flex; 57 | justify-content: flex-end; 58 | 59 | > * { 60 | margin-left: 0.5rem; 61 | } 62 | } 63 | } 64 | } 65 | .invalid > div { 66 | max-width: 400px; 67 | min-height: 200px; 68 | 69 | margin: 0 auto; 70 | padding: 0 1rem; 71 | 72 | > div { 73 | display: flex; 74 | 75 | input { 76 | width: initial; 77 | flex: 1 1 auto; 78 | margin-right: 0.5rem; 79 | } 80 | @media only screen and (max-width: 550px) { 81 | input { 82 | margin: 0.5rem 0; 83 | } 84 | flex-wrap: wrap; 85 | * { 86 | width: 100%; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/identity/index.scss: -------------------------------------------------------------------------------- 1 | @import "./identity.scss"; 2 | @import "./person.scss"; 3 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/identity/person.scss: -------------------------------------------------------------------------------- 1 | .person.smallView { 2 | @media only screen and (max-width: 550px) { 3 | //everythin greater than 4 | .profilePic { 5 | background-image: none !important; 6 | } 7 | .profilePic:after { 8 | all: initial; 9 | } 10 | .email { 11 | position: initial !important; 12 | } 13 | } 14 | } 15 | 16 | .person { 17 | margin: 0 auto; 18 | border-radius: 4px; 19 | overflow: hidden; 20 | min-width: 16rem; 21 | max-width: 20rem; 22 | width: 100%; 23 | 24 | background-color: #fff; 25 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 26 | 27 | .profilePic { 28 | min-width: 16rem; 29 | max-width: 24rem; 30 | width: 100%; 31 | 32 | position: relative; 33 | 34 | background-position: center; /* Center the image */ 35 | background-repeat: no-repeat; /* Do not repeat the image */ 36 | background-size: cover; 37 | 38 | .email { 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | 43 | background-color: #dfdfdf; 44 | opacity: 0.9; 45 | 46 | position: absolute; 47 | bottom: 0; 48 | width: 100%; 49 | p { 50 | @extend .regular; 51 | margin-right: 1rem; 52 | } 53 | img { 54 | height: 1.5rem; 55 | cursor: pointer; 56 | } 57 | } 58 | } 59 | .profilePic:after { 60 | content: ""; 61 | display: block; 62 | padding-bottom: 100%; 63 | } 64 | .profilePic.male { 65 | background-image: url("../../images/male.jpg"); 66 | } 67 | .profilePic.female { 68 | background-image: url("../../images/female.jpg"); 69 | } 70 | 71 | img.picture { 72 | min-width: 16rem; 73 | max-width: 24rem; 74 | width: 100%; 75 | } 76 | 77 | .otherInfo { 78 | padding: 0 1rem; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/layout/footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | background-color: $header-secondary-color; 3 | color: $font-color-primary; 4 | padding: 0.5rem 0; 5 | 6 | flex-shrink: 0; 7 | 8 | @extend .light; 9 | 10 | .footerFlex { 11 | max-width: $screen-break; 12 | 13 | @media only screen and (min-width: $screen-break*1.6) { 14 | max-width: $screen-break * 1.6; 15 | } 16 | 17 | margin: auto; 18 | height: 100%; 19 | 20 | display: flex; 21 | justify-content: space-evenly; 22 | align-content: center; 23 | flex-wrap: wrap; 24 | 25 | > * { 26 | flex: 1 0 50%; 27 | text-align: center; 28 | margin: 1rem 0; 29 | 30 | @media only screen and (min-width: $screen-break) { 31 | flex: 1 0 33%; 32 | } 33 | @media only screen and (min-width: $screen-break*1.6) { 34 | flex: 1 0 16%; 35 | } 36 | } 37 | .item { 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | 42 | .text { 43 | padding-left: 0.5rem; 44 | } 45 | .copyright { 46 | color: $font-color-disabled; 47 | padding-left: 0; 48 | } 49 | } 50 | } 51 | } 52 | 53 | #languageSelection { 54 | position: relative; 55 | cursor: pointer; 56 | } 57 | .languageSelection { 58 | position: absolute; 59 | background-color: #d1d1d1; 60 | top: -100px; 61 | z-index: 900000; 62 | border-radius: 2px; 63 | border: solid 1px #9c9c9c; 64 | 65 | .language { 66 | display: flex; 67 | padding: 0.5rem 1rem; 68 | align-items: center; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/layout/header.scss: -------------------------------------------------------------------------------- 1 | $header-height: 3rem; 2 | $header-font-size: 1.25rem; 3 | 4 | .header { 5 | width: 100%; 6 | background-color: $header-color; 7 | color: $font-color-secondary; 8 | z-index: 1000; 9 | 10 | a:focus { 11 | outline: 0; 12 | } 13 | div { 14 | padding-left: 5%; 15 | display: flex; 16 | align-items: center; 17 | 18 | img { 19 | height: $header-height * 2/3; 20 | } 21 | h1 { 22 | font-weight: 400; 23 | font-size: $header-font-size; 24 | color: $font-color-secondary; 25 | padding-left: 0.5rem; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/layout/index.scss: -------------------------------------------------------------------------------- 1 | @import "./header.scss"; 2 | @import "./footer.scss"; -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/faqHint.scss: -------------------------------------------------------------------------------- 1 | $faq-button-width: 80px; 2 | $close-button-width: 50px; 3 | 4 | .faqHint { 5 | background-color: $header-secondary-color; 6 | position: relative; 7 | padding: 0.8rem; 8 | font-size: 0.9rem; //maybe add a media query so mobile devices dont have 4 lines of text 9 | 10 | div { 11 | font-weight: 200; 12 | width: calc(100% - #{$faq-button-width} - #{$close-button-width}); 13 | margin: auto 0px; 14 | text-align: center; 15 | display: inline-block; 16 | } 17 | button { 18 | width: $faq-button-width; 19 | cursor: pointer; 20 | position: absolute; 21 | top: calc(50% - 16px); 22 | right: $close-button-width; 23 | } 24 | .closeButton { 25 | right: 0px; 26 | background: none; 27 | color: $font-color-primary; 28 | width: $close-button-width; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/generalInfo.scss: -------------------------------------------------------------------------------- 1 | .generalInfo { 2 | padding-left: $paddingLevel; 3 | .divider { 4 | margin: 0px; 5 | } 6 | .info { 7 | .row { 8 | height: 100%; 9 | border-bottom: 1px solid #d6d6d6; 10 | padding: 0 $paddingLevel; 11 | div { 12 | display: inline-block; 13 | width: 50%; 14 | padding: 0.8rem 0px; 15 | vertical-align: middle; 16 | } 17 | .category { 18 | font-weight: 200; 19 | } 20 | .value { 21 | padding-left: $paddingLevel; 22 | margin-right: -$paddingLevel; 23 | 24 | > * { 25 | width: 100%; 26 | } 27 | } 28 | } 29 | } 30 | .submit { 31 | display: flex; 32 | justify-content: right; 33 | button { 34 | margin: 1rem 0rem 1rem 1rem; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/identityAlert.scss: -------------------------------------------------------------------------------- 1 | .identityAlert { 2 | margin: 2rem 0; 3 | display: flex; 4 | 5 | align-items: center; 6 | 7 | p { 8 | margin: 0 1rem 0 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/index.scss: -------------------------------------------------------------------------------- 1 | @import "./faqHint.scss"; 2 | @import "./newSearch.scss"; 3 | @import "./newsletter.scss"; 4 | @import "./privacyRating.scss"; 5 | @import "./generalInfo.scss"; 6 | @import "./analysis.scss"; 7 | @import "./identityAlert.scss"; 8 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/newSearch.scss: -------------------------------------------------------------------------------- 1 | $faq-button-width: 120px; 2 | 3 | .newSearch { 4 | // Note that I know this css is not optimal but it does its job 5 | background-color: $header-secondary-color; 6 | left: 0; 7 | right: 0; 8 | width: 100%; 9 | z-index: 1000; 10 | 11 | //font-size: 0.9rem; //maybe add a media query so mobile devices dont have 4 lines of text 12 | 13 | div { 14 | margin: 0 auto; 15 | max-width: $screen-break; 16 | display: flex; 17 | align-items: center; 18 | padding: 0.8rem; 19 | } 20 | input { 21 | margin: auto 0px; 22 | flex-grow: 1; 23 | width: 100px; 24 | } 25 | button { 26 | margin-left: 1rem; 27 | cursor: pointer; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/newsletter.scss: -------------------------------------------------------------------------------- 1 | $paddingLevel: 1.2rem; 2 | $privacyRatingHeadlineHeight: 2rem; 3 | 4 | .newsletter { 5 | position: relative; 6 | margin-top: 0.5rem; 7 | > div { 8 | margin-bottom: 0.5rem; 9 | } 10 | .divider { 11 | width: 100%; 12 | height: 0px; 13 | border-bottom: $divider-border; 14 | margin: 2rem 0px; 15 | } 16 | > .divider { 17 | border-bottom: $divider-border-big; 18 | } 19 | h1 { 20 | font-size: $privacyRatingHeadlineHeight; 21 | font-weight: 200; 22 | margin: 1.25rem 0px; 23 | } 24 | h2 { 25 | font-size: 1.5rem; 26 | font-weight: 200; 27 | margin: 1.25rem 0px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/newsletter/privacyRating.scss: -------------------------------------------------------------------------------- 1 | .privacyRating { 2 | padding: 0px $paddingLevel; 3 | position: relative; 4 | padding-top: 1px; //this is here to activate the h1 padding 5 | 6 | .rating { 7 | font-size: 12.5rem; 8 | margin: -1rem 0px; 9 | font-weight: 600; 10 | color: #fbc02d; 11 | width: 100%; 12 | text-align: center; 13 | line-height: normal; 14 | } 15 | 16 | .shareButton { 17 | $sharebuttonHeight: 1.5rem; 18 | 19 | font-size: $sharebuttonHeight; 20 | position: absolute; 21 | right: $paddingLevel; 22 | top: calc((#{$privacyRatingHeadlineHeight} * 1.2 - #{$sharebuttonHeight}) / 2 + 1.25rem); 23 | } 24 | } 25 | .ratingHistory { 26 | padding: 0px 1.2rem; 27 | h2 { 28 | margin: 1.5em 0 0.5rem 0; 29 | } 30 | .ratings { 31 | display: flex; 32 | flex-flow: row; 33 | justify-content: space-between; 34 | 35 | overflow-x: auto; 36 | margin: 0 0 1rem 0; 37 | 38 | .historicRating { 39 | flex: 1 0 4rem; 40 | 41 | display: flex; 42 | flex-flow: column; 43 | justify-content: center; 44 | align-items: center; 45 | 46 | .grade { 47 | font-size: 2rem; 48 | } 49 | .date { 50 | font-weight: 200; 51 | } 52 | } 53 | } 54 | } 55 | .newsletterName { 56 | width: 100%; 57 | text-align: center; 58 | .big { 59 | font-size: 2rem; 60 | font-weight: 200; 61 | } 62 | .small { 63 | font-size: 1.5rem; 64 | font-weight: 100; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/onDemand.scss: -------------------------------------------------------------------------------- 1 | .emailInput { 2 | textarea { 3 | padding: 0; 4 | margin: 0 0 1rem 0; 5 | width: calc(100% - 2px); 6 | height: 100%; 7 | } 8 | } 9 | .onDemandInstructions { 10 | margin: 1rem 0; 11 | border: 1px solid $header-secondary-color; 12 | > div { 13 | border: none; 14 | } 15 | .collapsibleSmall { 16 | background-color: $header-secondary-color; 17 | display: flex; 18 | align-items: center; 19 | padding: 0 1rem; 20 | h2 { 21 | margin-right: 1rem; 22 | } 23 | button { 24 | flex: 0 0 auto; 25 | } 26 | } 27 | .collapsibleBig { 28 | border: none; 29 | } 30 | 31 | .instructions { 32 | margin: 1rem; 33 | h3 { 34 | font-weight: 300; 35 | font-size: 1.25rem; 36 | } 37 | h4 { 38 | font-weight: 300; 39 | font-size: 1rem; 40 | margin: 0.5rem; 41 | } 42 | .selection { 43 | .radioButton { 44 | margin: 1rem 0; 45 | font-size: 1.125rem; 46 | font-weight: 300; 47 | display: flex; 48 | 49 | input { 50 | margin: 0 1rem; 51 | } 52 | } 53 | } 54 | .instruction { 55 | img { 56 | display: block; 57 | margin: 1rem auto; 58 | max-width: 100%; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/spinner.scss: -------------------------------------------------------------------------------- 1 | .spinner { 2 | $spinner-height: 5rem; 3 | 4 | position: absolute; 5 | top: calc(50% - #{$spinner-height}/ 2); 6 | left: calc(50% - #{$spinner-height}/ 2); 7 | 8 | border: 0.625rem solid #dfdfdf; 9 | border-radius: 50%; 10 | border-top: 0.625rem solid $header-color; 11 | width: $spinner-height; 12 | height: $spinner-height; 13 | -webkit-animation: spin 750ms linear infinite; /* Safari */ 14 | animation: spin 750ms linear infinite; 15 | } 16 | 17 | /* Safari */ 18 | @-webkit-keyframes spin { 19 | 0% { 20 | -webkit-transform: rotate(0deg); 21 | } 22 | 100% { 23 | -webkit-transform: rotate(360deg); 24 | } 25 | } 26 | 27 | @keyframes spin { 28 | 0% { 29 | transform: rotate(0deg); 30 | } 31 | 100% { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/staticPages/faq.scss: -------------------------------------------------------------------------------- 1 | .faq { 2 | .questions { 3 | padding-left: $paddingLevel; 4 | .collapsibleBig { 5 | padding: 1rem 0; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/staticPages/imprint.scss: -------------------------------------------------------------------------------- 1 | .imprint, 2 | .privacy, 3 | .faq { 4 | p { 5 | margin: 1rem; 6 | line-height: 1.5rem; 7 | } 8 | li { 9 | margin: 0.5rem 0rem; 10 | } 11 | .text { 12 | line-height: normal; 13 | } 14 | h3 { 15 | margin: 1rem; 16 | } 17 | h2 { 18 | margin-top: 3rem; 19 | } 20 | a { 21 | text-decoration: underline !important; 22 | color: #9c27b0 !important; 23 | } 24 | em { 25 | @extend .regular; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/staticPages/index.scss: -------------------------------------------------------------------------------- 1 | @import "./notFound.scss"; 2 | @import "./imprint.scss"; 3 | @import "./faq.scss"; 4 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/staticPages/notFound.scss: -------------------------------------------------------------------------------- 1 | .notFound { 2 | margin-top: $header-height + 4.1rem; 3 | padding: 1rem; 4 | > div { 5 | text-align: center; 6 | margin-bottom: 1.5rem; 7 | } 8 | .heading { 9 | font-size: 6rem; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /privacymail/website/src/assets/styles/stepper.scss: -------------------------------------------------------------------------------- 1 | $icon-size: 1.5rem; 2 | 3 | .stepper { 4 | .heading { 5 | display: flex; 6 | 7 | @media only screen and (min-width: 600px) { 8 | //everythin greater than 9 | 10 | align-items: center; 11 | flex-flow: row; 12 | 13 | .step { 14 | padding: 0 1rem; 15 | } 16 | .line { 17 | display: initial; 18 | flex: 1 1 auto; 19 | border-color: #bdbdbd; 20 | border-top-style: solid; 21 | border-top-width: 1px; 22 | } 23 | } 24 | @media only screen and (max-width: 600px) { 25 | //everythin greater than 26 | 27 | align-items: flex-start; 28 | flex-flow: column; 29 | 30 | .line { 31 | display: none; 32 | } 33 | .step { 34 | padding: 0.5rem 1rem; 35 | } 36 | } 37 | .step { 38 | > span { 39 | display: flex; 40 | align-items: center; 41 | 42 | > span, 43 | > div { 44 | margin-right: 0.5rem; 45 | 46 | height: $icon-size; 47 | width: $icon-size; 48 | } 49 | } 50 | } 51 | } 52 | 53 | .stepperContent { 54 | margin: 0.5rem 0; 55 | padding: 0.5rem; 56 | 57 | border-radius: 2px; 58 | background-color: #f7f7f7; 59 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 60 | } 61 | } 62 | .numberIcon { 63 | font-size: $icon-size/1.65; 64 | border-radius: $icon-size/2; 65 | height: $icon-size; 66 | width: $icon-size; 67 | min-width: $icon-size; 68 | max-width: $icon-size; 69 | text-align: center; 70 | line-height: $icon-size; 71 | } 72 | .numberIcon.inactive { 73 | background-color: $font-color-disabled; 74 | color: $font-color-secondary; 75 | } 76 | .numberIcon.active { 77 | background-color: $header-color; 78 | color: $font-color-secondary; 79 | } 80 | -------------------------------------------------------------------------------- /privacymail/website/src/components/embed/AnalysisEmbed.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IEmbed } from "../../repository"; 4 | import OnOpenThirdparties from "./EmbedOnOpenThirdparties"; 5 | import OnClickThirdparties from "./EmbedOnClickThirdparties"; 6 | import PersonalisedLinks from "./EmbedPersonalisedLinks"; 7 | 8 | interface AnalysisProps { 9 | embed?: IEmbed; 10 | } 11 | /** 12 | * Defines the Analysis of embeds 13 | */ 14 | const AnalysisEmbed = (props: AnalysisProps) => { 15 | //Filters all Newslettes that embed this embed as ONVIEW 16 | const onOpenThirdparties = props.embed?.services.filter(service => service.embed_as.includes("ONVIEW")); 17 | 18 | //Filters all Newslettes that embed this embed as ONCLICK 19 | const onClickThirdparties = props.embed?.services.filter(service => service.embed_as.includes("ONCLICK")); 20 | return ( 21 |
22 |

23 | analysis_analysis 24 |

25 | 30 | 35 | 36 |
37 | ); 38 | }; 39 | export default AnalysisEmbed; 40 | -------------------------------------------------------------------------------- /privacymail/website/src/components/embed/Embed.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useParams, withRouter, RouteComponentProps } from "react-router-dom"; 3 | import FaqHint from "../newsletter/FaqHint"; 4 | import GerneralInfo from "../newsletter/GeneralInfo"; 5 | import AnalysisEmbed from "./AnalysisEmbed"; 6 | import { getEmbed, IEmbed } from "../../repository"; 7 | import Spinner from "../../utils/Spinner"; 8 | import EmbedHeadline from "./EmbedHeadline"; 9 | 10 | interface EmbedProps extends RouteComponentProps {} 11 | 12 | /** 13 | * Defines the Layout of the embedanalysis 14 | */ 15 | const Embed = (props: EmbedProps) => { 16 | let { id } = useParams(); 17 | const [embed, setEmbed] = useState(); 18 | const [isLoading, setIsLoading] = useState(true); 19 | 20 | /** 21 | * Refetched the data from the backend if the newsletter id changes 22 | */ 23 | useEffect(() => { 24 | setIsLoading(true); 25 | getEmbed(id, props.history, (newsletter: IEmbed) => { 26 | setEmbed(newsletter); 27 | setIsLoading(false); 28 | }); 29 | }, [id, props.history]); 30 | 31 | return ( 32 | 33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | ); 43 | }; 44 | export default withRouter(Embed); 45 | -------------------------------------------------------------------------------- /privacymail/website/src/components/embed/EmbedHeadline.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import SplitDomainName from "../../utils/SplitDomainName"; 4 | interface EmbedHeaderProps { 5 | embedName: string; 6 | } 7 | /** 8 | * This displays the Embed name 9 | */ 10 | const EmbedHeadline = (props: EmbedHeaderProps) => { 11 | return ( 12 |
13 |

14 | Thirdparty 15 |

16 | 17 |
18 | ); 19 | }; 20 | 21 | export default EmbedHeadline; 22 | -------------------------------------------------------------------------------- /privacymail/website/src/components/embed/EmbedOnClickThirdparties.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IThirdParty, Reliability } from "../../repository"; 4 | import CollapsibleItem from "../../utils/CollapsibleItem"; 5 | import Methode from "../newsletter/analysis/Methode"; 6 | import ThirdpartyConnections from "../newsletter/analysis/ThirdpartyConnections"; 7 | interface OnClickThirdpartiesProps { 8 | thirdparties?: IThirdParty[]; 9 | homeUrl?: string; 10 | reliability?: Reliability; 11 | } 12 | /** 13 | * Defines how the ONCLICK analysis should look like 14 | */ 15 | const EmbedOnClickThirdparties = (props: OnClickThirdpartiesProps) => { 16 | return ( 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | /** 24 | * Defines how the ONCLICK analysis preview should look like 25 | */ 26 | const OnClickThirdpartiesSmall = (props: OnClickThirdpartiesProps) => { 27 | return ( 28 | <> 29 |
{props.thirdparties?.length}
30 |
31 | 32 |
33 | 34 | ); 35 | }; 36 | /** 37 | * Defines how the ONCLICK analysis expanded view should look like 38 | */ 39 | const OnClickThirdpartiesBig = (props: OnClickThirdpartiesProps) => { 40 | return ( 41 | <> 42 |

43 | embed_connections 44 |

45 | 46 | 47 |
48 |
49 |

50 | embed_problemHeadline 51 |

52 |

53 | embed_problemOnClick 54 |

55 |
56 | 57 |
58 | 59 | 60 | 61 | ); 62 | }; 63 | 64 | export default EmbedOnClickThirdparties; 65 | -------------------------------------------------------------------------------- /privacymail/website/src/components/embed/EmbedOnOpenThirdparties.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IThirdParty, Reliability } from "../../repository"; 4 | import CollapsibleItem from "../../utils/CollapsibleItem"; 5 | import Methode from "../newsletter/analysis/Methode"; 6 | import ThirdpartyConnections from "../newsletter/analysis/ThirdpartyConnections"; 7 | 8 | interface OnOpenThirdpartiesProps { 9 | thirdparties?: IThirdParty[]; 10 | homeUrl?: string; 11 | reliability?: Reliability; 12 | } 13 | /** 14 | * Defines how the ONOPEN analysis should look like 15 | */ 16 | const EmbedOnOpenThirdparties = (props: OnOpenThirdpartiesProps) => { 17 | return ( 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | /** 25 | * Defines how the ONOPEN analysis preview should look like 26 | */ 27 | const OnOpenThirdpartiesSmall = (props: OnOpenThirdpartiesProps) => { 28 | return ( 29 | <> 30 |
{props.thirdparties?.length}
31 |
32 | 33 |
34 | 35 | ); 36 | }; 37 | /** 38 | * Defines how the ONOPEN analysis expanded view should look like 39 | */ 40 | const OnOpenThirdpartiesBig = (props: OnOpenThirdpartiesProps) => { 41 | return ( 42 | <> 43 |

44 | embed_connections 45 |

46 | 47 | 48 |
49 |
50 |

51 | embed_problemHeadline 52 |

53 |

54 | embed_problemOnOpen 55 |

56 |
57 | 58 |
59 | 60 | 61 | 62 | ); 63 | }; 64 | 65 | export default EmbedOnOpenThirdparties; 66 | -------------------------------------------------------------------------------- /privacymail/website/src/components/faq/FAQ.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import i18n from "../../i18n/i18n"; 4 | import faqJson from "../../i18n/faq.json"; 5 | import CollapsibleItem from "../../utils/CollapsibleItem"; 6 | import { Trans } from "react-i18next"; 7 | 8 | /** 9 | * This generates the FAQ by the faq.json in the i18n folder 10 | */ 11 | const FAQ = () => { 12 | //these are all the diffrent groups of questions 13 | const groups: string[] = []; 14 | faqJson.forEach(question => { 15 | if (!groups.includes(question.group)) groups.push(question.group); 16 | }); 17 | 18 | /** 19 | * this generates the questions from a given list 20 | * @param questions list of questions 21 | */ 22 | const generateQuestions = (questions: any[]) => { 23 | const currentLanguage = i18n.language.split("-")[0]; 24 | return questions.map(question => ( 25 | 26 | {question.question[currentLanguage]} 27 | {question.answer[currentLanguage]} 28 | 29 | )); 30 | }; 31 | 32 | /** 33 | * This generates the questions by the faq.json in the i18n folder 34 | * @param groups list of groups 35 | * @param questions list of questions 36 | */ 37 | const generateGroups = (groups: string[], questions: any[]) => { 38 | return groups.map(group => ( 39 | 40 |

41 | {group} 42 |

43 |
44 | {generateQuestions(questions.filter(question => question.group === group))} 45 |
46 |
47 | )); 48 | }; 49 | return ( 50 |
51 |

52 | faq_headline 53 |

54 |

55 | faq_subheadline 56 |

57 | {generateGroups(groups, faqJson)} 58 |
59 | ); 60 | }; 61 | 62 | export default FAQ; 63 | -------------------------------------------------------------------------------- /privacymail/website/src/components/footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { Icon } from "../../utils/Icon"; 4 | import { Link } from "react-router-dom"; 5 | import LanguageSelection from "./LanguageSelection"; 6 | /** 7 | * Defines the Footer of PrivacyMail 8 | */ 9 | function Footer() { 10 | return ( 11 |
12 |
13 | 14 | 15 |
16 | question_answer 17 |
18 | footer_faq 19 |
20 |
21 | 22 | 23 |
24 | github 25 |
26 | footer_github 27 |
28 |
29 |
30 | 31 |
32 | alternate_email 33 |
34 | footer_imprint 35 |
36 |
37 | 38 | 39 |
40 | policy 41 |
42 | footer_dataProtection 43 |
44 |
45 | 46 |
47 |
© {new Date().getFullYear()}, PrivacyMail Team
48 |
49 |
50 |
51 | ); 52 | } 53 | export default Footer; 54 | -------------------------------------------------------------------------------- /privacymail/website/src/components/header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logo from "../../assets/images/logo.png"; 3 | import { Link } from "react-router-dom"; 4 | 5 | /** 6 | * Defines the Header of PrivacyMail 7 | */ 8 | const Header = () => { 9 | return ( 10 |
11 | 12 |
13 | 14 |

PrivacyMail

15 |
16 | 17 |
18 | ); 19 | }; 20 | export default Header; 21 | -------------------------------------------------------------------------------- /privacymail/website/src/components/home/Detection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IconList, IconListItem } from "../../utils/IconList"; 4 | 5 | /** 6 | * This explains how we detect Tracking 7 | */ 8 | const Detection = () => { 9 | return ( 10 |
11 |

12 | home_howWeDetectIt 13 |

14 | 15 | 16 | home_howWeDetectIt1 17 | 18 | 19 | home_howWeDetectIt2 20 | 21 | 22 | home_howWeDetectIt3 23 | 24 | 25 | home_howWeDetectIt4 26 | 27 | 28 |
29 | ); 30 | }; 31 | export default Detection; 32 | -------------------------------------------------------------------------------- /privacymail/website/src/components/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Welcome from "./Welcome"; 3 | import WhatWeDo from "./WhatWeDo"; 4 | import Tracking from "./Tracking"; 5 | import WhyPrivacymail from "./WhyPrivacymail"; 6 | import Detection from "./Detection"; 7 | 8 | /** 9 | * This groups all the items of the homepage 10 | */ 11 | const Home = () => { 12 | return ( 13 |
14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | export default Home; 27 | -------------------------------------------------------------------------------- /privacymail/website/src/components/home/Statistics.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IStatistics, getStatistics } from "../../repository"; 4 | 5 | import i18n from "../../i18n/i18n"; 6 | /** 7 | * This show the current newsletter statistics of PrivacyMail 8 | */ 9 | const Statistics = () => { 10 | const [statistics, setStatistics] = useState(); 11 | useEffect(() => getStatistics(setStatistics), []); 12 | 13 | return ( 14 |
15 |
16 |
{statistics?.email_count.toLocaleString(i18n.language) ?? "..."}
17 |

18 | home_emailsAnalysied 19 |

20 |
21 |
22 |
{statistics?.tracker_count.toLocaleString(i18n.language) ?? "..."}
23 |

24 | home_found3rdPraties 25 |

26 |
27 |
28 |
{statistics?.service_count.toLocaleString(i18n.language) ?? "..."}
29 |

30 | home_registeredNewsletters 31 |

32 |
33 |
34 | ); 35 | }; 36 | export default Statistics; 37 | -------------------------------------------------------------------------------- /privacymail/website/src/components/home/Tracking.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IconList, IconListItem } from "../../utils/IconList"; 4 | 5 | /** 6 | * This explaines how tracking works 7 | */ 8 | const Tracking = () => { 9 | return ( 10 |
11 |

12 | home_howItWorks 13 |

14 | 15 | 16 | home_howItWorks1 17 | 18 | 19 | home_howItWorks2 20 | 21 | 22 | home_howItWorks3 23 | 24 | 25 | home_howItWorks4 26 | 27 | 28 |
29 | ); 30 | }; 31 | export default Tracking; 32 | -------------------------------------------------------------------------------- /privacymail/website/src/components/home/WhatWeDo.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import Statistics from "./Statistics"; 4 | 5 | /** 6 | * This gives a short summary why we do email tracking analysis 7 | */ 8 | const WhatWeDo = () => { 9 | return ( 10 |
11 |

12 | home_shiningALight 13 |

14 |
15 |

16 | home_shiningALightDetail 17 |

18 |
19 | 20 | 21 |
22 | ); 23 | }; 24 | export default WhatWeDo; 25 | -------------------------------------------------------------------------------- /privacymail/website/src/components/home/WhyPrivacymail.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { IconList, IconListItem } from "../../utils/IconList"; 4 | 5 | /** 6 | * This explaines why somebody should use PrivacyMail 7 | */ 8 | const WhyPrivacymail = () => { 9 | return ( 10 |
11 |

12 | home_whyUsePM 13 |

14 | 15 | 16 |

17 | home_whyUsePM1 18 |

19 |

20 | home_whyUsePM1detail 21 |

22 |
23 | 24 |

25 | home_whyUsePM2 26 |

27 |

28 | home_whyUsePM2detail 29 |

30 |
31 | 32 |

33 | home_whyUsePM3 34 |

35 |

36 | home_whyUsePM3detail 37 |

38 |
39 |
40 |
41 | ); 42 | }; 43 | export default WhyPrivacymail; 44 | -------------------------------------------------------------------------------- /privacymail/website/src/components/identity/Person.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IIdentity } from "../../repository/identity"; 3 | 4 | import { Icon } from "../../utils/Icon"; 5 | import { Trans } from "react-i18next"; 6 | 7 | interface PersonProps { 8 | identity?: IIdentity; 9 | className?: string; 10 | } 11 | /** 12 | * This is how a Identity gets visualized. 13 | */ 14 | const Person = (props: PersonProps) => { 15 | //copys the identity emailadress to the users clipboard 16 | const copyToClipboard = () => { 17 | navigator.clipboard.writeText(props.identity?.mail || ""); 18 | }; 19 | 20 | return ( 21 |
22 |
23 |
24 |

{props.identity?.mail}

25 | copied} titleDuration={1000} height={24}> 26 | file_copy 27 | 28 |
29 |
30 | 31 |
32 |

33 | 34 | identity_name 35 | {": "} 36 | 37 | {props.identity?.first_name} {props.identity?.surname} 38 |

39 |

40 | 41 | identity_gender 42 | {": "} 43 | 44 | {props.identity?.gender ? male : female} 45 |

46 |

47 | 48 | identity_website 49 | {": "} 50 | 51 | 52 | {props.identity?.service.url} 53 | 54 |

55 |
56 |
57 | ); 58 | }; 59 | 60 | export default Person; 61 | -------------------------------------------------------------------------------- /privacymail/website/src/components/imprint/Imprint.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | 4 | /** 5 | * This is the Imprint 6 | */ 7 | const Imprint = () => { 8 | return ( 9 |
10 |

11 | imprint 12 |

13 | 14 |

15 | imprint_headline1 16 |

17 |

18 | imprint_data1 19 |

20 | 21 |

22 | imprint_headline2 23 |

24 |

25 | imprint_disclaimer1 26 |

27 |

28 | imprint_data6 29 |

30 | 31 |

32 | imprint_headline3 33 |

34 |

35 | imprint_headline7 36 |

37 |

38 | imprint_data2 39 |

40 | 41 |

42 | imprint_headline4 43 |

44 |

45 | imprint_data3 46 |

47 | 48 |

49 | imprint_headline5 50 |

51 |

52 | imprint_data4 53 |

54 | 55 |

56 | imprint_headline6 57 |

58 |

59 | imprint_data5 60 |

61 |
62 | ); 63 | }; 64 | 65 | export default Imprint; 66 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/FaqHint.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { Link } from "react-router-dom"; 4 | 5 | /** 6 | * Shows a hint to our FAQ 7 | */ 8 | const FaqHint = () => { 9 | //checks if the banner was already dissmissed 10 | const [showBanner, setShowBanner] = useState( 11 | !JSON.parse(window.localStorage.getItem("faqBannerDismissed") || "false") 12 | ); 13 | 14 | /** 15 | * dismisses the banner 16 | */ 17 | const dismissBanner = () => { 18 | window.localStorage.setItem("faqBannerDismissed", "true"); 19 | setShowBanner(false); 20 | }; 21 | 22 | return showBanner ? ( 23 |
24 |
25 | faqHint 26 |
27 | 30 | 31 | 34 | 35 |
36 | ) : null; 37 | }; 38 | export default FaqHint; 39 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/HistoryRating.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IRating } from "../../repository"; 3 | import { Trans } from "react-i18next"; 4 | import { convertRatingToMark } from "../../utils/functions/convertRatingToMark"; 5 | import { getRatingColor } from "../../utils/functions/getRatingColor"; 6 | interface IHistoryRating { 7 | ratings?: IRating[]; 8 | } 9 | const HistoryRating = (props: IHistoryRating) => { 10 | return ( 11 | <> 12 |
13 |

14 | analysis_ratingHistory 15 |

16 |
17 | {props.ratings 18 | ?.sort((a, b) => { 19 | return new Date(b.date ?? 0).getTime() - new Date(a.date ?? 0).getTime(); 20 | }) 21 | .slice(0, 10) 22 | .map((rating, index) => { 23 | const date = new Date(rating.date ?? 0); 24 | const now = new Date(); 25 | const datestring = 26 | date.getFullYear() === now.getFullYear() 27 | ? date.getDate() + "." + date.getMonth() + "." 28 | : date.getMonth() + "/" + date.getFullYear(); 29 | 30 | return ( 31 |
32 | 36 | {convertRatingToMark(rating?.rating || -1)} 37 | 38 | {datestring} 39 |
40 | ); 41 | })} 42 |
43 |
44 | 45 | ); 46 | }; 47 | export default HistoryRating; 48 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/IdentityAlert.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { Link } from "react-router-dom"; 4 | 5 | interface IdentityAlertProps { 6 | newsletterName: string; 7 | } 8 | /** 9 | * Displays a litte Alert thats informing the user that to few identitys have been registered for this newsletter 10 | */ 11 | const IdentityAlert = (props: IdentityAlertProps) => { 12 | return ( 13 |
14 |

15 | identity_lowIdentityCount 16 |

17 | 18 | 21 | 22 |
23 | ); 24 | }; 25 | export default IdentityAlert; 26 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/NewSearch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Trans, withTranslation, WithTranslation } from "react-i18next"; 3 | import { Link, useParams, withRouter, RouteComponentProps } from "react-router-dom"; 4 | import { clickButtonOnEnterKeyById } from "../../utils/functions/onEnterKey"; 5 | 6 | interface NewSearchProps extends WithTranslation, RouteComponentProps { 7 | currentSearch?: string; 8 | } 9 | /** 10 | * Gives the user the opportunity to directly search for a new newsletter 11 | */ 12 | const NewSearch = (props: NewSearchProps) => { 13 | //receives id fron the url 14 | let { id } = useParams(); 15 | const hasId = ["service", "serviceNotFound"].includes(props.history.location.pathname.split("/")[1]); 16 | 17 | //initilizes the new search with the id 18 | const [newsletter, setNewsletter] = useState((hasId && id) || ""); 19 | //resets new search if url changes 20 | useEffect(() => setNewsletter((hasId && id) || ""), [id, hasId]); 21 | 22 | const placeholder = props.currentSearch || props.t("home_inputPlaceholder"); 23 | return ( 24 |
25 |
26 | setNewsletter(e.target.value)} 31 | onKeyUp={e => clickButtonOnEnterKeyById(e, "analizeButton")} 32 | /> 33 | 34 | 37 | 38 |
39 |
40 | ); 41 | }; 42 | export default withRouter(withTranslation()(NewSearch)); 43 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/NoEmailAlert.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { Link } from "react-router-dom"; 4 | 5 | interface NoEmailAlertProps { 6 | newsletterName: string; 7 | } 8 | /** 9 | * Displays a litte alert if PrivacyMail has not received any email from this newsletter 10 | */ 11 | const NoEmailAlert = (props: NoEmailAlertProps) => { 12 | return ( 13 |
14 |

15 | analysis_noEmailsReceived 16 |

17 | 18 | 21 | 22 |
23 | ); 24 | }; 25 | export default NoEmailAlert; 26 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/PrivacyRating.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import ShareButton from "./ShareButton"; 4 | import { IRating } from "../../repository"; 5 | import { getRatingColor } from "../../utils/functions/getRatingColor"; 6 | import SplitDomainName from "../../utils/SplitDomainName"; 7 | import { convertRatingToMark } from "../../utils/functions/convertRatingToMark"; 8 | interface Color { 9 | red: number; 10 | green: number; 11 | blue: number; 12 | } 13 | interface PrivacyRatingProps { 14 | newsletter: string; 15 | privacyRating?: IRating; 16 | } 17 | /** 18 | * Displayes the PrivacyRating 19 | */ 20 | const PrivacyRating = (props: PrivacyRatingProps) => { 21 | const grade = convertRatingToMark(props.privacyRating?.rating || -1); 22 | return ( 23 |
24 |

25 | analysis_privacyRating 26 |

27 |
28 | {props.privacyRating?.rating && grade} 29 |
30 | 31 | 32 |
33 | ); 34 | }; 35 | export default PrivacyRating; 36 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/ShareButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon } from "../../utils/Icon"; 3 | import { withTranslation, WithTranslation } from "react-i18next"; 4 | interface ShareButtonProps extends WithTranslation { 5 | newsletterName: string; 6 | rating: string; 7 | } 8 | /** 9 | * Displayes a Share Icon that triggers the native share of the device (only works on mobile browsers) 10 | */ 11 | const ShareButton = (props: ShareButtonProps) => { 12 | const navigatorAny: any = navigator; 13 | 14 | /** 15 | * Generates the Text that for the Share Funtion and triggers it 16 | */ 17 | const triggerNativeShare = () => { 18 | const shareData = { 19 | title: window.location.hostname, 20 | url: window.location.href, 21 | text: 22 | props 23 | .t("share_text") 24 | .replace("#company", props.newsletterName) 25 | .replace("#rating", props.rating) + "\n \n" 26 | }; 27 | navigatorAny.share(shareData); 28 | }; 29 | 30 | return navigatorAny.share ? ( 31 | 32 | share 33 | 34 | ) : null; 35 | }; 36 | export default withTranslation()(ShareButton); 37 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/analysis/Analysis.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { INewsletter } from "../../../repository"; 4 | import OnOpenThirdparties from "./OnOpenThirdparties"; 5 | import OnClickThirdparties from "./OnClickThirdparties"; 6 | import ABTesting from "./ABTesting"; 7 | import Spam from "./Spam"; 8 | import PersonalisedLinks from "./PersonalisedLinks"; 9 | 10 | interface AnalysisProps { 11 | newsletter?: INewsletter; 12 | } 13 | 14 | /** 15 | * Defines the Analysis 16 | */ 17 | const Analysis = (props: AnalysisProps) => { 18 | //Filters all ONVIEW Thirdparties 19 | const onOpenThirdparties = props.newsletter?.third_parties.filter(thirdpartie => 20 | thirdpartie.embed_as.includes("ONVIEW") 21 | ); 22 | 23 | //Filters all ONCLICK Thirdparties 24 | const onClickThirdparties = props.newsletter?.third_parties.filter(thirdpartie => 25 | thirdpartie.embed_as.includes("ONCLICK") 26 | ); 27 | return ( 28 |
29 |

30 | analysis_analysis 31 |

32 | 37 | 42 | 46 | 47 | 48 |
49 | ); 50 | }; 51 | export default Analysis; 52 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/analysis/ColoredNumbers.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { getRatingColor } from "../../../utils/functions/getRatingColor"; 3 | 4 | interface ColoredNumbers { 5 | number?: number; //number of violations found 6 | className?: string; 7 | pow?: number; //decides how fast the conversion should reach 1 8 | } 9 | /** 10 | * Colores a Number based on the number of violations 11 | */ 12 | const ColoredNumbers = (props: ColoredNumbers) => { 13 | /** 14 | * Converts any number to a number between 0 and 1 15 | * @param number The number of violations 16 | */ 17 | const numberToFraction = (number: number) => { 18 | return -Math.pow(1 - (props.pow ?? 0.5), number) + 1; 19 | }; 20 | 21 | return ( 22 |
26 | {props.number} 27 |
28 | ); 29 | }; 30 | export default ColoredNumbers; 31 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/analysis/Methode.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { Reliability } from "../../../repository"; 4 | 5 | interface MethodeProps { 6 | reliability?: Reliability; 7 | textId?: string; 8 | } 9 | /** 10 | * Displays the methode part of the big few of the analysis items 11 | */ 12 | const Methode = (props: MethodeProps) => { 13 | return ( 14 |
15 |

16 | analysis_methode 17 |

18 | {props.reliability && ( 19 |
20 | analysis_{props.reliability} 21 |
22 | )} 23 | {props.textId && ( 24 |
25 | {props.textId} 26 |
27 | )} 28 |
29 | ); 30 | }; 31 | 32 | export default Methode; 33 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/analysis/PassOrNotIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Icon } from "../../../utils/Icon"; 3 | 4 | interface PassOrNotIconProps { 5 | status?: PassOrNotState; 6 | className?: string; 7 | } 8 | 9 | export enum PassOrNotState { 10 | Denied = "Denied", 11 | Passed = "Passed", 12 | Disabled = "Disabled" 13 | } 14 | /** 15 | * This decides wich Icon should be displayed in the small version of some analysis items 16 | */ 17 | const PassOrNotIcon = (props: PassOrNotIconProps) => { 18 | const getIcon = (status: PassOrNotState | undefined) => { 19 | switch (status) { 20 | case PassOrNotState.Denied: 21 | return cancel; 22 | case PassOrNotState.Passed: 23 | return check_circle; 24 | case PassOrNotState.Disabled: 25 | return remove_circle; 26 | default: 27 | return null; 28 | } 29 | }; 30 | return
{getIcon(props.status)}
; 31 | }; 32 | 33 | export default PassOrNotIcon; 34 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/analysis/Spam.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { INewsletter, Reliability } from "../../../repository"; 4 | import PassOrNotIcon, { PassOrNotState } from "./PassOrNotIcon"; 5 | import Methode from "./Methode"; 6 | import CollapsibleItem from "../../../utils/CollapsibleItem"; 7 | 8 | interface SpamProps { 9 | newsletter?: INewsletter; 10 | reliability?: Reliability; 11 | } 12 | /** 13 | * Defines how the Spam analysis should look like 14 | */ 15 | const Spam = (props: SpamProps) => { 16 | return ( 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | /** 24 | * Defines how the Spam analysis should look like 25 | */ 26 | const SpamSmall = (props: SpamProps) => { 27 | const getStatus = (newsletter: INewsletter | undefined) => { 28 | if (newsletter?.third_party_spam === 0) { 29 | return PassOrNotState.Passed; 30 | } else if (newsletter?.third_party_spam !== 0) { 31 | return PassOrNotState.Denied; 32 | } else { 33 | return undefined; 34 | } 35 | }; 36 | const status = getStatus(props.newsletter); 37 | return ( 38 | <> 39 | 40 |
41 | {status === PassOrNotState.Passed ? analysis_Spam_no : analysis_Spam} 42 |
43 | 44 | ); 45 | }; 46 | 47 | interface SpamBigProps { 48 | reliability?: Reliability; 49 | } 50 | /** 51 | * Defines how the Spam analysis should look like 52 | */ 53 | const SpamBig = (props: SpamBigProps) => { 54 | return ( 55 | <> 56 |
57 |

58 | analysis_problemHeadline 59 |

60 |

61 | analysis_Spam_problem 62 |

63 |
64 | 65 |
66 | 67 | 68 | 69 | ); 70 | }; 71 | 72 | export default Spam; 73 | -------------------------------------------------------------------------------- /privacymail/website/src/components/newsletter/analysis/ThirdpartyConnections.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IThirdParty } from "../../../repository"; 3 | import { Link } from "react-router-dom"; 4 | import { Icon } from "../../../utils/Icon"; 5 | import { Trans } from "react-i18next"; 6 | 7 | interface ThirdpartyConnectionsProps { 8 | thirdparties?: IThirdParty[]; 9 | homeUrl?: string; 10 | linkTo?: string; 11 | } 12 | /** 13 | * This shows a displays a list of thirdparties. 14 | * The List also ges sorted. 15 | */ 16 | const ThirdpartyConnections = (props: ThirdpartyConnectionsProps) => { 17 | return ( 18 |
    19 | {props.thirdparties 20 | ?.sort((a, b) => { 21 | if (a.name === props.homeUrl) return -1; 22 | if (b.name === props.homeUrl) return 1; 23 | 24 | if (a.receives_identifier !== b.receives_identifier) { 25 | if (a.receives_identifier) return -1; 26 | if (b.receives_identifier) return 1; 27 | } 28 | 29 | return a.name < b.name ? -1 : 1; 30 | }) 31 | .map((thirdparty, index) => ( 32 | 38 | ))} 39 |
40 | ); 41 | }; 42 | 43 | interface ThirdpartyProps { 44 | thirdparty: IThirdParty; 45 | isHomeUrl: boolean; 46 | linkTo?: string; 47 | } 48 | /** 49 | * This shows single Thirdparty 50 | */ 51 | const Thirdparty = (props: ThirdpartyProps) => { 52 | return ( 53 |
  • 54 | {props.thirdparty.name} 55 |
    56 | {props.thirdparty.receives_identifier && ( 57 | thirdparty_receivesLeak}>danger 58 | )} 59 | {props.isHomeUrl && home} 60 |
    61 |
  • 62 | ); 63 | }; 64 | 65 | export default ThirdpartyConnections; 66 | -------------------------------------------------------------------------------- /privacymail/website/src/components/notfound/DefaultNotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans } from "react-i18next"; 3 | 4 | /** 5 | * This is the Default 404 page 6 | */ 7 | const DefaultNotFound = () => { 8 | return ( 9 |
    10 |
    11 | 404_heading 12 |
    13 |
    14 | 404_message3 15 |
    16 |
    17 | ); 18 | }; 19 | export default DefaultNotFound; 20 | -------------------------------------------------------------------------------- /privacymail/website/src/components/notfound/EmbedNotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { Trans } from "react-i18next"; 4 | import InvalidDomain from "../../utils/InvalidDomain"; 5 | 6 | /** 7 | * This is the 404 page if a Embed is not found 8 | */ 9 | const EmbedNotFound = () => { 10 | let { id } = useParams(); 11 | 12 | return ( 13 |
    14 |
    15 | 404_heading 16 |
    17 | home_analyise} /> 18 |
    19 | ); 20 | }; 21 | export default EmbedNotFound; 22 | -------------------------------------------------------------------------------- /privacymail/website/src/components/notfound/ServiceNotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useParams, Link } from "react-router-dom"; 3 | import { Trans } from "react-i18next"; 4 | import InvalidDomain from "../../utils/InvalidDomain"; 5 | import { isDomainVaild } from "../../utils/functions/isDomainValid"; 6 | 7 | /** 8 | * This is the 404 page if a newsletter is not found 9 | */ 10 | const ServiceNotFound = () => { 11 | let { id } = useParams(); 12 | 13 | return ( 14 |
    15 |
    16 | 404_heading 17 |
    18 | {/* If the domain is valid but not in the database the user will see a link to the identity add page */} 19 | {isDomainVaild(id || "") ? ( 20 | <> 21 |
    22 | 404_message1 23 | {id} 24 | 404_message2 25 |
    26 |
    27 | 404_improvment 28 |
    29 |
    30 | 31 | 34 | 35 |
    36 | 37 | ) : ( 38 | home_analyise} 43 | /> 44 | )} 45 |
    46 | ); 47 | }; 48 | export default ServiceNotFound; 49 | -------------------------------------------------------------------------------- /privacymail/website/src/components/onDemand/OnDemand.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Trans } from "react-i18next"; 3 | import { getEmailAnalysis, IEmailAnalysis } from "../../repository"; 4 | import OnDemandAnalysis from "./OnDemandAnalysis"; 5 | import OnDemandInput from "./OnDemandInput"; 6 | /** 7 | * This generates the On Demand Sited 8 | */ 9 | const OnDemand = () => { 10 | let session; 11 | 12 | try { 13 | session = JSON.parse(sessionStorage.getItem("emailAnalysis") ?? ""); 14 | } catch (error) { 15 | session = undefined; 16 | } 17 | const [emailAnalysis, setEmailAnalysis] = useState(session); 18 | const [rawEmail, setRawEmail] = useState(""); 19 | const [viewAnalysis, setViewAnalysis] = useState(session ? true : false); 20 | const runAnalysis = () => { 21 | setViewAnalysis(true); 22 | setEmailAnalysis(undefined); 23 | sessionStorage.removeItem("emailAnalysis"); 24 | getEmailAnalysis(rawEmail, (analysis: IEmailAnalysis | null) => { 25 | if (analysis) { 26 | setEmailAnalysis(analysis); 27 | sessionStorage.setItem("emailAnalysis", JSON.stringify(analysis)); 28 | } else { 29 | setViewAnalysis(false); 30 | } 31 | }); 32 | }; 33 | 34 | const returnToInput = () => { 35 | sessionStorage.removeItem("emailAnalysis"); 36 | setViewAnalysis(false); 37 | }; 38 | 39 | return ( 40 |
    41 |

    42 | onDemand_headline 43 |

    44 | 45 | {viewAnalysis ? ( 46 | 47 | ) : ( 48 | 49 | )} 50 |
    51 | ); 52 | }; 53 | 54 | export default OnDemand; 55 | -------------------------------------------------------------------------------- /privacymail/website/src/components/onDemand/OnDemandInput.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans, useTranslation } from "react-i18next"; 3 | import Instructions from "./instructions/Instructions"; 4 | 5 | interface OnDemandInputProps { 6 | rawEmail: string; 7 | setRawEmail: (email: string) => void; 8 | runAnalysis: () => void; 9 | } 10 | 11 | const OnDemandInput = (props: OnDemandInputProps) => { 12 | const { t } = useTranslation(); 13 | return ( 14 | <> 15 | 16 |
    17 |

    18 | onDemand_input_headlind 19 |

    20 |

    21 | onDemand_dataPrivacyInfo 22 |

    23 |